From 628060ae9f7e31b06d5e2ebbf41ddbb0552702c5 Mon Sep 17 00:00:00 2001 From: Torrentarr Dev Date: Thu, 5 Nov 2026 08:15:23 +0100 Subject: [PATCH 01/10] Refresh WebUI console and statistics; correct torrent processing and diagnostics --- .dockerignore | 1 + config.example.toml | 2 + docs/assets/favicon-16x16.png | Bin 1063 -> 1003 bytes docs/assets/favicon-32x32.png | Bin 3398 -> 2762 bytes docs/assets/favicon-48x48.png | Bin 6808 -> 4654 bytes docs/assets/logov2-clean.svg | 65 +- docs/assets/openapi.json | 284 +++ docs/configuration/config-file.md | 12 +- docs/configuration/maintenance-controls.md | 2 +- docs/configuration/seeding.md | 13 +- docs/configuration/torrents.md | 10 + docs/features/torrent-maintenance.md | 2 +- docs/webui/console-statistics.md | 29 + docs/webui/logs.md | 6 +- mkdocs.yml | 1 + scripts/check-openapi-drift.py | 13 +- scripts/generate-openapi-from-qbitrr.py | 2 + scripts/openapi_statistics.py | 53 + scripts/webui-parity-preview.py | 39 +- .../Configuration/StorageConfigMigration.cs | 18 +- .../Models/ProtectionDecision.cs | 2 + .../Services/ISeedingService.cs | 6 +- .../Services/ITorrentCacheService.cs | 14 +- .../Services/ITorrentProcessor.cs | 3 + .../Services/MaintenanceRequirements.cs | 2 +- .../Services/RemovalDecision.cs | 1 + src/Torrentarr.Host/Program.cs | 10 +- .../ApiClients/Arr/ArrActivityClient.cs | 62 + .../ApiClients/Arr/ArrClientResponse.cs | 8 +- .../ApiClients/Arr/LidarrClient.cs | 2 +- .../ApiClients/Arr/RadarrClient.cs | 2 +- .../ApiClients/Arr/ReadarrClient.cs | 2 +- .../ApiClients/Arr/SonarrClient.cs | 2 +- .../Database/DatabaseRetryExtensions.cs | 21 +- .../Database/ManualSqliteMigrations.cs | 10 + .../Database/Models/ArrActivity.cs | 28 + .../Database/Models/TransferSample.cs | 12 + .../Database/TorrentarrDbContext.cs | 15 + .../Endpoints/LogFileApi.cs | 5 +- .../Logging/ServiceLogSink.cs | 12 +- .../Services/ArrActivityService.cs | 234 +++ .../Services/ArrActivityTracking.cs | 52 + .../Services/ArrImportService.cs | 18 +- .../Services/ArrMediaService.cs | 12 +- .../Services/ArrSyncService.cs | 50 +- .../Services/MaintenanceCoordinator.cs | 12 +- .../Services/OperationalIssueMonitor.cs | 57 +- .../Services/QbitOverviewBuilder.cs | 23 + .../Services/SafeDeletionService.cs | 6 +- .../Services/SearchExecutor.cs | 1 + .../Services/SeedingService.cs | 140 +- .../Services/SonarrSearchScope.cs | 2 +- .../Services/TorrentAutomationService.cs | 10 - .../Services/TorrentCacheService.cs | 49 +- .../Services/TorrentProcessor.cs | 298 ++-- .../Services/TransferHistoryService.cs | 147 ++ .../Services/WorkerProcessSupervisor.cs | 33 +- src/Torrentarr.WebUI/Program.cs | 5 + src/Torrentarr.Workers/Program.cs | 18 +- .../Configuration/StorageMigrationTests.cs | 26 + .../TorrentClientMaintenanceTests.cs | 7 + .../Api/StatisticsEndpointTests.cs | 93 + .../ProcessOrchestratorServiceTests.cs | 26 + .../WorkerLogEventSinkTests.cs | 12 +- .../ApiClients/SonarrApiDiscoveryTests.cs | 43 + .../Database/DatabaseRetryExtensionsTests.cs | 47 + .../Database/SchemaParityTests.cs | 2 +- .../Services/ActiveWorkerSearchTests.cs | 41 +- .../Services/ArrActivityTests.cs | 205 +++ .../Services/AutomationLoggingTests.cs | 10 +- .../Services/LogRetentionTests.cs | 51 + .../Services/MaintenanceSafetyTests.cs | 16 + .../Services/OperationalIssueMonitorTests.cs | 82 + .../Services/ProtectionLimitTests.cs | 68 +- .../Services/QbitOverviewBuilderTests.cs | 21 + .../Services/SeedingServiceTests.cs | 229 ++- .../Services/TorrentCacheServiceTests.cs | 41 +- .../TorrentProcessorImportAllowlistTests.cs | 115 +- .../Services/TorrentProcessorTests.cs | 103 +- .../Services/TransferHistoryTests.cs | 115 ++ .../Services/WorkerLifecycleTests.cs | 32 +- tests/scripts/test_check_openapi_drift.py | 10 + webui/index.html | 3 +- webui/public/favicon-16x16.png | Bin 1063 -> 1003 bytes webui/public/favicon-32x32.png | Bin 3398 -> 2762 bytes webui/public/favicon-48x48.png | Bin 6808 -> 4654 bytes webui/public/favicon.ico | Bin 1079 -> 15086 bytes webui/public/icon-192.png | Bin 60407 -> 24481 bytes webui/public/icon-512.png | Bin 288554 -> 78211 bytes webui/public/logov2-clean.svg | 65 +- webui/public/manifest.json | 4 +- webui/src/AppShell.tsx | 244 ++- .../components/ArrActivityCharts.test.tsx | 148 ++ .../__tests__/components/Statistics.test.tsx | 44 + .../components/TransferHistory.test.tsx | 115 ++ webui/src/__tests__/pages/AppShell.test.tsx | 98 +- .../__tests__/pages/ProcessesView.test.tsx | 224 ++- .../pages/QbitCategoriesView.test.tsx | 50 + webui/src/__tests__/setup.ts | 18 + webui/src/api/client.ts | 37 + webui/src/api/types.ts | 47 + webui/src/components/ArrActivityCharts.tsx | 388 +++++ webui/src/components/CatalogStatistics.tsx | 90 + webui/src/components/ConfirmDialog.tsx | 2 +- webui/src/components/ShellDialog.tsx | 61 + webui/src/components/Statistics.tsx | 52 + webui/src/components/TransferHistory.tsx | 266 +++ webui/src/icons/alert.svg | 4 +- webui/src/icons/build.svg | 4 +- webui/src/icons/check-mark.svg | 6 +- webui/src/icons/close.svg | 5 +- webui/src/icons/download.svg | 6 +- webui/src/icons/gear.svg | 6 +- webui/src/icons/live-streaming.svg | 9 +- webui/src/icons/loading-arrow.svg | 4 +- webui/src/icons/log.svg | 4 +- webui/src/icons/logov2-clean.svg | 65 +- webui/src/icons/plus.svg | 5 +- webui/src/icons/process.svg | 16 +- webui/src/icons/refresh-arrow.svg | 4 +- webui/src/icons/trash.svg | 9 +- webui/src/icons/up-arrow.svg | 4 +- webui/src/icons/visibility.svg | 10 +- webui/src/pages/LoginPage.tsx | 2 + webui/src/pages/MaintenanceView.tsx | 29 + webui/src/pages/ProcessesView.tsx | 174 +- webui/src/pages/QbitCategoriesView.tsx | 116 +- .../pages/arrCatalog/ArrCatalogBodyChrome.tsx | 5 +- .../src/pages/arrCatalog/ArrCatalogShell.tsx | 6 + webui/src/styles.css | 1547 ++++++++++++----- webui/src/utils/arrActivityChart.ts | 112 ++ webui/src/utils/transferChart.ts | 30 + 132 files changed, 6145 insertions(+), 1326 deletions(-) create mode 100644 docs/webui/console-statistics.md create mode 100644 scripts/openapi_statistics.py create mode 100644 src/Torrentarr.Infrastructure/ApiClients/Arr/ArrActivityClient.cs create mode 100644 src/Torrentarr.Infrastructure/Database/Models/ArrActivity.cs create mode 100644 src/Torrentarr.Infrastructure/Database/Models/TransferSample.cs create mode 100644 src/Torrentarr.Infrastructure/Services/ArrActivityService.cs create mode 100644 src/Torrentarr.Infrastructure/Services/ArrActivityTracking.cs create mode 100644 src/Torrentarr.Infrastructure/Services/TransferHistoryService.cs create mode 100644 tests/Torrentarr.Host.Tests/Api/StatisticsEndpointTests.cs create mode 100644 tests/Torrentarr.Infrastructure.Tests/Database/DatabaseRetryExtensionsTests.cs create mode 100644 tests/Torrentarr.Infrastructure.Tests/Services/ArrActivityTests.cs create mode 100644 tests/Torrentarr.Infrastructure.Tests/Services/LogRetentionTests.cs create mode 100644 tests/Torrentarr.Infrastructure.Tests/Services/TransferHistoryTests.cs create mode 100644 webui/src/__tests__/components/ArrActivityCharts.test.tsx create mode 100644 webui/src/__tests__/components/Statistics.test.tsx create mode 100644 webui/src/__tests__/components/TransferHistory.test.tsx create mode 100644 webui/src/components/ArrActivityCharts.tsx create mode 100644 webui/src/components/CatalogStatistics.tsx create mode 100644 webui/src/components/ShellDialog.tsx create mode 100644 webui/src/components/Statistics.tsx create mode 100644 webui/src/components/TransferHistory.tsx create mode 100644 webui/src/utils/arrActivityChart.ts create mode 100644 webui/src/utils/transferChart.ts diff --git a/.dockerignore b/.dockerignore index 19a006d5..22442596 100644 --- a/.dockerignore +++ b/.dockerignore @@ -45,6 +45,7 @@ webui/coverage/ *.db-shm # Config files (use example) +.config/ config.toml # Test files diff --git a/config.example.toml b/config.example.toml index 47ba9ee5..58f8d28f 100644 --- a/config.example.toml +++ b/config.example.toml @@ -225,6 +225,8 @@ ExcludedCategories = [] ExcludedTags = [] # Explicit mappings describe the same folder in the client and Torrentarr. +# Recycling requires mappings even when both paths are identical. In Docker, +# LocalPath is inside Torrentarr; the directory must already be mounted/readable. # [[qBit.Maintenance.PathMappings]] # ClientPath = '/downloads' # LocalPath = '/data/downloads' diff --git a/docs/assets/favicon-16x16.png b/docs/assets/favicon-16x16.png index 00c828e79d245d65bc3376bfc2d7738ae70e7961..8cbdfb5453273eba711f5bd0be61b8ad3aeee6d4 100644 GIT binary patch delta 982 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 literal 6808 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 - - - - - - - - - - + + Torrentarr + + + + + + + + + + + + + + + + diff --git a/docs/assets/openapi.json b/docs/assets/openapi.json index d5e7f96e..6056c28b 100644 --- a/docs/assets/openapi.json +++ b/docs/assets/openapi.json @@ -5902,6 +5902,7 @@ }, "/web/qbit/overview": { "get": { + "description": "Includes optional summaries per available client: instance, torrentCount, totalSize, downloading, seeding, stopped, other. These monitored-category totals are computed before the display cap and deduplicated by client and torrent hash. Clients without an inventory response are omitted.", "operationId": "web_qbit_overview", "parameters": [ { @@ -6426,6 +6427,289 @@ ] } }, + "/web/statistics/arr-activity": { + "get": { + "description": "Follows WebUI authentication. Worker-level series retain seven days; no backfill. Queue depth averages fresh heartbeats. Completions count only tracked Torrentarr submissions: searches require successful command completion; imports require positive correlated download-import history after submission. Counts are timestamped when confirmed, not when requested. Missing/partial coverage is explicit. Buckets are 1, 5, or 30 minutes. Disabled/deleted workers can remain in retained history.", + "parameters": [ + { + "in": "query", + "name": "range", + "schema": { + "default": "24h", + "enum": [ + "1h", + "24h", + "7d" + ], + "type": "string" + } + }, + { + "description": "Optional configured Arr section name.", + "in": "query", + "name": "worker", + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "properties": { + "bucketMinutes": { + "type": "integer" + }, + "generatedAt": { + "format": "date-time", + "type": "string" + }, + "range": { + "type": "string" + }, + "series": { + "items": { + "properties": { + "instance": { + "type": "string" + }, + "points": { + "items": { + "properties": { + "completionSamples": { + "type": "integer" + }, + "expectedSamples": { + "type": "integer" + }, + "importsConfirmed": { + "nullable": true, + "type": "integer" + }, + "queueDepth": { + "nullable": true, + "type": "number" + }, + "queueSamples": { + "type": "integer" + }, + "searchesCompleted": { + "nullable": true, + "type": "integer" + }, + "timestamp": { + "format": "date-time", + "type": "string" + } + }, + "type": "object" + }, + "type": "array" + }, + "type": { + "type": "string" + } + }, + "type": "object" + }, + "type": "array" + } + }, + "type": "object" + } + } + }, + "description": "OK" + }, + "400": { + "description": "Invalid range or worker" + }, + "401": { + "description": "Authentication required" + } + }, + "summary": "Persistent Arr queue and confirmed activity history", + "tags": [ + "Torrentarr Statistics" + ] + } + }, + "/web/statistics/catalog": { + "get": { + "description": "Follows configured WebUI authentication. Transfer rates are bytes/second, collected once per minute and retained seven days. Completed buckets use 1, 5, or 30 minutes. Null rates indicate no observations; samples below expectedSamples indicate partial coverage. Catalog availability is monitored media with files; missing is monitored minus available. Catalog counts ignore page and text filters.", + "parameters": [ + { + "in": "query", + "name": "kind", + "required": true, + "schema": { + "enum": [ + "radarr", + "sonarr", + "lidarr", + "readarr" + ], + "type": "string" + } + }, + { + "description": "Arr section name; omitted or aggregate selects all instances of this kind.", + "in": "query", + "name": "instance", + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "properties": { + "available": { + "type": "integer" + }, + "missing": { + "type": "integer" + }, + "monitored": { + "type": "integer" + }, + "total": { + "type": "integer" + }, + "unit": { + "type": "string" + } + }, + "type": "object" + } + } + }, + "description": "OK" + }, + "400": { + "description": "Invalid range, kind, or instance" + }, + "401": { + "description": "Authentication required" + } + }, + "summary": "Complete catalog availability rollup", + "tags": [ + "Torrentarr Statistics" + ] + } + }, + "/web/statistics/transfers": { + "get": { + "description": "Follows configured WebUI authentication. Transfer rates are bytes/second, collected once per minute and retained seven days. Completed buckets use 1, 5, or 30 minutes. Null rates indicate no observations; samples below expectedSamples indicate partial coverage. Catalog availability is monitored media with files; missing is monitored minus available. Catalog counts ignore page and text filters.", + "parameters": [ + { + "description": "Enabled client name; omitted or all selects all enabled clients.", + "in": "query", + "name": "instance", + "schema": { + "type": "string" + } + }, + { + "in": "query", + "name": "range", + "schema": { + "default": "24h", + "enum": [ + "1h", + "24h", + "7d" + ], + "type": "string" + } + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "properties": { + "bucketMinutes": { + "type": "integer" + }, + "generatedAt": { + "format": "date-time", + "type": "string" + }, + "range": { + "enum": [ + "1h", + "24h", + "7d" + ], + "type": "string" + }, + "series": { + "items": { + "properties": { + "instance": { + "type": "string" + }, + "points": { + "items": { + "properties": { + "downloadRate": { + "format": "double", + "nullable": true, + "type": "number" + }, + "expectedSamples": { + "type": "integer" + }, + "samples": { + "type": "integer" + }, + "timestamp": { + "format": "date-time", + "type": "string" + }, + "uploadRate": { + "format": "double", + "nullable": true, + "type": "number" + } + }, + "type": "object" + }, + "type": "array" + } + }, + "type": "object" + }, + "type": "array" + } + }, + "type": "object" + } + } + }, + "description": "OK" + }, + "400": { + "description": "Invalid range, kind, or instance" + }, + "401": { + "description": "Authentication required" + } + }, + "summary": "Persistent whole-client transfer history", + "tags": [ + "Torrentarr Statistics" + ] + } + }, "/web/status": { "get": { "description": "All clients are equal. Read qbitInstances by instance name; the legacy qbit field has been removed.", diff --git a/docs/configuration/config-file.md b/docs/configuration/config-file.md index 3523f390..7e485227 100644 --- a/docs/configuration/config-file.md +++ b/docs/configuration/config-file.md @@ -200,6 +200,16 @@ LocalPath = "/data/downloads" The same fields work in legacy `[qBit]` and `[qBit-name]` sections. `DownloadPath` is the default root **as seen by the client**, used when creating missing categories. Existing category paths are preserved. Monitor `Path` values are absolute paths **as seen by Torrentarr**; mounts must already exist. Path mappings translate between these views without moving files. +Recycling requires explicit mappings for every configured client's file ownership inventory, including when client and Torrentarr paths are identical. A missing mapping keeps affected cleanup pending; it does not indicate a worker crash. For a shared `/downloads` mount visible at the same path in both containers: + +```toml +[[TorrentClient.local.Maintenance.PathMappings]] +ClientPath = '/downloads' +LocalPath = '/downloads' +``` + +Use this example instead of the mapping above when both paths match. For Docker, `LocalPath` is the path **inside Torrentarr's container**, not the host path. Verify the mounted directory and permissions before saving; a mapping does not create a mount. Config reload applies saved mappings on the next worker cycle, which can then perform previously blocked recycling after fresh protection and ownership checks. + New clients have no monitors. New monitor rows start disabled with a `10G` limit. Limits accept nonnegative bytes or K/M/G/T suffixes (powers of 1024); use `Enabled = false` to disable a row. Duplicate paths are rejected, while temporarily unavailable paths may still be saved. `TorrentScanIntervalSeconds` accepts only 5, 15, 30, 60, 90, or 120 seconds; new clients default to 30. Arr and qBit-only torrent processing, tracker sorting, failed/recheck handling, and routine protection inspections follow this per-client interval. Free space checks use the shortest interval among clients sharing each filesystem. Independent filesystems keep separate deadlines. Scans run immediately at startup; configuration changes and client reconnections can wake processing early. Each loop runs sequentially and skips missed ticks; slow operations and error backoff can delay scans. Fresh safety checks and manual actions may run between scans. @@ -220,7 +230,7 @@ Maintenance remains the canonical per-client configuration and all ten tools rem The old global `CompletedDownloadFolder`, `FreeSpace`, `FreeSpaceFolder`, `AutoPauseResume`, and `FreeSpaceThresholdGB` fields are migrated automatically, backed up to `config.toml.bak`, and removed from the saved file. Existing per-client values take precedence, including an explicitly empty monitor list. -For one client, a missing download root inherits the old completed folder (reverse-mapped only when unambiguous). The monitor uses the old free-space folder, completed folder, then mapped client download path. The old limit and disabled state are preserved. With multiple clients, explicit client paths are used; ambiguous paths remain disabled and produce warnings for review. Migration does not create path mappings. +For one client, a missing download root inherits the old completed folder (reverse-mapped only when unambiguous). The monitor uses the old free-space folder, completed folder, then mapped client download path. The old limit and disabled state are preserved. With multiple clients, explicit client paths are used; ambiguous paths remain disabled and produce warnings for review. Migration does not create path mappings. Unresolved migration warnings are saved under each client’s `Migration.Notices` and appear as issues linked to Config. Review and correct the indicated settings, then clear the reviewed notice entries in `config.toml`. Missing mappings required for recycling and invalid/inaccessible configured mapping roots also create issues before cleanup is attempted; those issues resolve after correction. Legacy `TORRENTARR_SETTINGS_*` environment overrides and their `QBITRR_SETTINGS_*` aliases still apply at runtime through equivalent conversion, without being written to disk. Explicitly empty primary variables suppress their aliases. diff --git a/docs/configuration/maintenance-controls.md b/docs/configuration/maintenance-controls.md index b500b5f1..89e9ee65 100644 --- a/docs/configuration/maintenance-controls.md +++ b/docs/configuration/maintenance-controls.md @@ -138,7 +138,7 @@ Generated by `Torrentarr.ConfigSchemaGenerator`. All maintenance controls are av | `Maintenance.TrackerAssignments[].TrackerContains` | text | `""` | Case-insensitive tracker URL match. | | `Maintenance.TrackerAssignments[].Category` | text | `""` | | | `Maintenance.TrackerAssignments[].Tags` | tags | `[]` | | -| `Maintenance.PathMappings` | collection | `[]` | Translate torrent client paths into paths visible to Torrentarr. This does not mount folders or move files. The longest matching client prefix wins. Saved mappings apply at the next worker processing cycle. | +| `Maintenance.PathMappings` | collection | `[]` | Translate torrent client paths into paths visible to Torrentarr. Recycling requires explicit mappings, even for identical paths; all configured clients must have complete ownership inventory. This does not mount folders or move files. The longest matching client prefix wins. Saved mappings apply at the next worker processing cycle. | | `Maintenance.PathMappings[].ClientPath` | text | `""` | Folder as seen by the torrent client. | | `Maintenance.PathMappings[].LocalPath` | text | `""` | The same folder as seen by Torrentarr, including inside its Docker container. | | `Trackers[].Protection.Policy` | select | `"custom"` | | diff --git a/docs/configuration/seeding.md b/docs/configuration/seeding.md index 46e5e7d7..921b96d0 100644 --- a/docs/configuration/seeding.md +++ b/docs/configuration/seeding.md @@ -122,14 +122,10 @@ A missed deadline preserves protection and triggers reporting/recovery. Protection checks normally run at least every five minutes when enabled, with targeted one-minute inspection for deadline or native stop-limit risk. Monitoring runs independently of long maintenance jobs. Outages may delay these checks; failures back off and preserve protection. These checks cannot guarantee service -availability or tracker-side acceptance. Authorized Torrentarr-owned pauses may be -recovered; deliberate user pauses remain blockers unless explicit paused-repair -resume permission was selected. +availability or tracker-side acceptance. H&R only restricts torrent/content deletion and recycling. Imports, pause/resume, tracker/category changes, and repair follow their configured policies and ownership checks independently. Per-torrent native ratio, seed-time and inactive seed-time stop limits can interrupt -protected seeding. Explicit protection temporarily overrides conflicting limits and -records their original values, restoring them after clearance if the user has not -changed them. Global client preferences are unchanged. See the [official qBittorrent +protected seeding. Protection reports that risk without overriding configured limits. Previously overridden limits are restored only when Torrentarr still owns the unlimited values and the torrent identity matches. Global client preferences are unchanged. See the [official qBittorrent share-limit contract](https://github.com/qbittorrent/qBittorrent/wiki/WebUI-API-%28qBittorrent-5.0%29#set-torrent-share-limit). ## Global seeding settings @@ -224,10 +220,7 @@ New policies expose AccountingGraceMinutes and AdditionalSeedHours independently ## Native stop limits -When protection is explicitly configured, conflicting managed per-torrent stop -limits are recorded and temporarily disabled to preserve seeding. After clearance, -previous values are restored only if the unlimited values still belong to Torrentarr; -later user changes are retained. Global qBittorrent preferences are never changed. +H&R does not disable native stop limits or resume torrents to enforce continuous seeding. Existing overrides from older versions are restored only while their unlimited values still belong to Torrentarr; later user changes are retained. Global qBittorrent preferences are never changed. A stopped torrent with unknown pause ownership is reported rather than assumed safe to resume. Review client global stop/queue settings separately. diff --git a/docs/configuration/torrents.md b/docs/configuration/torrents.md index 332b6e3b..acac6d69 100644 --- a/docs/configuration/torrents.md +++ b/docs/configuration/torrents.md @@ -925,3 +925,13 @@ Adjust patterns if too many valid files are excluded. - **Stalled handling** - Configure `StalledDelay` and `DoNotRemoveSlow` - **Tracker overrides** - Custom settings per tracker for optimal performance - **Start conservative** and enable automation after testing + +## Import rejection and deferred removal + +An import allowlist warning means that the configured rules excluded content needed for import. Executables remain excluded. Rejected `.rar` and split `.r00`/`.r01` archive files are identified in the diagnostic; Torrentarr does not extract archives through this workflow. Use an appropriate unpacking workflow outside Torrentarr or select a release containing allowed media files. Adding archive extensions to the allowlist does not extract them. + +Import rejection and cleanup are separate outcomes. H&R can preserve a rejected release until its configured obligations are met. A healthy protection wait is informational; missing mappings, unknown tracker evidence, and tracker deadline or native-stop risks remain actionable warnings. Legacy diagnostics use client-reported seed time, including the configured tracker update buffer; remaining time describes only the time requirement and does not clear unmet ratio obligations. + +Unchanged allowlist and removal diagnostics move to Debug after the first message. Changed blockers or configuration produce a fresh message; retries continue normally and operational issues remain current. Suppression is in memory and resets after a restart or cache eviction. + +Configured file inclusion/exclusion rules determine import eligibility. If allowed content remains and ordinary readiness checks pass, excluded files remaining on disk do not block import. `AutoDelete` cleanup is independent and retries safely; H&R can prevent that cleanup or torrent removal without suppressing the import. This applies to all excluded file types, with no screenshot or metadata exception list. All-files-excluded releases remain rejected, and unsafe paths, missing storage, failed filtering requests, and Arr failures remain actionable. diff --git a/docs/features/torrent-maintenance.md b/docs/features/torrent-maintenance.md index 3a15a55b..b6163f9d 100644 --- a/docs/features/torrent-maintenance.md +++ b/docs/features/torrent-maintenance.md @@ -23,7 +23,7 @@ Unsaved changes must be saved or discarded before switching clients or leaving. ## Scheduling and protection -Every torrent removal and deletion of referenced torrent content requires current HnR clearance. Manual execution, custom-format rejection, failed categories, import errors, and file exclusions cannot bypass it. Unmet or unknown obligations preserve the torrent and content. Arr queue removal and blocklisting for failed downloads follow confirmed protected removal; Arr is not asked to remove the torrent independently. Recycle preparation rechecks clearance and restores moved content if clearance changes before client removal. +Every torrent removal and deletion of referenced torrent content requires current HnR clearance. Manual execution, custom-format rejection, failed categories, import errors, and file exclusions cannot bypass it. Unmet or unknown obligations prevent those deletion actions. They do not block imports, configured pauses/resumes, tracker/category changes, or repair; those actions retain their own scope, capability, path, and ownership checks. Arr queue removal and blocklisting for failed downloads follow confirmed protected removal; Arr is not asked to remove the torrent independently. Recycle preparation rechecks clearance and restores moved content if clearance changes before client removal. Manual mode applies a reviewed preview once. Automatic mode runs on the saved schedule without requiring a restart and retains scheduling across restarts. Existing session settings migrate to Manual with their schedules preserved. Cron expressions use UTC. Missed runs are not replayed; persisted claims prevent the same scheduled minute from running twice after restart. diff --git a/docs/webui/console-statistics.md b/docs/webui/console-statistics.md new file mode 100644 index 00000000..785dd400 --- /dev/null +++ b/docs/webui/console-statistics.md @@ -0,0 +1,29 @@ +# Console and statistics + +Torrentarr uses grouped navigation for monitoring, libraries and clients, and administration. Display controls retain the saved theme and density preferences. Small screens use the Menu drawer. + +Transfer activity appears inside Torrent Clients. It records whole-client download/upload rates, including traffic outside monitored categories. Collection runs on the server every minute even with the browser closed. Samples survive restarts, are kept for seven days in `torrentarr.db`, and are pruned hourly. No history is backfilled. + +Choose 1 hour, 24 hours (default), or 7 days. Graphs use completed 1-, 5-, or 30-minute intervals. Each client has its own series; absent or incomplete intervals break the line. Coverage and the latest complete interval are labeled. Disabled clients are not sampled. Disconnected clients and unsupported metrics produce gaps rather than zero traffic. Live controls automatic refresh; the existing Refresh button also refreshes statistics. Changing range or client fetches that selection once. + +Monitored-category activity uses full inventories before the 500-row display cap and deduplicates overlapping categories. It is separate from whole-client transfer history and text search. Unavailable client inventories are excluded and partial coverage is labeled. + +Library bars report available and missing **monitored** movies, episodes, albums, or books from the full selected catalog, independently of page and text filters. Monitoring is a separate count. Manage summarizes operation outcomes in the latest 20 displayed runs. + +The `/web/statistics/transfers` and `/web/statistics/catalog` endpoints follow the configured WebUI authentication policy; their parameters and payloads are documented in OpenAPI. The history table is a Torrentarr extension and does not change qBitrr-compatible tables or TOML configuration. + +The logo is true vector artwork with transparent negative space, traced and shaded from the PNG reference. WebUI and documentation use the SVG; platform icons are raster exports. + +## Arr activity + +Processes uses aligned instance rows. Each Arr Torrent activity target opens its category in Torrent Clients, selecting a unique matching client or all clients when the category is shared. Old torrent searches are cleared. Search activity and restart actions remain separate. Wiki and GitHub links are available in both navigation footers. + +The two inline Arr graphs show queue depth and confirmed completed activity. Choose 1h, 24h (default), or 7d and group by Arr type (default) or individual worker. Radarr is orange, Sonarr teal, Lidarr green, and Readarr violet; worker colors and line patterns are stable. Legends toggle series. Click a graph or use the keyboard-accessible time slider to inspect an interval; the data table exposes values and coverage. + +Queue observations come from fresh worker heartbeats every minute. Stale, stopped, or unavailable workers leave gaps. Queue buckets are averages; type totals sum worker averages and disclose incomplete coverage. Completion buckets sum confirmed observations, separately for searches and torrent-import batches. Partial coverage breaks the line; it never becomes a fabricated zero. + +Only Torrentarr-submitted commands are tracked. Search requests count after Arr reports successful completion. Import requests count only with a correlated positive download-import history event dated after submission. Accepted commands, completed scans with no import, queue disappearance, tags, and failed/cancelled work do not count. Multiple files in one torrent batch and retries sharing confirmation evidence do not inflate the count. Counts use the time confirmation was recorded. Pending records survive worker restarts; unconfirmed work expires after seven days. There is no historical backfill. + +The server reconciles only instances with pending work, at most once per minute, using the existing API-version and TLS settings. Telemetry failures do not change search/import/protection behavior. Additional Torrentarr-specific tables retain samples and operation evidence for seven days; pruning runs hourly. Standalone WebUI cannot supply worker queue observations when no supervisor is present. + +`GET /web/statistics/arr-activity?range=24h&worker=Radarr-example` follows WebUI authentication and returns worker identity/type, timestamped values, queue/completion coverage, and expected sample counts. Omit worker for all configured workers and retained history. Existing transfer-history endpoints are unchanged. diff --git a/docs/webui/logs.md b/docs/webui/logs.md index 5280abfa..0568da77 100644 --- a/docs/webui/logs.md +++ b/docs/webui/logs.md @@ -32,9 +32,9 @@ attempts/retries and decision reasons; TRACE adds snapshots, effective settings and rule inputs. Runtime level selection is available from Processes. Persistent changes belong in the configuration editor. See [Debug logging](../troubleshooting/debug-logging.md). -Files append on restart and rotate daily with seven files retained per stream. -There is **no size-based rotation or file size cap**. Large TRACE files remain -accessible through bounded tail reads and streaming downloads. +Use **INFO** for normal operation and enable TRACE temporarily for investigations. Repeated per-item skips and database details require DEBUG/TRACE; cycle summaries and actionable failures remain available at INFO. + +Files append on restart and rotate daily or when they reach **128 MiB**. Each stream retains at most **eight files**, with a **seven-day age limit**; whichever limit is reached first applies. Size rotation uses numbered suffixes, and individual events remain intact. Existing oversized files age out through normal retention. Aggregate and per-service streams are retained, and sanitized file-sink diagnostics are written to stderr. ## API diff --git a/mkdocs.yml b/mkdocs.yml index 32df1ec4..c2b7eab3 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -257,6 +257,7 @@ nav: - Web UI: - webui/index.md + - Console and Statistics: webui/console-statistics.md - Processes View: webui/processes.md - Issues View: webui/issues.md - Logs View: webui/logs.md diff --git a/scripts/check-openapi-drift.py b/scripts/check-openapi-drift.py index 851c1a04..0a10102d 100644 --- a/scripts/check-openapi-drift.py +++ b/scripts/check-openapi-drift.py @@ -7,12 +7,13 @@ import sys from copy import deepcopy from openapi_status import STATUS_SCHEMA +from openapi_statistics import EXTENSION_PATHS as STATISTICS_PATHS, apply as apply_statistics from openapi_maintenance import EXTENSION_PATHS as MAINTENANCE_PATHS, apply as apply_maintenance from pathlib import Path from typing import Any HTTP_METHODS = {"get", "put", "post", "delete", "patch", "head", "options", "trace"} -TORRENTARR_EXTENSIONS = MAINTENANCE_PATHS | {f"{prefix}/issues{suffix}" for prefix in ("/web", "/api") for suffix in ("", "/summary", "/{id}")} | { +TORRENTARR_EXTENSIONS = STATISTICS_PATHS | MAINTENANCE_PATHS | {f"{prefix}/issues{suffix}" for prefix in ("/web", "/api") for suffix in ("", "/summary", "/{id}")} | { "/api/arr/{category}/open/{kind}/{entryId}", "/api/lidarr/{category}/tracks", "/api/qbit/categories", @@ -365,13 +366,21 @@ def maintenance_drift(document: dict) -> list[str]: return errors +def statistics_drift(document: dict) -> list[str]: + expected = {"paths": {}} + apply_statistics(expected) + return [f"generated statistics operation differs: {path}" + for path, operations in expected['paths'].items() + if document.get('paths', {}).get(path) != operations] + + def main(argv: list[str]) -> int: if len(argv) != 3: print(f"usage: {argv[0]} TORRENTARR_SPEC QBITRR_SPEC", file=sys.stderr) return 2 torrentarr = load(argv[1]) qbitrr = load(argv[2]) - errors = compare(torrentarr, qbitrr) + maintenance_drift(torrentarr) + errors = compare(torrentarr, qbitrr) + maintenance_drift(torrentarr) + statistics_drift(torrentarr) if errors: print(f"OpenAPI parity failed with {len(errors)} difference(s):") for error in errors: diff --git a/scripts/generate-openapi-from-qbitrr.py b/scripts/generate-openapi-from-qbitrr.py index 4c875413..4660be62 100644 --- a/scripts/generate-openapi-from-qbitrr.py +++ b/scripts/generate-openapi-from-qbitrr.py @@ -8,6 +8,7 @@ import urllib.request from copy import deepcopy from openapi_status import STATUS_SCHEMA +from openapi_statistics import EXTENSION_PATHS as STATISTICS_PATHS, apply as apply_statistics from openapi_maintenance import apply as apply_maintenance QBITRR_REF = os.environ.get("QBITRR_OPENAPI_REF", "v5.14.6-1") @@ -169,6 +170,7 @@ def main() -> int: operation["responses"]["200"]["content"]["application/json"]["schema"] = deepcopy(STATUS_SCHEMA) apply_maintenance(spec) + apply_statistics(spec) out = sys.argv[1] if len(sys.argv) > 1 else OUT_PATH with open(out, "w", encoding="utf-8") as f: diff --git a/scripts/openapi_statistics.py b/scripts/openapi_statistics.py new file mode 100644 index 00000000..1393df23 --- /dev/null +++ b/scripts/openapi_statistics.py @@ -0,0 +1,53 @@ +"""Torrentarr console statistics API extensions.""" +from copy import deepcopy + +EXTENSION_PATHS = {"/web/statistics/transfers", "/web/statistics/catalog", "/web/statistics/arr-activity"} + + +def apply(spec): + number = {"type": "number", "format": "double", "nullable": True} + bucket = {"type": "object", "properties": { + "timestamp": {"type": "string", "format": "date-time"}, + "downloadRate": number, "uploadRate": number, + "samples": {"type": "integer"}, "expectedSamples": {"type": "integer"}}} + series = {"type": "object", "properties": {"instance": {"type": "string"}, + "points": {"type": "array", "items": bucket}}} + history = {"type": "object", "properties": { + "range": {"type": "string", "enum": ["1h", "24h", "7d"]}, + "bucketMinutes": {"type": "integer"}, "generatedAt": {"type": "string", "format": "date-time"}, + "series": {"type": "array", "items": series}}} + catalog = {"type": "object", "properties": {"unit": {"type": "string"}, + **{name: {"type": "integer"} for name in ("total", "available", "monitored", "missing")}}} + for path, summary, parameters, schema in [ + ("/web/statistics/transfers", "Persistent whole-client transfer history", [ + {"name": "instance", "in": "query", "schema": {"type": "string"}, "description": "Enabled client name; omitted or all selects all enabled clients."}, + {"name": "range", "in": "query", "schema": {"type": "string", "enum": ["1h", "24h", "7d"], "default": "24h"}}], history), + ("/web/statistics/catalog", "Complete catalog availability rollup", [ + {"name": "kind", "in": "query", "required": True, "schema": {"type": "string", "enum": ["radarr", "sonarr", "lidarr", "readarr"]}}, + {"name": "instance", "in": "query", "schema": {"type": "string"}, "description": "Arr section name; omitted or aggregate selects all instances of this kind."}], catalog), + ]: + spec["paths"][path] = {"get": {"tags": ["Torrentarr Statistics"], "summary": summary, + "description": "Follows configured WebUI authentication. Transfer rates are bytes/second, collected once per minute and retained seven days. Completed buckets use 1, 5, or 30 minutes. Null rates indicate no observations; samples below expectedSamples indicate partial coverage. Catalog availability is monitored media with files; missing is monitored minus available. Catalog counts ignore page and text filters.", + "parameters": parameters, + "responses": {"200": {"description": "OK", "content": {"application/json": {"schema": deepcopy(schema)}}}, + "400": {"description": "Invalid range, kind, or instance"}, "401": {"description": "Authentication required"}}}} + point = {"type": "object", "properties": { + "timestamp": {"type": "string", "format": "date-time"}, + "queueDepth": {"type": "number", "nullable": True}, + **{name: {"type": "integer", "nullable": True} for name in ("searchesCompleted", "importsConfirmed")}, + **{name: {"type": "integer"} for name in ("queueSamples", "completionSamples", "expectedSamples")}}} + spec["paths"]["/web/statistics/arr-activity"] = {"get": { + "tags": ["Torrentarr Statistics"], "summary": "Persistent Arr queue and confirmed activity history", + "description": "Follows WebUI authentication. Worker-level series retain seven days; no backfill. Queue depth averages fresh heartbeats. Completions count only tracked Torrentarr submissions: searches require successful command completion; imports require positive correlated download-import history after submission. Counts are timestamped when confirmed, not when requested. Missing/partial coverage is explicit. Buckets are 1, 5, or 30 minutes. Disabled/deleted workers can remain in retained history.", + "parameters": [ + {"name": "range", "in": "query", "schema": {"type": "string", "enum": ["1h", "24h", "7d"], "default": "24h"}}, + {"name": "worker", "in": "query", "schema": {"type": "string"}, "description": "Optional configured Arr section name."}], + "responses": {"200": {"description": "OK", "content": {"application/json": {"schema": { + "type": "object", "properties": {"range": {"type": "string"}, "bucketMinutes": {"type": "integer"}, + "generatedAt": {"type": "string", "format": "date-time"}, "series": {"type": "array", "items": { + "type": "object", "properties": {"instance": {"type": "string"}, "type": {"type": "string"}, + "points": {"type": "array", "items": point}}}}}}}}}, + "400": {"description": "Invalid range or worker"}, "401": {"description": "Authentication required"}}}} + overview = spec.get("paths", {}).get("/web/qbit/overview", {}).get("get") + if overview: + overview["description"] = "Includes optional summaries per available client: instance, torrentCount, totalSize, downloading, seeding, stopped, other. These monitored-category totals are computed before the display cap and deduplicated by client and torrent hash. Clients without an inventory response are omitted." diff --git a/scripts/webui-parity-preview.py b/scripts/webui-parity-preview.py index 32286fbf..6195deb9 100644 --- a/scripts/webui-parity-preview.py +++ b/scripts/webui-parity-preview.py @@ -9,6 +9,8 @@ import argparse import json import threading +import math +from datetime import datetime, timezone parser = argparse.ArgumentParser(description=__doc__) parser.add_argument('--scenario', choices=['full', 'empty', 'no-readarr', 'offline', 'large', 'missing-images', 'login', 'setup', 'update'], default='full') scenario = parser.parse_args().scenario @@ -89,6 +91,38 @@ def do_GET(self): return self.reply(config) if p == '/web/meta': return self.reply(dict(current_version='6.15.2', ready=True, auth_required=scenario in ('login', 'setup'), local_auth_enabled=True, setup_required=scenario == 'setup', url_base='/prefix' if u.path.startswith('/prefix/') else '', repository_url='https://github.com/Feramance/Torrentarr', installation_type='docker', auto_update_supported=False, update_available=scenario == 'update', latest_version='6.15.3', changelog='Fixture release notes')) + if p == '/web/statistics/arr-activity': + selected_range = q.get('range', ['24h'])[0] + minutes, bucket = {'1h': (60, 1), '24h': (1440, 5), '7d': (10080, 30)}[selected_range] + end = int(datetime.now(timezone.utc).timestamp() / 60) // bucket * bucket + activity = [] + for worker_index, arr in enumerate(arrs): + points = [dict(timestamp=datetime.fromtimestamp((end-minutes+i*bucket)*60, timezone.utc).isoformat(), + queueDepth=max(0, round(12+worker_index*4+8*math.sin(i/17+worker_index))) if i%45 else None, + searchesCompleted=(i+worker_index)%4 if i%45 else None, + importsConfirmed=(i+worker_index)%3 if i%45 else None, + queueSamples=bucket if i%45 else 0, completionSamples=bucket if i%45 else 0, expectedSamples=bucket) + for i in range(minutes//bucket)] + activity.append(dict(instance=arr['name'], type=arr['type'], points=points)) + return self.reply(dict(range=selected_range,bucketMinutes=bucket,generatedAt=datetime.now(timezone.utc).isoformat(),series=activity)) + if p == '/web/statistics/transfers': + ranges = {'1h': (60, 1), '24h': (1440, 5), '7d': (10080, 30)} + selected_range = q.get('range', ['24h'])[0] + minutes, bucket = ranges[selected_range] + end = int(datetime.now(timezone.utc).timestamp() / 60) // bucket * bucket + points = [dict(timestamp=datetime.fromtimestamp((end - minutes + i * bucket) * 60, timezone.utc).isoformat(), + downloadRate=(8 + 6 * math.sin(i / 14)) * 1024 ** 2 if i % 50 else None, + uploadRate=(2 + math.cos(i / 19)) * 1024 ** 2 if i % 50 else None, + samples=bucket if i % 50 else 0, expectedSamples=bucket) for i in range(minutes // bucket)] + return self.reply(dict(range=selected_range, bucketMinutes=bucket, generatedAt=datetime.now(timezone.utc).isoformat(), + series=[] if scenario == 'empty' else [dict(instance='qBit-main', points=points)])) + if p == '/web/statistics/catalog': + unit = {'radarr': 'movies', 'sonarr': 'episodes', 'lidarr': 'albums', 'readarr': 'books'}[q.get('kind', ['radarr'])[0]] + return self.reply(dict(unit=unit, total=240, available=180, monitored=210, missing=30)) + if p == '/web/issues/summary': + return self.reply(dict(available=True, activeGroups=0)) + if p == '/web/issues': + return self.reply(dict(available=True, groups=[], total=0, page=0, pageSize=50)) if p == '/web/status': return self.reply(dict(ready=True, arrs=arrs, qbitInstances={'qBit-main': dict(alive=scenario != 'offline', host='qbittorrent', port=8080, version='5.1')} if scenario != 'empty' else {})) if p == '/web/arr': @@ -105,7 +139,8 @@ def do_GET(self): if p == '/web/qbit/categories': return self.reply(dict(ready=True, categories=[] if scenario == 'empty' else [category])) if p == '/web/qbit/overview': - return self.reply(dict(ready=True, instances=[] if scenario == 'empty' else ['qBit-main'], categories=[] if scenario == 'empty' else [category])) + return self.reply(dict(ready=True, instances=[] if scenario == 'empty' else ['qBit-main'], categories=[] if scenario == 'empty' else [category], + summaries=[] if scenario == 'empty' else [dict(instance='qBit-main', torrentCount=42, totalSize=204800, downloading=8, seeding=28, stopped=4, other=2)])) if p == '/web/logs': return self.reply(dict(files=['torrentarr.log'] if self.server.reference else [dict(name='torrentarr.log', size=2048, modified='2026-10-18T12:00:00Z')])) if p == '/web/logs/search': @@ -140,6 +175,8 @@ def do_GET(self): if '/author/' in p: return self.reply(dict(**extra, author=author, books=[book])) return self.reply(dict(**extra, book_total=2, authors=[dict(author=author)])) + if p == '/web/torrent-clients': + return self.reply([]) if p == '/api/torrent-clients': return self.reply([]) if 'maintenance' in p: diff --git a/src/Torrentarr.Core/Configuration/StorageConfigMigration.cs b/src/Torrentarr.Core/Configuration/StorageConfigMigration.cs index 34afb336..8abb6c8c 100644 --- a/src/Torrentarr.Core/Configuration/StorageConfigMigration.cs +++ b/src/Torrentarr.Core/Configuration/StorageConfigMigration.cs @@ -94,7 +94,7 @@ private static void ConvertClients(TomlTable root, TomlTable legacy) { var download = Map(completed, client.Maintenance.PathMappings, reverse: true); if (download != null) { table["DownloadPath"] = download; client.DownloadPath = download; } - else Warn(id, "could not translate the old completed folder to a client download path"); + else Warn(id, table, "could not translate the old completed folder to a client download path"); } if (table.ContainsKey("FreeSpace")) continue; var path = LocalDownloadPath(client); @@ -113,8 +113,8 @@ private static void ConvertClients(TomlTable root, TomlTable legacy) table["FreeSpace"] = validPath ? new TomlTableArray { new TomlTable { ["Path"] = path!, ["Enabled"] = enabled, ["Limit"] = limit } } : new TomlTableArray(); - if (ambiguous && validPath) Warn(id, "legacy monitor ownership is ambiguous; the inherited path is disabled for review"); - if (!validPath) Warn(id, "no unambiguous local monitor path; free-space protection remains disabled until configured"); + if (ambiguous && validPath) Warn(id, table, "legacy monitor ownership is ambiguous; the inherited path is disabled for review"); + if (!validPath) Warn(id, table, "no unambiguous local monitor path; free-space protection remains disabled until configured"); } } @@ -129,7 +129,7 @@ public static void ApplyEnvironment(TomlTable root, TomlTable legacy, IEnumerabl { var rootPath = Map(legacy["CompletedDownloadFolder"].ToString(), client.Maintenance.PathMappings, reverse: true); if (rootPath != null) { table["DownloadPath"] = rootPath; client.DownloadPath = rootPath; } - else Warn(id, "legacy environment download folder cannot be reverse-mapped"); + else Warn(id, table, "legacy environment download folder cannot be reverse-mapped"); } if (client.FreeSpace.Count == 0) table.Remove("FreeSpace"); else @@ -168,5 +168,13 @@ public static void ApplyEnvironment(TomlTable root, TomlTable legacy, IEnumerabl return null; } - private static void Warn(string id, string message) => Console.Error.WriteLine($"Storage migration [{id}]: {message}."); + private static void Warn(string id, TomlTable client, string message) + { + if (!client.TryGetValue("Migration", out var value)) client["Migration"] = value = new TomlTable(); + var migration = value as TomlTable ?? throw new InvalidDataException("Migration must be a table."); + if (!migration.TryGetValue("Notices", out var noticesValue)) migration["Notices"] = noticesValue = new TomlArray(); + var notices = noticesValue as TomlArray ?? throw new InvalidDataException("Migration.Notices must be a list."); + if (!notices.Contains(message)) notices.Add(message); + Console.Error.WriteLine($"Storage migration [{id}]: {message}."); + } } diff --git a/src/Torrentarr.Core/Models/ProtectionDecision.cs b/src/Torrentarr.Core/Models/ProtectionDecision.cs index 3977f1e0..a216cd62 100644 --- a/src/Torrentarr.Core/Models/ProtectionDecision.cs +++ b/src/Torrentarr.Core/Models/ProtectionDecision.cs @@ -20,6 +20,8 @@ public sealed class ProtectionDecision public string DeadlineRisk { get; set; } = "none"; public bool LocalEstimate { get; set; } = true; public bool NativeStopLimitRisk { get; set; } + + public string SeedAccountingDescription() => $"{(LocalEstimate ? "Locally observed" : "Client-reported")} seed={ObservedSeedSeconds:F0}s; remaining time requirement={RemainingSeedSeconds:F0}s (ratio/accounting requirements may still apply)"; } public sealed class ProtectionObservation diff --git a/src/Torrentarr.Core/Services/ISeedingService.cs b/src/Torrentarr.Core/Services/ISeedingService.cs index d434ba2f..b0977661 100644 --- a/src/Torrentarr.Core/Services/ISeedingService.cs +++ b/src/Torrentarr.Core/Services/ISeedingService.cs @@ -11,7 +11,8 @@ public enum TorrentRemovalIntent { SeedingLimit, StalledDownload, - RejectedByAllowlist + RejectedByAllowlist, + RejectedByCustomFormat } public interface ISeedingService @@ -63,6 +64,9 @@ public interface ISeedingService /// Task IsHnRSafeToRemoveAsync(TorrentInfo torrent, TrackerConfig config, CancellationToken cancellationToken = default); + /// Evaluate ordinary seeding limits without H&R deletion authorization. + Task SeedingLimitsMetAsync(TorrentInfo torrent, CancellationToken cancellationToken = default) => Task.FromResult(false); + /// /// Check if torrent meets removal conditions based on RemoveMode. /// Matches qBitrr's _should_remove_torrent() exactly. diff --git a/src/Torrentarr.Core/Services/ITorrentCacheService.cs b/src/Torrentarr.Core/Services/ITorrentCacheService.cs index fd9f67e3..6a9e0ea6 100644 --- a/src/Torrentarr.Core/Services/ITorrentCacheService.cs +++ b/src/Torrentarr.Core/Services/ITorrentCacheService.cs @@ -71,18 +71,8 @@ public interface ITorrentCacheService /// void MarkFileFiltered(string qBitInstanceName, string hash); - /// - /// Record an allowlist warning and return whether it should be logged after throttling. - /// - bool ShouldLogAllowlistWarning( - string qBitInstanceName, - string hash, - DateTimeOffset timestamp, - TimeSpan throttlePeriod); - - /// Throttle repeated identical removal blocks, reporting a changed reason immediately. - bool ShouldLogRemovalBlock(string instanceId, string hash, string reason, - DateTimeOffset timestamp, TimeSpan throttlePeriod) => true; + /// Report a changed diagnostic; null evidence clears a resolved stream. State is scoped to client and torrent. + bool DiagnosticChanged(string instanceId, string hash, string stream, string? evidence); } public class TorrentCacheStats diff --git a/src/Torrentarr.Core/Services/ITorrentProcessor.cs b/src/Torrentarr.Core/Services/ITorrentProcessor.cs index 0aebd013..c3c41651 100644 --- a/src/Torrentarr.Core/Services/ITorrentProcessor.cs +++ b/src/Torrentarr.Core/Services/ITorrentProcessor.cs @@ -74,3 +74,6 @@ public enum TorrentState CheckingResumeData, MetadataDownloading } + +/// A requested torrent inventory could not be inspected; this is not a successful empty scan. +public sealed class TorrentProcessingUnavailableException(string message) : IOException(message); diff --git a/src/Torrentarr.Core/Services/MaintenanceRequirements.cs b/src/Torrentarr.Core/Services/MaintenanceRequirements.cs index 4d3cea7f..f65b9205 100644 --- a/src/Torrentarr.Core/Services/MaintenanceRequirements.cs +++ b/src/Torrentarr.Core/Services/MaintenanceRequirements.cs @@ -24,7 +24,7 @@ public static class MaintenanceRequirements MaintenanceOperation.OrphanScan => InventoryRequirements.Metadata | InventoryRequirements.Files | InventoryRequirements.LocalFiles | InventoryRequirements.GlobalOwnership, MaintenanceOperation.HardlinkAudit => InventoryRequirements.Metadata | InventoryRequirements.Files | InventoryRequirements.LocalFiles, MaintenanceOperation.UnregisteredCleanup or MaintenanceOperation.ErrorRecovery => InventoryRequirements.Metadata | InventoryRequirements.Trackers | InventoryRequirements.Properties, - MaintenanceOperation.CategoryReconcile or MaintenanceOperation.AutomaticManagement or MaintenanceOperation.RepairPaused => InventoryRequirements.Metadata | InventoryRequirements.Trackers | InventoryRequirements.Properties, + MaintenanceOperation.CategoryReconcile or MaintenanceOperation.AutomaticManagement or MaintenanceOperation.RepairPaused => InventoryRequirements.Metadata, MaintenanceOperation.TrackerErrorTagging => InventoryRequirements.Metadata | InventoryRequirements.Trackers, MaintenanceOperation.PrivateTagging => InventoryRequirements.Metadata | InventoryRequirements.Properties, MaintenanceOperation.RecycleRetention => InventoryRequirements.None, diff --git a/src/Torrentarr.Core/Services/RemovalDecision.cs b/src/Torrentarr.Core/Services/RemovalDecision.cs index ef4d225b..12742022 100644 --- a/src/Torrentarr.Core/Services/RemovalDecision.cs +++ b/src/Torrentarr.Core/Services/RemovalDecision.cs @@ -4,6 +4,7 @@ public sealed record RemovalBlocker(string Code, string Reason, string Remediati public sealed record RemovalDecision(IReadOnlyList Blockers) { + public bool IsExpectedProtectionWait() => Blockers.Count > 0 && Blockers.All(b => b.Code == "hnr.protected"); public bool Allowed => Blockers.Count == 0; public string Reason => string.Join("; ", Blockers.Select(b => $"{b.Code}: {b.Reason} Next: {b.Remediation}")); } diff --git a/src/Torrentarr.Host/Program.cs b/src/Torrentarr.Host/Program.cs index 05fadb50..f3fb4423 100644 --- a/src/Torrentarr.Host/Program.cs +++ b/src/Torrentarr.Host/Program.cs @@ -187,6 +187,8 @@ builder.Services.AddSingleton(); builder.Services.AddHostedService(sp => sp.GetRequiredService()); builder.Services.AddSingleton(sp => sp.GetRequiredService()); + builder.Services.AddHostedService(); + builder.Services.AddHostedService(); builder.Services.AddSingleton(); builder.Services.AddSingleton(); builder.Services.AddSingleton(); @@ -869,6 +871,9 @@ static bool MetaForceAllowed(HttpContext ctx, TorrentarrConfig cfg) app.MapGet("/web/config/schema", () => Results.Ok(ConfigSchemaBuilder.Build())); + app.MapTransferHistory(); + app.MapArrActivity(); + app.MapGet("/web/qbit/overview", async (HttpRequest request, TorrentarrConfig cfg, ITorrentClientRegistry qbitManager) => { var instance = request.Query["instance"].FirstOrDefault(); @@ -2771,7 +2776,7 @@ private async Task ProcessSpecialCategoriesAsync(CancellationToken cancellationT _logger.LogInformation("[{Instance}] Requested recheck for torrent: {Name}", instanceName, torrent.Name); } } - catch (Exception ex) + catch (Exception ex) when (ex is not OperationCanceledException) { _logger.LogError(ex, "[{Instance}] Error processing special categories", instanceName); } @@ -2854,7 +2859,7 @@ private async Task SortManagedTorrentsByTrackerPriorityAsync( { torrentList = await client.GetTorrentsAsync(category: null, "priority", cancellationToken); } - catch (Exception ex) + catch (Exception ex) when (ex is not OperationCanceledException) { _logger.LogDebug(ex, "[{Instance}] SortTorrents: falling back to added_on sort", instanceName); torrentList = await client.GetTorrentsAsync(category: null, "added_on", cancellationToken); @@ -2923,6 +2928,7 @@ private async Task SortManagedTorrentsByTrackerPriorityAsync( await client.SetTopPriorityAsync(torrent.Hash, cancellationToken); } } + catch (OperationCanceledException) when (cancellationToken.IsCancellationRequested) { throw; } catch (Exception ex) { _logger.LogError(ex, "[{Instance}] SortTorrents policy step failed", instanceName); diff --git a/src/Torrentarr.Infrastructure/ApiClients/Arr/ArrActivityClient.cs b/src/Torrentarr.Infrastructure/ApiClients/Arr/ArrActivityClient.cs new file mode 100644 index 00000000..13588e74 --- /dev/null +++ b/src/Torrentarr.Infrastructure/ApiClients/Arr/ArrActivityClient.cs @@ -0,0 +1,62 @@ +using Newtonsoft.Json.Linq; +using RestSharp; +namespace Torrentarr.Infrastructure.ApiClients.Arr; + +internal interface IArrActivityClient : IDisposable +{ + Task ReadActivityAsync(string resource, CancellationToken ct); +} + +internal static class ArrActivityResponse +{ + internal static async Task ReadAsync(RestClient client, RestRequest request, CancellationToken ct) + { + var response = await ArrClientResponse.ExecuteAsync(client, request, ct); + ArrClientResponse.EnsureSuccess(response, "GET activity evidence"); + return JArray.Parse(response.Content ?? throw new IOException("Missing activity evidence")); + } +} + +public partial class RadarrClient +{ + void IDisposable.Dispose() => _client.Dispose(); + async Task IArrActivityClient.ReadActivityAsync(string resource, CancellationToken ct) + { + var request = new RestRequest("/api/v3/" + resource, Method.Get); + AddApiKeyHeader(request); + return await ArrActivityResponse.ReadAsync(_client, request, ct); + } +} + +public partial class SonarrClient +{ + void IDisposable.Dispose() => _client.Dispose(); + async Task IArrActivityClient.ReadActivityAsync(string resource, CancellationToken ct) + { + var request = await CreateRequestAsync(resource, Method.Get, ct); + AddApiKeyHeader(request); + return await ArrActivityResponse.ReadAsync(_client, request, ct); + } +} + +public partial class LidarrClient +{ + void IDisposable.Dispose() => _client.Dispose(); + async Task IArrActivityClient.ReadActivityAsync(string resource, CancellationToken ct) + { + var request = new RestRequest("/api/v1/" + resource, Method.Get); + AddApiKeyHeader(request); + return await ArrActivityResponse.ReadAsync(_client, request, ct); + } +} + +public partial class ReadarrClient +{ + void IDisposable.Dispose() => _client.Dispose(); + async Task IArrActivityClient.ReadActivityAsync(string resource, CancellationToken ct) + { + var request = new RestRequest("/api/v1/" + resource, Method.Get); + AddApiKeyHeader(request); + return await ArrActivityResponse.ReadAsync(_client, request, ct); + } +} diff --git a/src/Torrentarr.Infrastructure/ApiClients/Arr/ArrClientResponse.cs b/src/Torrentarr.Infrastructure/ApiClients/Arr/ArrClientResponse.cs index 668399db..a4f10515 100644 --- a/src/Torrentarr.Infrastructure/ApiClients/Arr/ArrClientResponse.cs +++ b/src/Torrentarr.Infrastructure/ApiClients/Arr/ArrClientResponse.cs @@ -7,8 +7,12 @@ namespace Torrentarr.Infrastructure.ApiClients.Arr; internal static class ArrClientResponse { - internal static Task ExecuteAsync(RestClient client, RestRequest request, CancellationToken ct) => - HttpRetryHelper.ExecuteArrAsync(client, request, ct); + internal static async Task ExecuteAsync(RestClient client, RestRequest request, CancellationToken ct) + { + var response = await HttpRetryHelper.ExecuteArrAsync(client, request, ct); + await Torrentarr.Infrastructure.Services.ArrActivityTracking.RecordAcceptedAsync(request, response, ct); + return response; + } internal static void EnsureSuccess(RestResponse response, string operation) { diff --git a/src/Torrentarr.Infrastructure/ApiClients/Arr/LidarrClient.cs b/src/Torrentarr.Infrastructure/ApiClients/Arr/LidarrClient.cs index f9c440be..f1db8ef4 100644 --- a/src/Torrentarr.Infrastructure/ApiClients/Arr/LidarrClient.cs +++ b/src/Torrentarr.Infrastructure/ApiClients/Arr/LidarrClient.cs @@ -6,7 +6,7 @@ namespace Torrentarr.Infrastructure.ApiClients.Arr; /// /// Lidarr API client using RestSharp /// -public class LidarrClient +public partial class LidarrClient : IArrActivityClient { private readonly RestClient _client; private readonly string _apiKey; diff --git a/src/Torrentarr.Infrastructure/ApiClients/Arr/RadarrClient.cs b/src/Torrentarr.Infrastructure/ApiClients/Arr/RadarrClient.cs index 002a77fd..3adedc04 100644 --- a/src/Torrentarr.Infrastructure/ApiClients/Arr/RadarrClient.cs +++ b/src/Torrentarr.Infrastructure/ApiClients/Arr/RadarrClient.cs @@ -6,7 +6,7 @@ namespace Torrentarr.Infrastructure.ApiClients.Arr; /// /// Radarr API client using RestSharp /// -public class RadarrClient +public partial class RadarrClient : IArrActivityClient { private readonly RestClient _client; private readonly string _apiKey; diff --git a/src/Torrentarr.Infrastructure/ApiClients/Arr/ReadarrClient.cs b/src/Torrentarr.Infrastructure/ApiClients/Arr/ReadarrClient.cs index 25a4aaf8..df932094 100644 --- a/src/Torrentarr.Infrastructure/ApiClients/Arr/ReadarrClient.cs +++ b/src/Torrentarr.Infrastructure/ApiClients/Arr/ReadarrClient.cs @@ -7,7 +7,7 @@ namespace Torrentarr.Infrastructure.ApiClients.Arr; /// /// Readarr API client using RestSharp. Author → book (no track layer). /// -public class ReadarrClient +public partial class ReadarrClient : IArrActivityClient { private readonly RestClient _client; private readonly string _apiKey; diff --git a/src/Torrentarr.Infrastructure/ApiClients/Arr/SonarrClient.cs b/src/Torrentarr.Infrastructure/ApiClients/Arr/SonarrClient.cs index 58d8c187..6341f5be 100644 --- a/src/Torrentarr.Infrastructure/ApiClients/Arr/SonarrClient.cs +++ b/src/Torrentarr.Infrastructure/ApiClients/Arr/SonarrClient.cs @@ -6,7 +6,7 @@ namespace Torrentarr.Infrastructure.ApiClients.Arr; /// /// Sonarr API client using RestSharp /// -public class SonarrClient +public partial class SonarrClient : IArrActivityClient { private readonly RestClient _client; private readonly string _apiKey; diff --git a/src/Torrentarr.Infrastructure/Database/DatabaseRetryExtensions.cs b/src/Torrentarr.Infrastructure/Database/DatabaseRetryExtensions.cs index 663274f1..be72385f 100644 --- a/src/Torrentarr.Infrastructure/Database/DatabaseRetryExtensions.cs +++ b/src/Torrentarr.Infrastructure/Database/DatabaseRetryExtensions.cs @@ -18,7 +18,7 @@ public static async Task SaveChangesWithRetryAsync( CancellationToken cancellationToken = default) { // Capture only operational identifiers and state, never arbitrary entity values or payloads. - var changes = logger?.IsEnabled(LogLevel.Information) == true + var changes = logger?.IsEnabled(LogLevel.Debug) == true ? context.ChangeTracker.Entries() .Where(e => e.State is EntityState.Added or EntityState.Modified or EntityState.Deleted) .Select(e => (Entry: e, State: e.State)).ToArray() @@ -35,9 +35,9 @@ async Task SaveAsync() "Searched" or "Upgrade" or "IsRequest" or "Imported" or "QualityMet" or "CustomFormatMet" or "SeasonNumber" or "EpisodeNumber" or "ArrSeriesId" or "AirDateUtc" or "ReleaseDate") .Select(p => $"{p.Metadata.Name}={p.CurrentValue}")); - logger?.LogInformation("Database saved {Operation}: {Entity} | {Identity}", state, entry.Metadata.ClrType.Name, identity); + logger?.LogDebug("Database saved {Operation}: {Entity} | {Identity}", state, entry.Metadata.ClrType.Name, identity); } - logger?.LogInformation("Database save completed: {Count} entries saved (added={Added}, updated={Updated}, deleted={Deleted})", + logger?.LogDebug("Database save completed: {Count} entries saved (added={Added}, updated={Updated}, deleted={Deleted})", count, changes.Count(c => c.State == EntityState.Added), changes.Count(c => c.State == EntityState.Modified), changes.Count(c => c.State == EntityState.Deleted)); return count; @@ -103,11 +103,16 @@ public static bool IsSqliteCorruption(Exception ex) return false; } - private static bool IsRetriable(Exception ex) => - ex is DbUpdateException or SqliteException - && (ex.Message.Contains("database is locked", StringComparison.OrdinalIgnoreCase) - || ex.Message.Contains("disk I/O", StringComparison.OrdinalIgnoreCase) - || ex.Message.Contains("malformed", StringComparison.OrdinalIgnoreCase)); + private static bool IsRetriable(Exception error) + { + for (Exception? current = error; current != null; current = current.InnerException) + if (current is SqliteException sqlite && sqlite.SqliteErrorCode is 5 or 6 + || current is DbUpdateException or SqliteException && + (current.Message.Contains("database is locked", StringComparison.OrdinalIgnoreCase) + || current.Message.Contains("disk I/O", StringComparison.OrdinalIgnoreCase) + || current.Message.Contains("malformed", StringComparison.OrdinalIgnoreCase))) return true; + return false; + } private static bool IsCorruption(Exception ex) => IsSqliteCorruption(ex); } diff --git a/src/Torrentarr.Infrastructure/Database/ManualSqliteMigrations.cs b/src/Torrentarr.Infrastructure/Database/ManualSqliteMigrations.cs index 66cbf626..a1dfceab 100644 --- a/src/Torrentarr.Infrastructure/Database/ManualSqliteMigrations.cs +++ b/src/Torrentarr.Infrastructure/Database/ManualSqliteMigrations.cs @@ -40,6 +40,16 @@ CREATE TABLE IF NOT EXISTS torrentarr_operational_issues ( AddColumnIfMissing(db, "albumfilesmodel", "PhysicalRelease", "TEXT"); AddColumnIfMissing(db, "albumfilesmodel", "MinimumAvailability", "TEXT"); + CreateTableIfMissing(db, "torrentarr_arr_samples", "CREATE TABLE IF NOT EXISTS torrentarr_arr_samples (Instance TEXT NOT NULL, Minute INTEGER NOT NULL, Type TEXT NOT NULL, QueueDepth INTEGER, CompletionObserved INTEGER NOT NULL, PRIMARY KEY (Instance, Minute));"); + db.Database.ExecuteSqlRaw("CREATE INDEX IF NOT EXISTS IX_torrentarr_arr_samples_Minute ON torrentarr_arr_samples (Minute)"); + CreateTableIfMissing(db, "torrentarr_arr_operations", "CREATE TABLE IF NOT EXISTS torrentarr_arr_operations (Instance TEXT NOT NULL, CommandId INTEGER NOT NULL, Type TEXT NOT NULL, Kind TEXT NOT NULL, Server TEXT NOT NULL, ClientInstance TEXT NOT NULL, DownloadId TEXT NOT NULL, SubmittedAt INTEGER NOT NULL, ConfirmedAt INTEGER, Outcome TEXT NOT NULL DEFAULT 'pending', EvidenceId TEXT, PRIMARY KEY (Instance, CommandId));"); + db.Database.ExecuteSqlRaw("CREATE UNIQUE INDEX IF NOT EXISTS IX_torrentarr_arr_operations_Instance_EvidenceId ON torrentarr_arr_operations (Instance, EvidenceId)"); + db.Database.ExecuteSqlRaw("CREATE INDEX IF NOT EXISTS IX_torrentarr_arr_operations_SubmittedAt ON torrentarr_arr_operations (SubmittedAt)"); + db.Database.ExecuteSqlRaw("CREATE INDEX IF NOT EXISTS IX_torrentarr_arr_operations_ConfirmedAt ON torrentarr_arr_operations (ConfirmedAt)"); + db.Database.ExecuteSqlRaw("CREATE INDEX IF NOT EXISTS IX_torrentarr_arr_operations_Instance_DownloadId ON torrentarr_arr_operations (Instance, DownloadId)"); + db.Database.ExecuteSqlRaw("CREATE INDEX IF NOT EXISTS IX_torrentarr_arr_operations_Instance_Outcome ON torrentarr_arr_operations (Instance, Outcome)"); + CreateTableIfMissing(db, "torrentarr_transfer_samples", "CREATE TABLE IF NOT EXISTS torrentarr_transfer_samples (ClientInstance TEXT NOT NULL, Minute INTEGER NOT NULL, DownloadRate INTEGER NOT NULL, UploadRate INTEGER NOT NULL, PRIMARY KEY (ClientInstance, Minute));"); + db.Database.ExecuteSqlRaw("CREATE INDEX IF NOT EXISTS IX_torrentarr_transfer_samples_Minute ON torrentarr_transfer_samples (Minute)"); CreateTableIfMissing(db, "torrentarr_maintenance_state", "CREATE TABLE IF NOT EXISTS torrentarr_maintenance_state (Key TEXT NOT NULL PRIMARY KEY, Data TEXT NOT NULL DEFAULT '{}');"); CreateTableIfMissing(db, "searchactivity", "CREATE TABLE IF NOT EXISTS searchactivity ( category TEXT NOT NULL PRIMARY KEY, summary TEXT, timestamp TEXT );"); diff --git a/src/Torrentarr.Infrastructure/Database/Models/ArrActivity.cs b/src/Torrentarr.Infrastructure/Database/Models/ArrActivity.cs new file mode 100644 index 00000000..19106b01 --- /dev/null +++ b/src/Torrentarr.Infrastructure/Database/Models/ArrActivity.cs @@ -0,0 +1,28 @@ +using System.ComponentModel.DataAnnotations.Schema; +namespace Torrentarr.Infrastructure.Database.Models; + +[Table("torrentarr_arr_samples")] +public sealed class ArrActivitySample +{ + public string Instance { get; set; } = ""; + public long Minute { get; set; } + public string Type { get; set; } = ""; + public int? QueueDepth { get; set; } + public bool CompletionObserved { get; set; } +} + +[Table("torrentarr_arr_operations")] +public sealed class ArrActivityOperation +{ + public string Instance { get; set; } = ""; + public int CommandId { get; set; } + public string Type { get; set; } = ""; + public string Kind { get; set; } = ""; + public string Server { get; set; } = ""; + public string ClientInstance { get; set; } = ""; + public string DownloadId { get; set; } = ""; + public long SubmittedAt { get; set; } + public long? ConfirmedAt { get; set; } + public string Outcome { get; set; } = "pending"; + public string? EvidenceId { get; set; } +} diff --git a/src/Torrentarr.Infrastructure/Database/Models/TransferSample.cs b/src/Torrentarr.Infrastructure/Database/Models/TransferSample.cs new file mode 100644 index 00000000..448fd32b --- /dev/null +++ b/src/Torrentarr.Infrastructure/Database/Models/TransferSample.cs @@ -0,0 +1,12 @@ +using System.ComponentModel.DataAnnotations.Schema; + +namespace Torrentarr.Infrastructure.Database.Models; + +[Table("torrentarr_transfer_samples")] +public sealed class TransferSample +{ + public string ClientInstance { get; set; } = ""; + public long Minute { get; set; } + public long DownloadRate { get; set; } + public long UploadRate { get; set; } +} diff --git a/src/Torrentarr.Infrastructure/Database/TorrentarrDbContext.cs b/src/Torrentarr.Infrastructure/Database/TorrentarrDbContext.cs index 345a35ec..61c47fc0 100644 --- a/src/Torrentarr.Infrastructure/Database/TorrentarrDbContext.cs +++ b/src/Torrentarr.Infrastructure/Database/TorrentarrDbContext.cs @@ -32,11 +32,26 @@ public TorrentarrDbContext(DbContextOptions options) public DbSet SonarrSearchRetries { get; set; } public DbSet MaintenanceState { get; set; } + public DbSet ArrActivitySamples { get; set; } + public DbSet ArrActivityOperations { get; set; } + + public DbSet TransferSamples { get; set; } + public DbSet OperationalIssues { get; set; } protected override void OnModelCreating(ModelBuilder modelBuilder) { base.OnModelCreating(modelBuilder); + modelBuilder.Entity().HasKey(s => new { s.Instance, s.Minute }); + modelBuilder.Entity().HasIndex(s => s.Minute); + modelBuilder.Entity().HasKey(s => new { s.Instance, s.CommandId }); + modelBuilder.Entity().HasIndex(s => new { s.Instance, s.EvidenceId }).IsUnique(); + modelBuilder.Entity().HasIndex(s => s.SubmittedAt); + modelBuilder.Entity().HasIndex(s => s.ConfirmedAt); + modelBuilder.Entity().HasIndex(s => new { s.Instance, s.DownloadId }); + modelBuilder.Entity().HasIndex(s => new { s.Instance, s.Outcome }); + modelBuilder.Entity().HasKey(s => new { s.ClientInstance, s.Minute }); + modelBuilder.Entity().HasIndex(s => s.Minute); modelBuilder.Entity().Property(i => i.ArrInstance).UseCollation("NOCASE"); modelBuilder.Entity().Property(i => i.ClientInstance).UseCollation("NOCASE"); modelBuilder.Entity().HasIndex(i => new { i.Code, i.ArrInstance, i.ClientInstance, i.SubjectType, i.SubjectId }).IsUnique(); diff --git a/src/Torrentarr.Infrastructure/Endpoints/LogFileApi.cs b/src/Torrentarr.Infrastructure/Endpoints/LogFileApi.cs index 4d43cacf..cf8a4d23 100644 --- a/src/Torrentarr.Infrastructure/Endpoints/LogFileApi.cs +++ b/src/Torrentarr.Infrastructure/Endpoints/LogFileApi.cs @@ -387,10 +387,11 @@ private static List SiblingLogFiles(string logsRoot, string name, bool i if (!includeRotated || !Directory.Exists(logsRoot)) return files; - var prefix = Path.GetFileName(name); + var prefix = Regex.Replace(Path.GetFileName(name), @"(?:_\d{3,})?\.log$", ""); + var sibling = new Regex("^" + Regex.Escape(prefix) + @"(?:_\d{3,})?\.log(?:\..*)?$", RegexOptions.CultureInvariant | RegexOptions.NonBacktracking); foreach (var candidate in Directory.GetFiles(logsRoot, prefix + "*")) { - if (files.Contains(candidate, StringComparer.OrdinalIgnoreCase)) + if (!sibling.IsMatch(Path.GetFileName(candidate)) || files.Contains(candidate, StringComparer.OrdinalIgnoreCase)) continue; files.Add(candidate); } diff --git a/src/Torrentarr.Infrastructure/Logging/ServiceLogSink.cs b/src/Torrentarr.Infrastructure/Logging/ServiceLogSink.cs index 8f530641..21ca5ed4 100644 --- a/src/Torrentarr.Infrastructure/Logging/ServiceLogSink.cs +++ b/src/Torrentarr.Infrastructure/Logging/ServiceLogSink.cs @@ -13,6 +13,15 @@ namespace Torrentarr.Infrastructure.Logging; /// Shared daily files for every process, with an aggregate and one service destination. public class ServiceLogSink(string logsPath, string processType) : ILogEventSink, IDisposable { + internal const long FileSizeLimit = 128L * 1024 * 1024; + internal const int RetainedFileCount = 8; + + static ServiceLogSink() => Serilog.Debugging.SelfLog.Enable(message => Console.Error.WriteLine(DiagnosticText.Safe(message))); + + private readonly long _fileSizeLimit = FileSizeLimit; + internal ServiceLogSink(string logsPath, string processType, long fileSizeLimit) : this(logsPath, processType) + => _fileSizeLimit = fileSizeLimit; + private readonly Dictionary _writers = new(StringComparer.Ordinal); private readonly object _gate = new(); @@ -52,7 +61,8 @@ private Logger Writer(string service) Directory.CreateDirectory(logsPath); writer = new LoggerConfiguration().MinimumLevel.Verbose() .WriteTo.File(new ServiceLogFormatter(), Path.Combine(logsPath, service + "-.log"), - rollingInterval: RollingInterval.Day, shared: true, retainedFileCountLimit: 7, fileSizeLimitBytes: null) + rollingInterval: RollingInterval.Day, shared: true, retainedFileCountLimit: RetainedFileCount, + fileSizeLimitBytes: _fileSizeLimit, rollOnFileSizeLimit: true, retainedFileTimeLimit: TimeSpan.FromDays(7)) .CreateLogger(); _writers.Add(service, writer); return writer; diff --git a/src/Torrentarr.Infrastructure/Services/ArrActivityService.cs b/src/Torrentarr.Infrastructure/Services/ArrActivityService.cs new file mode 100644 index 00000000..c75b43ea --- /dev/null +++ b/src/Torrentarr.Infrastructure/Services/ArrActivityService.cs @@ -0,0 +1,234 @@ +using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Routing; +using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Hosting; +using Microsoft.Extensions.Logging; +using Newtonsoft.Json.Linq; +using Torrentarr.Core.Configuration; +using Torrentarr.Core.Interfaces; +using Torrentarr.Infrastructure.ApiClients.Arr; +using Torrentarr.Infrastructure.Database; +using Torrentarr.Infrastructure.Database.Models; + +namespace Torrentarr.Infrastructure.Services; + +public sealed class ArrActivityCollector(IServiceScopeFactory scopes, IServiceProvider services, + TorrentarrConfig config, ILogger logger) : BackgroundService +{ + private long _lastPrune; + protected override async Task ExecuteAsync(CancellationToken ct) + { + using var timer = new PeriodicTimer(TimeSpan.FromMinutes(1)); + do + { + try + { + var supervisor = services.GetService(); + var statuses = supervisor == null ? new Dictionary() : await supervisor.GetProcessStatusAsync(); + await CollectAsync(DateTimeOffset.UtcNow, statuses, ReadAsync, ct); + } + catch (OperationCanceledException) when (ct.IsCancellationRequested) { break; } + catch (Exception ex) { logger.LogWarning(ex, "Arr activity collection unavailable"); } + } while (await timer.WaitForNextTickAsync(ct)); + } + + private static async Task ReadAsync(ArrInstanceConfig settings, string resource, CancellationToken ct) + { + using IArrActivityClient client = settings.Type.ToLowerInvariant() switch + { + "radarr" => new RadarrClient(settings.URI, settings.APIKey, settings.SkipTLSVerify), + "sonarr" => new SonarrClient(settings.URI, settings.APIKey, settings.SkipTLSVerify), + "lidarr" => new LidarrClient(settings.URI, settings.APIKey, settings.SkipTLSVerify), + "readarr" => new ReadarrClient(settings.URI, settings.APIKey, settings.SkipTLSVerify), + _ => throw new InvalidOperationException("Unsupported Arr activity type") + }; + return await client.ReadActivityAsync(resource, ct); + } + + internal async Task CollectAsync(DateTimeOffset now, IReadOnlyDictionary statuses, + Func> read, CancellationToken ct = default) + { + using var scope = scopes.CreateScope(); + var db = scope.ServiceProvider.GetRequiredService(); + var minute = now.ToUnixTimeSeconds() / 60; + foreach (var (name, settings) in config.ArrInstances.ToArray()) + { + if (!settings.Managed || settings.Type is not ("radarr" or "sonarr" or "lidarr" or "readarr")) continue; + var status = statuses.GetValueOrDefault(name); + var fresh = status?.IsAlive == true && status.ConfigRevision >= config.Revision && status.LastHeartbeat is { } heartbeat + && now.UtcDateTime - heartbeat <= TimeSpan.FromSeconds(15) && heartbeat <= now.UtcDateTime; + var observed = fresh; + try + { + // Repeated callers in one minute must not poll Arr again or rewrite coverage. + if (await db.ArrActivitySamples.AnyAsync(s => s.Instance == name && s.Minute == minute, ct)) continue; + var pending = await db.ArrActivityOperations.Where(o => o.Instance == name && o.Outcome == "pending").ToListAsync(ct); + var eligible = new List(); + foreach (var operation in pending) + { + if (operation.Server != ArrActivityTracking.ServerKey(settings) || operation.SubmittedAt < now.ToUnixTimeSeconds() - 604800) + operation.Outcome = "expired"; + else eligible.Add(operation); + } + if (eligible.Count > 0) + { + using var timeout = CancellationTokenSource.CreateLinkedTokenSource(ct); + timeout.CancelAfter(TimeSpan.FromSeconds(20)); + var commands = await read(settings, "command", timeout.Token); + JArray history = []; + if (eligible.Any(o => o.Kind == "import")) + { + var since = DateTimeOffset.FromUnixTimeSeconds(eligible.Where(o => o.Kind == "import").Min(o => o.SubmittedAt)); + var eventType = settings.Type is "lidarr" or "readarr" ? 8 : 3; + history = await read(settings, $"history/since?date={Uri.EscapeDataString(since.ToString("O"))}&eventType={eventType}", timeout.Token); + } + await ReconcileAsync(db, eligible, commands, history, now, ct); + } + } + catch (OperationCanceledException) when (ct.IsCancellationRequested) { throw; } + catch (Exception ex) { observed = false; logger.LogDebug(ex, "Arr completion evidence unavailable for {Instance}", name); } + db.ArrActivitySamples.Add(new ArrActivitySample + { + Instance = name, + Type = settings.Type, + Minute = minute, + QueueDepth = fresh && status!.QueueCount is >= 0 ? status.QueueCount : null, + CompletionObserved = observed + }); + await db.SaveChangesAsync(ct); + } + if (minute - _lastPrune >= 60) + { + await db.ArrActivitySamples.Where(s => s.Minute < minute - 10080).ExecuteDeleteAsync(ct); + await db.ArrActivityOperations.Where(o => (o.ConfirmedAt ?? o.SubmittedAt) < now.ToUnixTimeSeconds() - 604800).ExecuteDeleteAsync(ct); + _lastPrune = minute; + } + } + + internal static async Task ReconcileAsync(TorrentarrDbContext db, List pending, + JArray commands, JArray history, DateTimeOffset now, CancellationToken ct = default) + { + var importInstances = pending.Where(o => o.Kind == "import").Select(o => o.Instance).Distinct().ToList(); + var importHashes = pending.Where(o => o.Kind == "import").Select(o => o.DownloadId).Distinct().ToList(); + var previousImports = importInstances.Count == 0 ? new List() : await db.ArrActivityOperations + .Where(o => o.Kind == "import" && importInstances.Contains(o.Instance) && importHashes.Contains(o.DownloadId)).ToListAsync(ct); + var claimed = previousImports.Where(o => o.EvidenceId != null).Select(o => (o.Instance, o.EvidenceId)).ToHashSet(); + var confirmedImports = previousImports.Where(o => o.Outcome == "completed").ToList(); + foreach (var operation in pending.OrderBy(o => o.SubmittedAt).ThenBy(o => o.CommandId)) + { + if (operation.Outcome != "pending") continue; + var command = commands.FirstOrDefault(c => c["id"]?.Value() == operation.CommandId); + var state = command?["status"]?.Value()?.ToLowerInvariant(); + if (state is "failed" or "aborted" or "cancelled" or "canceled") { operation.Outcome = "failed"; continue; } + var result = command?["result"]?.Value()?.ToLowerInvariant(); + if (result is "failed" or "unsuccessful" or "2") { operation.Outcome = "failed"; continue; } + if (result is "unknown" or "0") continue; + if (state != "completed" && !(operation.Kind == "import" && command == null)) continue; + if (operation.Kind == "search" && result is not ("successful" or "1")) continue; + if (operation.Kind == "import") + { + if (confirmedImports.Any(o => o.Instance == operation.Instance && o.DownloadId == operation.DownloadId + && o.ConfirmedAt >= operation.SubmittedAt)) { operation.Outcome = "duplicate"; continue; } + var evidence = history.FirstOrDefault(h => PositiveImport(h, operation) + && !claimed.Contains((operation.Instance, "history:" + h["id"]!.Value()))); + if (evidence == null) continue; + operation.EvidenceId = "history:" + evidence["id"]!.Value(); + claimed.Add((operation.Instance, operation.EvidenceId)); + } + operation.Outcome = "completed"; + operation.ConfirmedAt = now.ToUnixTimeSeconds(); + if (operation.Kind == "import") confirmedImports.Add(operation); + } + } + + internal static bool PositiveImport(JToken history, ArrActivityOperation operation) + { + var type = history["eventType"]?.Value(); + var expected = operation.Type is "lidarr" or "readarr" ? "downloadImported" : "downloadFolderImported"; + var numeric = operation.Type is "lidarr" or "readarr" ? "8" : "3"; + return history["id"] != null && !string.IsNullOrEmpty(operation.DownloadId) + && string.Equals(history["downloadId"]?.Value(), operation.DownloadId, StringComparison.OrdinalIgnoreCase) + && (string.Equals(type, expected, StringComparison.OrdinalIgnoreCase) || type == numeric) + && DateTimeOffset.TryParse(history["date"]?.Value(), out var date) + && date.ToUnixTimeSeconds() >= operation.SubmittedAt; + } +} + +public sealed record ArrActivityPoint(DateTimeOffset Timestamp, double? QueueDepth, int? SearchesCompleted, + int? ImportsConfirmed, int QueueSamples, int CompletionSamples, int ExpectedSamples); +public sealed record ArrActivitySeries(string Instance, string Type, List Points); +public sealed record ArrActivityHistory(string Range, int BucketMinutes, DateTimeOffset GeneratedAt, List Series); + +public static class ArrActivityApi +{ + public static void MapArrActivity(this IEndpointRouteBuilder app) => app.MapGet("/web/statistics/arr-activity", + async (string? range, string? worker, TorrentarrConfig config, TorrentarrDbContext db, CancellationToken ct) => + { + range ??= "24h"; + if (range is not ("1h" or "24h" or "7d")) return Results.BadRequest(new { error = "range must be 1h, 24h, or 7d" }); + if (!string.IsNullOrEmpty(worker) && !config.ArrInstances.ContainsKey(worker)) + return Results.BadRequest(new { error = "Unknown Arr worker" }); + var canonicalWorker = string.IsNullOrEmpty(worker) ? null : config.ArrInstances.Keys.First(n => n.Equals(worker, StringComparison.OrdinalIgnoreCase)); + return Results.Ok(await QueryAsync(db, config, range, canonicalWorker, DateTimeOffset.UtcNow, ct)); + }); + + public static async Task QueryAsync(TorrentarrDbContext db, TorrentarrConfig config, + string range, string? worker, DateTimeOffset now, CancellationToken ct = default) + { + var (duration, bucket) = range switch + { + "1h" => (60, 1), + "24h" => (1440, 5), + "7d" => (10080, 30), + _ => throw new ArgumentOutOfRangeException(nameof(range)) + }; + var end = now.ToUnixTimeSeconds() / 60 / bucket * bucket; + var start = end - duration; + var samples = await db.ArrActivitySamples.AsNoTracking().Where(s => s.Minute >= start && s.Minute < end + && (worker == null || worker == "" || s.Instance == worker)) + .GroupBy(s => new { s.Instance, s.Type, Bucket = s.Minute / bucket * bucket }) + .Select(g => new + { + g.Key.Instance, + g.Key.Type, + g.Key.Bucket, + QueueDepth = g.Average(s => (double?)s.QueueDepth), + QueueSamples = g.Count(s => s.QueueDepth != null), + CompletionSamples = g.Count(s => s.CompletionObserved) + }).ToListAsync(ct); + var events = await db.ArrActivityOperations.AsNoTracking().Where(o => o.Outcome == "completed" && o.ConfirmedAt >= start * 60 && o.ConfirmedAt < end * 60 + && (worker == null || worker == "" || o.Instance == worker)) + .GroupBy(o => new { o.Instance, o.Type, Bucket = o.ConfirmedAt!.Value / 60 / bucket * bucket }) + .Select(g => new + { + g.Key.Instance, + g.Key.Type, + g.Key.Bucket, + Searches = g.Count(o => o.Kind == "search"), + Imports = g.Count(o => o.Kind == "import") + }).ToListAsync(ct); + var names = config.ArrInstances.Where(a => a.Value.Managed && (string.IsNullOrEmpty(worker) || a.Key == worker)) + .Select(a => a.Key).Union(samples.Select(s => s.Instance)).Union(events.Select(s => s.Instance)).Order().ToList(); + var sampleLookup = samples.ToLookup(s => (s.Instance, s.Bucket)); + var eventLookup = events.ToLookup(s => (s.Instance, s.Bucket)); + var series = names.Select(name => new ArrActivitySeries(name, + samples.OrderBy(s => s.Bucket).LastOrDefault(s => s.Instance == name)?.Type ?? events.FirstOrDefault(e => e.Instance == name)?.Type ?? config.ArrInstances[name].Type, + Enumerable.Range(0, duration / bucket).Select(i => + { + var minute = start + i * bucket; + var rows = sampleLookup[(name, minute)].ToList(); + var queueSamples = rows.Sum(s => s.QueueSamples); + var coverage = rows.Sum(s => s.CompletionSamples); + var completed = eventLookup[(name, minute)].ToList(); + var searches = completed.Sum(e => e.Searches); + var imports = completed.Sum(e => e.Imports); + return new ArrActivityPoint(DateTimeOffset.FromUnixTimeSeconds(minute * 60), + queueSamples == 0 ? null : rows.Sum(s => (s.QueueDepth ?? 0) * s.QueueSamples) / queueSamples, + coverage == 0 && searches == 0 ? null : searches, + coverage == 0 && imports == 0 ? null : imports, queueSamples, coverage, bucket); + }).ToList())).ToList(); + return new(range, bucket, now, series); + } +} diff --git a/src/Torrentarr.Infrastructure/Services/ArrActivityTracking.cs b/src/Torrentarr.Infrastructure/Services/ArrActivityTracking.cs new file mode 100644 index 00000000..64ebeaee --- /dev/null +++ b/src/Torrentarr.Infrastructure/Services/ArrActivityTracking.cs @@ -0,0 +1,52 @@ +using System.Security.Cryptography; +using System.Text; +using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.Logging; +using Newtonsoft.Json.Linq; +using RestSharp; +using Torrentarr.Core.Configuration; +using Torrentarr.Infrastructure.Database; + +namespace Torrentarr.Infrastructure.Services; + +/// Narrow execution-context scopes attribute real command responses to their submitter. +internal sealed class ArrActivityTracking : IDisposable +{ + private sealed record Context(TorrentarrDbContext? Db, ILogger? Logger, string Instance, + ArrInstanceConfig? Config, string Kind, string Hash, string Client); + private static readonly AsyncLocal Current = new(); + private readonly Context? _previous; + private ArrActivityTracking(Context value) { _previous = Current.Value; Current.Value = value; } + internal static IDisposable ForClient(string? client) => new ArrActivityTracking( + new(null, null, "", null, "", "", client ?? "")); + internal static IDisposable Begin(TorrentarrDbContext db, ILogger logger, string instance, + ArrInstanceConfig config, string kind, string hash = "") => new ArrActivityTracking( + new(db, logger, instance, config, kind, hash, Current.Value?.Client ?? "")); + public void Dispose() => Current.Value = _previous; + internal static string ServerKey(ArrInstanceConfig config) => Convert.ToHexString( + SHA256.HashData(Encoding.UTF8.GetBytes(config.URI.TrimEnd('/')))); + + internal static async Task RecordAcceptedAsync(RestRequest request, RestResponse response, CancellationToken ct) + { + var context = Current.Value; + if (context?.Db == null || context.Config == null || !context.Db.Database.IsSqlite() + || request.Method != Method.Post || !request.Resource.TrimEnd('/').EndsWith("/command") || !response.IsSuccessful) return; + try + { + var id = JObject.Parse(response.Content ?? "{}")["id"]?.Value() ?? 0; + if (id <= 0) return; + var server = ServerKey(context.Config); + var submitted = DateTimeOffset.UtcNow.ToUnixTimeSeconds(); + var hash = context.Hash.ToUpperInvariant(); + var previousTimeout = context.Db.Database.GetCommandTimeout(); + try + { + // Optional statistics must not hold up normal worker writes on a busy SQLite database. + context.Db.Database.SetCommandTimeout(1); + await context.Db.Database.ExecuteSqlInterpolatedAsync($"INSERT OR IGNORE INTO torrentarr_arr_operations (Instance,CommandId,Type,Kind,Server,ClientInstance,DownloadId,SubmittedAt,Outcome) VALUES ({context.Instance},{id},{context.Config.Type},{context.Kind},{server},{context.Client},{hash},{submitted},'pending')", ct); + } + finally { context.Db.Database.SetCommandTimeout(previousTimeout); } + } + catch (Exception ex) { context.Logger?.LogDebug(ex, "Activity submission tracking unavailable for {Instance}", context.Instance); } + } +} diff --git a/src/Torrentarr.Infrastructure/Services/ArrImportService.cs b/src/Torrentarr.Infrastructure/Services/ArrImportService.cs index f8a19093..20195b02 100644 --- a/src/Torrentarr.Infrastructure/Services/ArrImportService.cs +++ b/src/Torrentarr.Infrastructure/Services/ArrImportService.cs @@ -61,8 +61,9 @@ public async Task TriggerImportAsync( // Find the Arr instance configuration for this category _logger.LogTrace("Looking up Arr instance for category {Category}", category); - var arrInstance = _config.ArrInstances.Values.FirstOrDefault(i => - i.Category.Equals(category, StringComparison.OrdinalIgnoreCase)); + var target = _config.ArrInstances.FirstOrDefault(p => + p.Value.Category.Equals(category, StringComparison.OrdinalIgnoreCase)); + var arrInstance = target.Value; if (arrInstance == null) { @@ -77,12 +78,13 @@ public async Task TriggerImportAsync( _logger.LogInformation("Found Arr instance: Type={Type}, URI={URI}, Category={Category}", arrInstance.Type, DiagnosticText.Safe(arrInstance.URI), arrInstance.Category); - using var instanceScope = _logger.BeginScope(new Dictionary { ["ProcessInstance"] = _config.ArrInstances.FirstOrDefault(p => ReferenceEquals(p.Value, arrInstance)).Key ?? category }); + using var instanceScope = _logger.BeginScope(new Dictionary { ["ProcessInstance"] = target.Key }); _logger.LogTrace("Import request: path={Path}; configuredMode={ConfiguredMode}; globalMode={GlobalMode}; type={Type}", contentPath, arrInstance.ImportMode, _config.Settings.ImportMode, arrInstance.Type); try { _logger.LogInformation("Routing import to {Type} handler", arrInstance.Type); + using var activity = ArrActivityTracking.Begin(_dbContext, _logger, target.Key, arrInstance, "import", hash); var result = arrInstance.Type.ToLower() switch { "radarr" => await TriggerRadarrImportAsync(arrInstance, hash, contentPath, cancellationToken), @@ -370,8 +372,9 @@ public async Task IsCustomFormatUnmetAsync(string hash, string category, C ["TorrentHash"] = hash, ["TorrentCategory"] = category }); - var arrInstance = _config.ArrInstances.Values.FirstOrDefault(i => - i.Category.Equals(category, StringComparison.OrdinalIgnoreCase)); + var target = _config.ArrInstances.FirstOrDefault(p => + p.Value.Category.Equals(category, StringComparison.OrdinalIgnoreCase)); + var arrInstance = target.Value; if (arrInstance == null || !arrInstance.Search.CustomFormatUnmetSearch) { @@ -537,8 +540,9 @@ public async Task BlocklistAndReSearchAsync(string hash, string category, ["TorrentHash"] = hash, ["TorrentCategory"] = category }); - var arrInstance = _config.ArrInstances.Values.FirstOrDefault(i => - i.Category.Equals(category, StringComparison.OrdinalIgnoreCase)); + var target = _config.ArrInstances.FirstOrDefault(p => + p.Value.Category.Equals(category, StringComparison.OrdinalIgnoreCase)); + var arrInstance = target.Value; if (arrInstance == null) { diff --git a/src/Torrentarr.Infrastructure/Services/ArrMediaService.cs b/src/Torrentarr.Infrastructure/Services/ArrMediaService.cs index 98488180..3f82e0d2 100644 --- a/src/Torrentarr.Infrastructure/Services/ArrMediaService.cs +++ b/src/Torrentarr.Infrastructure/Services/ArrMediaService.cs @@ -337,7 +337,7 @@ private async Task> GetSearchCandidatesAsync( { if ((!searchConfig.Unmonitored && movie.Monitored != true) || movie.Searched) { - _logger.LogInformation("Search skipped for {Title} [id={ArrId}, instance={Instance}]: monitored={Monitored}, searched={Searched}", movie.Title, movie.ArrId, instanceName, movie.Monitored, movie.Searched); + _logger.LogDebug("Search skipped for {Title} [id={ArrId}, instance={Instance}]: monitored={Monitored}, searched={Searched}", movie.Title, movie.ArrId, instanceName, movie.Monitored, movie.Searched); continue; } var priority = GetReasonPriority(movie.Reason, searchConfig); @@ -372,7 +372,7 @@ private async Task> GetSearchCandidatesAsync( { if ((!searchConfig.Unmonitored && album.Monitored != true) || album.Searched) { - _logger.LogInformation("Search skipped for {Title} [id={ArrId}, instance={Instance}]: monitored={Monitored}, searched={Searched}", album.Title, album.ArrId, instanceName, album.Monitored, album.Searched); + _logger.LogDebug("Search skipped for {Title} [id={ArrId}, instance={Instance}]: monitored={Monitored}, searched={Searched}", album.Title, album.ArrId, instanceName, album.Monitored, album.Searched); continue; } var priority = GetReasonPriority(album.Reason, searchConfig); @@ -401,7 +401,7 @@ private async Task> GetSearchCandidatesAsync( { if ((!searchConfig.Unmonitored && book.Monitored != true) || book.Searched) { - _logger.LogInformation("Search skipped for {Title} [id={ArrId}, instance={Instance}]: monitored={Monitored}, searched={Searched}", book.Title, book.ArrId, instanceName, book.Monitored, book.Searched); + _logger.LogDebug("Search skipped for {Title} [id={ArrId}, instance={Instance}]: monitored={Monitored}, searched={Searched}", book.Title, book.ArrId, instanceName, book.Monitored, book.Searched); continue; } var priority = GetReasonPriority(book.Reason, searchConfig); @@ -452,7 +452,7 @@ private async Task> GetUpgradeCandidatesAsync( { if (movie.Monitored != true || !movie.HasFile || (searchConfig.DoUpgradeSearch ? movie.Upgrade : movie.Searched)) { - _logger.LogInformation("Upgrade search skipped for {Title} [id={ArrId}, instance={Instance}]: monitored={Monitored}, hasFile={HasFile}, upgrade={Upgrade}, searched={Searched}", movie.Title, movie.ArrId, instanceName, movie.Monitored, movie.HasFile, movie.Upgrade, movie.Searched); + _logger.LogDebug("Upgrade search skipped for {Title} [id={ArrId}, instance={Instance}]: monitored={Monitored}, hasFile={HasFile}, upgrade={Upgrade}, searched={Searched}", movie.Title, movie.ArrId, instanceName, movie.Monitored, movie.HasFile, movie.Upgrade, movie.Searched); continue; } var priority = GetUpgradePriority(movie.QualityMet, movie.CustomFormatMet, searchConfig); @@ -487,7 +487,7 @@ private async Task> GetUpgradeCandidatesAsync( { if (album.Monitored != true || !album.HasFile || (searchConfig.DoUpgradeSearch ? album.Upgrade : album.Searched)) { - _logger.LogInformation("Upgrade search skipped for {Title} [id={ArrId}, instance={Instance}]: monitored={Monitored}, hasFile={HasFile}, upgrade={Upgrade}, searched={Searched}", album.Title, album.ArrId, instanceName, album.Monitored, album.HasFile, album.Upgrade, album.Searched); + _logger.LogDebug("Upgrade search skipped for {Title} [id={ArrId}, instance={Instance}]: monitored={Monitored}, hasFile={HasFile}, upgrade={Upgrade}, searched={Searched}", album.Title, album.ArrId, instanceName, album.Monitored, album.HasFile, album.Upgrade, album.Searched); continue; } var priority = GetUpgradePriority(album.QualityMet, album.CustomFormatMet, searchConfig); @@ -518,7 +518,7 @@ private async Task> GetUpgradeCandidatesAsync( { if (book.Monitored != true || !book.HasFile || (searchConfig.DoUpgradeSearch ? book.Upgrade : book.Searched)) { - _logger.LogInformation("Upgrade search skipped for {Title} [id={ArrId}, instance={Instance}]: monitored={Monitored}, hasFile={HasFile}, upgrade={Upgrade}, searched={Searched}", book.Title, book.ArrId, instanceName, book.Monitored, book.HasFile, book.Upgrade, book.Searched); + _logger.LogDebug("Upgrade search skipped for {Title} [id={ArrId}, instance={Instance}]: monitored={Monitored}, hasFile={HasFile}, upgrade={Upgrade}, searched={Searched}", book.Title, book.ArrId, instanceName, book.Monitored, book.HasFile, book.Upgrade, book.Searched); continue; } var priority = GetUpgradePriority(book.QualityMet, book.CustomFormatMet, searchConfig); diff --git a/src/Torrentarr.Infrastructure/Services/ArrSyncService.cs b/src/Torrentarr.Infrastructure/Services/ArrSyncService.cs index f69d0796..14785806 100644 --- a/src/Torrentarr.Infrastructure/Services/ArrSyncService.cs +++ b/src/Torrentarr.Infrastructure/Services/ArrSyncService.cs @@ -1344,7 +1344,7 @@ private static bool MinimumAvailabilityCheck(string? minimumAvailability, DateTi // Case 1: Year > now.year or Year == 0 → Skip if (year > now.Year || year == 0) { - logger?.LogInformation("Unavailable for search (availability rule 1): {Title} - Minimum Availability: {MinAvail}, Dates Cinema:{Cinemas}, Digital:{Digital}, Physical:{Physical}", + logger?.LogDebug("Unavailable for search (availability rule 1): {Title} - Minimum Availability: {MinAvail}, Dates Cinema:{Cinemas}, Digital:{Digital}, Physical:{Physical}", title, minimumAvailability, inCinemas, digitalRelease, physicalRelease); return false; } @@ -1352,7 +1352,7 @@ private static bool MinimumAvailabilityCheck(string? minimumAvailability, DateTi // Case 2: Year < now.year - 1 → Grab (old movie over 1 year old) if (year < now.Year - 1) { - logger?.LogInformation("Available for search (availability rule 2): {Title} - Minimum Availability: {MinAvail}, Dates Cinema:{Cinemas}, Digital:{Digital}, Physical:{Physical}", + logger?.LogDebug("Available for search (availability rule 2): {Title} - Minimum Availability: {MinAvail}, Dates Cinema:{Cinemas}, Digital:{Digital}, Physical:{Physical}", title, minimumAvailability, inCinemas, digitalRelease, physicalRelease); return true; } @@ -1360,7 +1360,7 @@ private static bool MinimumAvailabilityCheck(string? minimumAvailability, DateTi // Case 3: No dates + "released" → Grab if (inCinemas == null && digitalRelease == null && physicalRelease == null && minimumAvailability == "released") { - logger?.LogInformation("Available for search (availability rule 3): {Title} - Minimum Availability: {MinAvail}, Dates Cinema:{Cinemas}, Digital:{Digital}, Physical:{Physical}", + logger?.LogDebug("Available for search (availability rule 3): {Title} - Minimum Availability: {MinAvail}, Dates Cinema:{Cinemas}, Digital:{Digital}, Physical:{Physical}", title, minimumAvailability, inCinemas, digitalRelease, physicalRelease); return true; } @@ -1370,13 +1370,13 @@ private static bool MinimumAvailabilityCheck(string? minimumAvailability, DateTi { if (digitalRelease <= now || physicalRelease <= now) { - logger?.LogInformation("Available for search (availability rule 4): {Title} - Minimum Availability: {MinAvail}, Dates Cinema:{Cinemas}, Digital:{Digital}, Physical:{Physical}", + logger?.LogDebug("Available for search (availability rule 4): {Title} - Minimum Availability: {MinAvail}, Dates Cinema:{Cinemas}, Digital:{Digital}, Physical:{Physical}", title, minimumAvailability, inCinemas, digitalRelease, physicalRelease); return true; } else { - logger?.LogInformation("Unavailable for search (availability rule 5): {Title} - Minimum Availability: {MinAvail}, Dates Cinema:{Cinemas}, Digital:{Digital}, Physical:{Physical}", + logger?.LogDebug("Unavailable for search (availability rule 5): {Title} - Minimum Availability: {MinAvail}, Dates Cinema:{Cinemas}, Digital:{Digital}, Physical:{Physical}", title, minimumAvailability, inCinemas, digitalRelease, physicalRelease); return false; } @@ -1389,13 +1389,13 @@ private static bool MinimumAvailabilityCheck(string? minimumAvailability, DateTi { if (digitalRelease <= now) { - logger?.LogInformation("Available for search (availability rule 6): {Title} - Minimum Availability: {MinAvail}, Dates Cinema:{Cinemas}, Digital:{Digital}, Physical:{Physical}", + logger?.LogDebug("Available for search (availability rule 6): {Title} - Minimum Availability: {MinAvail}, Dates Cinema:{Cinemas}, Digital:{Digital}, Physical:{Physical}", title, minimumAvailability, inCinemas, digitalRelease, physicalRelease); return true; } else { - logger?.LogInformation("Unavailable for search (availability rule 7): {Title} - Minimum Availability: {MinAvail}, Dates Cinema:{Cinemas}, Digital:{Digital}, Physical:{Physical}", + logger?.LogDebug("Unavailable for search (availability rule 7): {Title} - Minimum Availability: {MinAvail}, Dates Cinema:{Cinemas}, Digital:{Digital}, Physical:{Physical}", title, minimumAvailability, inCinemas, digitalRelease, physicalRelease); return false; } @@ -1404,13 +1404,13 @@ private static bool MinimumAvailabilityCheck(string? minimumAvailability, DateTi { if (physicalRelease <= now) { - logger?.LogInformation("Available for search (availability rule 8): {Title} - Minimum Availability: {MinAvail}, Dates Cinema:{Cinemas}, Digital:{Digital}, Physical:{Physical}", + logger?.LogDebug("Available for search (availability rule 8): {Title} - Minimum Availability: {MinAvail}, Dates Cinema:{Cinemas}, Digital:{Digital}, Physical:{Physical}", title, minimumAvailability, inCinemas, digitalRelease, physicalRelease); return true; } else { - logger?.LogInformation("Unavailable for search (availability rule 9): {Title} - Minimum Availability: {MinAvail}, Dates Cinema:{Cinemas}, Digital:{Digital}, Physical:{Physical}", + logger?.LogDebug("Unavailable for search (availability rule 9): {Title} - Minimum Availability: {MinAvail}, Dates Cinema:{Cinemas}, Digital:{Digital}, Physical:{Physical}", title, minimumAvailability, inCinemas, digitalRelease, physicalRelease); return false; } @@ -1420,7 +1420,7 @@ private static bool MinimumAvailabilityCheck(string? minimumAvailability, DateTi // Case 10: No dates + "inCinemas" → Grab if (inCinemas == null && digitalRelease == null && physicalRelease == null && minimumAvailability == "inCinemas") { - logger?.LogInformation("Available for search (availability rule 10): {Title} - Minimum Availability: {MinAvail}, Dates Cinema:{Cinemas}, Digital:{Digital}, Physical:{Physical}", + logger?.LogDebug("Available for search (availability rule 10): {Title} - Minimum Availability: {MinAvail}, Dates Cinema:{Cinemas}, Digital:{Digital}, Physical:{Physical}", title, minimumAvailability, inCinemas, digitalRelease, physicalRelease); return true; } @@ -1430,13 +1430,13 @@ private static bool MinimumAvailabilityCheck(string? minimumAvailability, DateTi { if (inCinemas <= now) { - logger?.LogInformation("Available for search (availability rule 11): {Title} - Minimum Availability: {MinAvail}, Dates Cinema:{Cinemas}, Digital:{Digital}, Physical:{Physical}", + logger?.LogDebug("Available for search (availability rule 11): {Title} - Minimum Availability: {MinAvail}, Dates Cinema:{Cinemas}, Digital:{Digital}, Physical:{Physical}", title, minimumAvailability, inCinemas, digitalRelease, physicalRelease); return true; } else { - logger?.LogInformation("Unavailable for search (availability rule 12): {Title} - Minimum Availability: {MinAvail}, Dates Cinema:{Cinemas}, Digital:{Digital}, Physical:{Physical}", + logger?.LogDebug("Unavailable for search (availability rule 12): {Title} - Minimum Availability: {MinAvail}, Dates Cinema:{Cinemas}, Digital:{Digital}, Physical:{Physical}", title, minimumAvailability, inCinemas, digitalRelease, physicalRelease); return false; } @@ -1449,13 +1449,13 @@ private static bool MinimumAvailabilityCheck(string? minimumAvailability, DateTi { if (digitalRelease <= now) { - logger?.LogInformation("Available for search (availability rule 13): {Title} - Minimum Availability: {MinAvail}, Dates Cinema:{Cinemas}, Digital:{Digital}, Physical:{Physical}", + logger?.LogDebug("Available for search (availability rule 13): {Title} - Minimum Availability: {MinAvail}, Dates Cinema:{Cinemas}, Digital:{Digital}, Physical:{Physical}", title, minimumAvailability, inCinemas, digitalRelease, physicalRelease); return true; } else { - logger?.LogInformation("Unavailable for search (availability rule 14): {Title} - Minimum Availability: {MinAvail}, Dates Cinema:{Cinemas}, Digital:{Digital}, Physical:{Physical}", + logger?.LogDebug("Unavailable for search (availability rule 14): {Title} - Minimum Availability: {MinAvail}, Dates Cinema:{Cinemas}, Digital:{Digital}, Physical:{Physical}", title, minimumAvailability, inCinemas, digitalRelease, physicalRelease); return false; } @@ -1464,13 +1464,13 @@ private static bool MinimumAvailabilityCheck(string? minimumAvailability, DateTi { if (physicalRelease <= now) { - logger?.LogInformation("Available for search (availability rule 15): {Title} - Minimum Availability: {MinAvail}, Dates Cinema:{Cinemas}, Digital:{Digital}, Physical:{Physical}", + logger?.LogDebug("Available for search (availability rule 15): {Title} - Minimum Availability: {MinAvail}, Dates Cinema:{Cinemas}, Digital:{Digital}, Physical:{Physical}", title, minimumAvailability, inCinemas, digitalRelease, physicalRelease); return true; } else { - logger?.LogInformation("Unavailable for search (availability rule 16): {Title} - Minimum Availability: {MinAvail}, Dates Cinema:{Cinemas}, Digital:{Digital}, Physical:{Physical}", + logger?.LogDebug("Unavailable for search (availability rule 16): {Title} - Minimum Availability: {MinAvail}, Dates Cinema:{Cinemas}, Digital:{Digital}, Physical:{Physical}", title, minimumAvailability, inCinemas, digitalRelease, physicalRelease); return false; } @@ -1478,7 +1478,7 @@ private static bool MinimumAvailabilityCheck(string? minimumAvailability, DateTi else { // Case 17: No inCinemas + no dates + "inCinemas" → Skip - logger?.LogInformation("Unavailable for search (availability rule 17): {Title} - Minimum Availability: {MinAvail}, Dates Cinema:{Cinemas}, Digital:{Digital}, Physical:{Physical}", + logger?.LogDebug("Unavailable for search (availability rule 17): {Title} - Minimum Availability: {MinAvail}, Dates Cinema:{Cinemas}, Digital:{Digital}, Physical:{Physical}", title, minimumAvailability, inCinemas, digitalRelease, physicalRelease); return false; } @@ -1487,13 +1487,13 @@ private static bool MinimumAvailabilityCheck(string? minimumAvailability, DateTi // Case 18: "announced" → Grab if (minimumAvailability == "announced") { - logger?.LogInformation("Available for search (availability rule 18): {Title} - Minimum Availability: {MinAvail}, Dates Cinema:{Cinemas}, Digital:{Digital}, Physical:{Physical}", + logger?.LogDebug("Available for search (availability rule 18): {Title} - Minimum Availability: {MinAvail}, Dates Cinema:{Cinemas}, Digital:{Digital}, Physical:{Physical}", title, minimumAvailability, inCinemas, digitalRelease, physicalRelease); return true; } // Case 19: Default → Skip - logger?.LogInformation("Unavailable for search (availability rule 19): {Title} - Minimum Availability: {MinAvail}, Dates Cinema:{Cinemas}, Digital:{Digital}, Physical:{Physical}", + logger?.LogDebug("Unavailable for search (availability rule 19): {Title} - Minimum Availability: {MinAvail}, Dates Cinema:{Cinemas}, Digital:{Digital}, Physical:{Physical}", title, minimumAvailability, inCinemas, digitalRelease, physicalRelease); return false; } @@ -1504,17 +1504,17 @@ private bool CheckEpisodeAvailability(DateTime? airDateUtc, string episodeTitle, if (airDateUtc == null) { - logger?.LogInformation("Episode {Title} - No air date, waiting before automatic search", episodeTitle); + logger?.LogDebug("Episode {Title} - No air date, waiting before automatic search", episodeTitle); return false; } if (airDateUtc > now) { - logger?.LogInformation("Episode {Title} - Not aired yet (AirDate: {AirDate}), skipping", episodeTitle, airDateUtc); + logger?.LogDebug("Episode {Title} - Not aired yet (AirDate: {AirDate}), skipping", episodeTitle, airDateUtc); return false; } - logger?.LogInformation("Episode {Title} - Available (aired on {AirDate})", episodeTitle, airDateUtc); + logger?.LogDebug("Episode {Title} - Available (aired on {AirDate})", episodeTitle, airDateUtc); return true; } @@ -1524,17 +1524,17 @@ private bool CheckAlbumAvailability(DateTime? releaseDate, string albumTitle, IL if (releaseDate == null) { - logger?.LogInformation("Album {Title} - No release date, available for search", albumTitle); + logger?.LogDebug("Album {Title} - No release date, available for search", albumTitle); return true; } if (releaseDate > now) { - logger?.LogInformation("Album {Title} - Not released yet (ReleaseDate: {ReleaseDate}), skipping", albumTitle, releaseDate); + logger?.LogDebug("Album {Title} - Not released yet (ReleaseDate: {ReleaseDate}), skipping", albumTitle, releaseDate); return false; } - logger?.LogInformation("Album {Title} - Available (released on {ReleaseDate})", albumTitle, releaseDate); + logger?.LogDebug("Album {Title} - Available (released on {ReleaseDate})", albumTitle, releaseDate); return true; } diff --git a/src/Torrentarr.Infrastructure/Services/MaintenanceCoordinator.cs b/src/Torrentarr.Infrastructure/Services/MaintenanceCoordinator.cs index b42131dd..6f6e2615 100644 --- a/src/Torrentarr.Infrastructure/Services/MaintenanceCoordinator.cs +++ b/src/Torrentarr.Infrastructure/Services/MaintenanceCoordinator.cs @@ -292,13 +292,6 @@ public async Task PreviewAsync(MaintenancePreviewRequest reques else if (!await seeding.CanDeleteTorrentAsync(candidate.Torrent, "maintenance preview", ct)) action.BlockedReason ??= (await seeding.EvaluateRemovalAsync(candidate.Torrent, TorrentRemovalIntent.SeedingLimit, ct))?.Reason ?? "Protection service rejected removal without diagnostic evidence."; } - if (action.Kind is MaintenanceActionKind.SetCategory or MaintenanceActionKind.SetAutomaticManagement) - { - var candidate = inspectable.FirstOrDefault(i => i.Torrent.Hash == action.TorrentHash); - if (candidate == null || !seeding.HnrAllowsDelete(candidate.Torrent, candidate.Trackers)) - action.BlockedReason ??= candidate == null ? "Torrent metadata is missing; refresh the preview." : ProtectionEvidence(seeding, candidate); - } - if (MaintenanceRequirements.MovesContent(action.Kind)) { var item = inventory.Torrents.FirstOrDefault(i => i.Torrent.ClientInstanceId.Equals(id, StringComparison.OrdinalIgnoreCase) && i.Torrent.Hash == action.TorrentHash); @@ -865,14 +858,11 @@ private async Task ApplyActionAsync(MaintenanceAction action if (action.Kind is MaintenanceActionKind.RecycleContent or MaintenanceActionKind.DeleteContent && !await seeding.CanDeleteTorrentAsync(item.Torrent, "maintenance apply", ct)) return (await seeding.EvaluateRemovalAsync(item.Torrent, TorrentRemovalIntent.SeedingLimit, ct))?.Reason ?? "Protection service rejected removal without diagnostic evidence."; - if (action.Kind is MaintenanceActionKind.RecycleContent or MaintenanceActionKind.DeleteContent or MaintenanceActionKind.SetAutomaticManagement or MaintenanceActionKind.SetCategory) + if (action.Kind is MaintenanceActionKind.RecycleContent or MaintenanceActionKind.DeleteContent) { if (!item.TrackersComplete || !seeding.HnrAllowsDelete(item.Torrent, item.Trackers)) return ProtectionEvidence(seeding, item); } - if (action.Kind == MaintenanceActionKind.Recheck && item.Torrent.IsSeeding && !MaintenanceRecovery.IsError(item.Torrent, cfg.Maintenance) - && (!item.TrackersComplete || !seeding.HnrAllowsDelete(item.Torrent, item.Trackers))) - return "Protected seeding must not be interrupted for a tracker-only issue."; var required = MaintenanceRequirements.For(action.Operation, cfg.Maintenance); if (required.HasFlag(InventoryRequirements.Trackers) && !item.TrackersComplete) return "Tracker inspection is incomplete."; if (required.HasFlag(InventoryRequirements.Files) && !item.FilesComplete) return "Torrent file enumeration is incomplete."; diff --git a/src/Torrentarr.Infrastructure/Services/OperationalIssueMonitor.cs b/src/Torrentarr.Infrastructure/Services/OperationalIssueMonitor.cs index 162ddb39..876d5459 100644 --- a/src/Torrentarr.Infrastructure/Services/OperationalIssueMonitor.cs +++ b/src/Torrentarr.Infrastructure/Services/OperationalIssueMonitor.cs @@ -16,6 +16,8 @@ public sealed class OperationalIssueMonitor(IServiceScopeFactory scopes, IOperat // Completion cursor per scope; equal timestamps retain IDs to avoid losing simultaneous runs. private readonly Dictionary RunIds)> _maintenanceRuns = new(); + private List _configurationIssues = []; + protected override async Task ExecuteAsync(CancellationToken ct) { while (!ct.IsCancellationRequested) @@ -35,8 +37,9 @@ internal async Task InspectAsync(CancellationToken ct) if (services.GetService() is { } reloader) { var state = reloader.GetStatus(); - if (state.State is "error" or "applied") await Observe(new("config.apply", "system", "Configuration could not be applied", $"Accepted revision={state.AcceptedRevision}; state={state.State}; error={DiagnosticText.Safe(state.Error)}; components: {string.Join("; ", state.Components.Select(c => $"{c.Key}: applied={c.Value.AppliedRevision}, error={DiagnosticText.Safe(c.Value.Error)}"))}.", "Check configuration validation and component status.", Target: "config"), state.State == "error", ct); + if (state.State is "error" or "applied") await Observe(new("config.apply", "system", "Configuration could not be applied", $"File={DiagnosticText.Safe(reloader.ConfigPath)}; accepted revision={state.AcceptedRevision}; state={state.State}; error={DiagnosticText.Safe(state.Error)}; components: {string.Join("; ", state.Components.Select(c => $"{c.Key}: applied={c.Value.AppliedRevision}, error={DiagnosticText.Safe(c.Value.Error)}"))}.", "Open Config and correct the indicated settings in the saved configuration file. Save and verify that all components acknowledge the accepted revision.", Target: "config"), state.State == "error", ct); } + await InspectConfigurationAsync(services, ct); if (services.GetService() is { LastChecked: not null } network) await Observe(new("network.internet", "system", "Internet connectivity is unavailable", $"Last connectivity check={network.LastChecked:O}; connected={network.IsConnected}. {network.FailureReason ?? "Client reachability and configured HTTP/TCP probes did not establish connectivity; no further probe detail was supplied."}", "Check the host network, DNS and configured connectivity hosts."), !network.IsConnected, ct); if (services.GetService() is { } clients) @@ -78,7 +81,7 @@ internal async Task InspectAsync(CancellationToken ct) foreach (var recovery in state.Recovery.Where(r => r.Stage is not ("new" or "checked"))) current.Add(new("automation.recovery", "automation", "Torrent recovery is pending", $"Torrent={recovery.TorrentName}; hash={recovery.TorrentHash}; operation={recovery.Operation}; stage={recovery.Stage}; attempts={recovery.Attempts}; failure since={recovery.FailureSince:O}; {recovery.Reason}", "Inspect the recovery status and storage access; do not remove protection evidence.", ClientInstance: recovery.ClientInstanceId, SubjectType: "torrent", SubjectId: recovery.TorrentHash, Severity: "warning", Target: "maintenance")); foreach (var group in state.Protection.Where(p => p.NativeStopLimitRisk || p.DeadlineRisk is "warning" or "critical" or "unachievable").GroupBy(p => new { p.ClientInstanceId, p.TorrentHash })) - current.Add(new("automation.protection", "automation", "Tracker obligation may be at risk", $"Hash={group.Key.TorrentHash}; {string.Join("; ", group.Select(p => $"{p.TorrentName}: tracker={p.Tracker}, state={p.State}, observed={p.ObservedSeedSeconds:F0}s, remaining={p.RemainingSeedSeconds:F0}s, deadline={p.Deadline:O}, recovery deadline={p.LatestRecoveryStart:O}, risk={p.DeadlineRisk}, native stop risk={p.NativeStopLimitRisk}. {p.Reason}"))}", "Review tracker protection and seeding limits in Manage; preserve the torrent while obligations remain.", ClientInstance: group.Key.ClientInstanceId, SubjectType: "torrent", SubjectId: group.Key.TorrentHash, + current.Add(new("automation.protection", "automation", "Tracker obligation may be at risk", $"Hash={group.Key.TorrentHash}; {string.Join("; ", group.Select(p => $"{p.TorrentName}: tracker={p.Tracker}, state={p.State}, {p.SeedAccountingDescription()}, deadline={p.Deadline:O}, recovery deadline={p.LatestRecoveryStart:O}, risk={p.DeadlineRisk}, native stop risk={p.NativeStopLimitRisk}. {p.Reason}"))}", "Review tracker protection and seeding limits in Manage; preserve the torrent while obligations remain.", ClientInstance: group.Key.ClientInstanceId, SubjectType: "torrent", SubjectId: group.Key.TorrentHash, Severity: group.Any(p => p.DeadlineRisk is "critical" or "unachievable") ? "error" : "warning", Target: "maintenance")); foreach (var inspection in state.Inspections) if (inspection.ConsecutiveFailures > 0 || inspection.LastSucceededAt.HasValue) @@ -142,6 +145,56 @@ internal async Task InspectAsync(CancellationToken ct) } } + private async Task InspectConfigurationAsync(IServiceProvider services, CancellationToken ct) + { + var current = new List(); + var validation = ConfigValidationHelper.ValidateAll(config); + if (!validation.Ok) + current.Add(new("config.validation", "system", "Configuration needs correction", + DiagnosticText.Safe(validation.Error), "Open Config and correct the indicated setting, then save and verify configuration activation.", Target: "config")); + + var clients = config.GetAllTorrentClients(); + var recycling = clients.Values.Any(c => !c.Disabled && c.Maintenance?.RecycleBin?.Enabled == true); + var paths = services.GetService(); + foreach (var (id, client) in clients) + { + if (!client.Disabled && (string.IsNullOrWhiteSpace(client.Type) || string.IsNullOrWhiteSpace(client.Host) + || client.Host == "CHANGE_ME" || client.Port is < 1 or > 65535)) + current.Add(new("config.client", "system", "Torrent client configuration is incomplete", + "An enabled torrent client requires a Type, Host and port between 1 and 65535.", + "Open Config and complete this client's connection settings, or disable/remove an unused client.", ClientInstance: id, Target: "config")); + if (client.Migration?.Notices is { Count: > 0 } notices) + current.Add(new("config.migration", "system", "Migrated configuration needs review", + DiagnosticText.Safe(string.Join("; ", notices)), + "Open Config and review this client's migrated DownloadPath, FreeSpace and Maintenance settings. Verify ownership and local mounts before enabling protection. After reviewing and correcting the settings, clear the corresponding Migration.Notices entries in config.toml.", + ClientInstance: id, Severity: "warning", Target: "config")); + if (client.Maintenance?.PathMappings is not { } mappings) continue; + var errors = new List(); + if (recycling && mappings.Count == 0 && !string.IsNullOrWhiteSpace(client.Host) && client.Host != "CHANGE_ME") + errors.Add("Maintenance.PathMappings is missing. Recycling requires a complete file ownership inventory across all configured clients, even when client and local paths are identical."); + if (paths != null) errors.AddRange(paths.Validate(mappings)); + if (errors.Count > 0) + current.Add(new("config.storage", "system", "Client storage configuration needs correction", + DiagnosticText.Safe(string.Join("; ", errors)), + "Open Config and configure this client's Maintenance.PathMappings: ClientPath is visible to the torrent client; LocalPath is the same folder inside Torrentarr. Verify mounts and permissions, then save.", + ClientInstance: id, Severity: "warning", Target: "config")); + } + + var previous = _configurationIssues.AsEnumerable(); + if (services.GetService() is { } db) + { + var persisted = await db.OperationalIssues.AsNoTracking().Where(i => i.ResolvedAt == null + && (i.Code == "config.validation" || i.Code == "config.migration" || i.Code == "config.storage" || i.Code == "config.client")).ToListAsync(ct); + previous = previous.Concat(persisted.Select(i => new OperationalIssueReport(i.Code, i.Category, i.Title, + i.Impact, i.Remediation, i.ArrInstance, i.ClientInstance, i.SubjectType, i.SubjectId, i.Severity, i.Target))); + } + foreach (var old in previous.DistinctBy(i => (i.Code, i.ClientInstance))) + if (!current.Any(i => i.Code == old.Code && i.ClientInstance == old.ClientInstance)) + await issues.ResolveAsync(old, ct); + foreach (var issue in current) await issues.ReportAsync(issue, ct); + _configurationIssues = current; + } + private Task Observe(OperationalIssueReport issue, bool failed, CancellationToken ct) => failed ? issues.ReportAsync(issue, ct) : issues.ResolveAsync(issue, ct); diff --git a/src/Torrentarr.Infrastructure/Services/QbitOverviewBuilder.cs b/src/Torrentarr.Infrastructure/Services/QbitOverviewBuilder.cs index 7e64e967..909baea6 100644 --- a/src/Torrentarr.Infrastructure/Services/QbitOverviewBuilder.cs +++ b/src/Torrentarr.Infrastructure/Services/QbitOverviewBuilder.cs @@ -29,6 +29,7 @@ public static async Task BuildAsync( .ToHashSet(StringComparer.OrdinalIgnoreCase); var categories = new List(); + var summaries = new List(); foreach (var instanceName in instanceNames) { var qbit = cfg.GetTorrentClient(instanceName); @@ -53,11 +54,13 @@ public static async Task BuildAsync( var monitored = new HashSet(qbit.ManagedCategories, StringComparer.OrdinalIgnoreCase); monitored.UnionWith(arrCategories); + var matched = new Dictionary(StringComparer.OrdinalIgnoreCase); foreach (var catName in monitored.OrderBy(c => c, StringComparer.OrdinalIgnoreCase)) { var inCat = torrents.Where(t => CategoryPathHelper.MatchesConfigured(t.Category, new[] { catName }, prefix: CategoryOwnershipHelper.PrefixMatchAllowedForOwner(cfg, catName, instanceName)) == CategoryPathHelper.NormalizeCategory(catName)).ToList(); + foreach (var torrent in inCat) matched.TryAdd(torrent.Hash, torrent); var truncated = inCat.Count > MaxTorrentsPerCategory; var slice = inCat.Take(MaxTorrentsPerCategory).Select(SerializeTorrent).ToList(); var seeding = inCat.Count(t => t.IsSeeding); @@ -80,16 +83,36 @@ public static async Task BuildAsync( torrents = slice }); } + var unique = matched.Values.ToList(); + summaries.Add(new + { + instance = instanceName, + torrentCount = unique.Count, + totalSize = unique.Sum(t => t.Size), + downloading = unique.Count(t => Activity(t) == "downloading"), + seeding = unique.Count(t => Activity(t) == "seeding"), + stopped = unique.Count(t => Activity(t) == "stopped"), + other = unique.Count(t => Activity(t) == "other") + }); } return new { instances = instanceNames, categories, + summaries, ready = true }; } + private static string Activity(TorrentInfo torrent) + { + var state = torrent.State.ToLowerInvariant(); + if (torrent.IsStopped || state.StartsWith("paused")) return "stopped"; + if (torrent.IsDownloading || state is "stalleddl" or "forceddl" or "queueddl" or "metadl" or "forcedmetadl") return "downloading"; + return torrent.IsSeeding ? "seeding" : "other"; + } + private static object SerializeTorrent(TorrentInfo t) => new { hash = t.Hash, diff --git a/src/Torrentarr.Infrastructure/Services/SafeDeletionService.cs b/src/Torrentarr.Infrastructure/Services/SafeDeletionService.cs index f6e09db0..62ee623c 100644 --- a/src/Torrentarr.Infrastructure/Services/SafeDeletionService.cs +++ b/src/Torrentarr.Infrastructure/Services/SafeDeletionService.cs @@ -80,7 +80,11 @@ private async Task RemoveAsync(string clientInstanceId, stri var inventory = validatedInventory ?? await _inventory.BuildAsync(ct: ct); if (!inventory.IsComplete) - return new(false, true, 0, $"Content deletion blocked because inventory is incomplete: {string.Join("; ", inventory.Warnings)}"); + { + var mappings = inventory.Diagnostics.Where(d => d.Code is "missing-mapping" or "unmatched-prefix" or "invalid-mapping" or "inaccessible-local-root") + .Select(d => $"Client '{d.ClientInstanceId}', client path '{d.Examples.FirstOrDefault()?.ClientPath ?? "unavailable"}': configure this client's Maintenance.PathMappings (ClientPath -> LocalPath) and ensure the local root is mounted and accessible inside Torrentarr. Identical client/local paths also require an explicit mapping."); + return new(false, true, 0, DiagnosticText.Safe($"Recycling blocked because global file ownership inventory is incomplete: {string.Join("; ", inventory.Warnings)} {string.Join(" ", mappings)}")); + } var item = inventory.Torrents.FirstOrDefault(t => t.Torrent.ClientInstanceId.Equals(clientInstanceId, StringComparison.OrdinalIgnoreCase) && t.Torrent.Hash.Equals(hash, StringComparison.OrdinalIgnoreCase)); diff --git a/src/Torrentarr.Infrastructure/Services/SearchExecutor.cs b/src/Torrentarr.Infrastructure/Services/SearchExecutor.cs index a40ae864..5b01b0a8 100644 --- a/src/Torrentarr.Infrastructure/Services/SearchExecutor.cs +++ b/src/Torrentarr.Infrastructure/Services/SearchExecutor.cs @@ -252,6 +252,7 @@ await _db.Series.AsNoTracking().Where(e => e.ArrInstance == instanceName).ToList bool triggered; try { + using var activity = ArrActivityTracking.Begin(_db, _logger, instanceName, arrConfig, "search"); triggered = isSonarr ? await TriggerSonarrSearchAsync(instanceName, arrConfig, plan, cancellationToken) : await TriggerSearchForCandidateAsync(instanceName, arrConfig, candidate, false, cancellationToken); diff --git a/src/Torrentarr.Infrastructure/Services/SeedingService.cs b/src/Torrentarr.Infrastructure/Services/SeedingService.cs index 49d68988..401425fc 100644 --- a/src/Torrentarr.Infrastructure/Services/SeedingService.cs +++ b/src/Torrentarr.Infrastructure/Services/SeedingService.cs @@ -410,20 +410,7 @@ public List GetProtectionDecisions(TorrentInfo torrent, IRea } else { - var safe = IsHnRSafeToRemove(torrent, new TrackerConfig - { - HitAndRunMode = effective.HitAndRunMode, - MinSeedRatio = effective.MinSeedRatio, - MinSeedingTimeDays = effective.MinSeedingTimeDays, - HitAndRunMinimumDownloadPercent = effective.HitAndRunMinimumDownloadPercent, - HitAndRunPartialSeedRatio = effective.HitAndRunPartialSeedRatio, - TrackerUpdateBuffer = effective.TrackerUpdateBuffer - }); - decision = new() - { - State = safe && healthy ? "eligible" : healthy ? "protected" : "unknown", - Reason = $"Legacy mode={effective.HitAndRunMode}; ratio={torrent.Ratio:F3}/{effective.MinSeedRatio}; seed={torrent.SeedingTime}s/{effective.MinSeedingTimeDays * 86400}s; minimum download={effective.HitAndRunMinimumDownloadPercent}%; partial ratio={effective.HitAndRunPartialSeedRatio}; tracker healthy={healthy}; obligations met={safe}." - }; + decision = LegacyProtectionDecision(torrent, effective, healthy); } decision.ClientInstanceId = torrent.ClientInstanceId; decision.TorrentHash = torrent.Hash; decision.TorrentName = torrent.Name; decision.Tracker = TrackerLogHost(cfg.Uri); decisions.Add(decision); @@ -435,25 +422,16 @@ public List GetProtectionDecisions(TorrentInfo torrent, IRea var knownPublic = torrent.IsPrivate == false; var legacy = IsHnREnabled(category.HitAndRunMode); var categoryConfigured = matched.Count == 0 && legacy; - var categorySafe = categoryConfigured && IsHnRSafeToRemove(torrent, new TrackerConfig - { - HitAndRunMode = category.HitAndRunMode, - MinSeedRatio = category.MinSeedRatio, - MinSeedingTimeDays = category.MinSeedingTimeDays, - HitAndRunMinimumDownloadPercent = category.HitAndRunMinimumDownloadPercent, - HitAndRunPartialSeedRatio = category.HitAndRunPartialSeedRatio, - TrackerUpdateBuffer = category.TrackerUpdateBuffer - }); var healthy = torrentTrackers.Any(t => t.Status == 2 && string.IsNullOrWhiteSpace(t.Msg)); - decisions.Add(new() + var decision = categoryConfigured ? LegacyProtectionDecision(torrent, category, healthy) : new ProtectionDecision { - ClientInstanceId = torrent.ClientInstanceId, - TorrentHash = torrent.Hash, - TorrentName = torrent.Name, - State = categoryConfigured && healthy ? categorySafe ? "eligible" : "protected" : knownPublic && !legacy ? "eligible" : "unknown", - Reason = categoryConfigured ? $"Category mode={category.HitAndRunMode}; ratio={torrent.Ratio:F3}/{category.MinSeedRatio}; seed={torrent.SeedingTime}s/{category.MinSeedingTimeDays * 86400}s; minimum download={category.HitAndRunMinimumDownloadPercent}%; partial ratio={category.HitAndRunPartialSeedRatio}; tracker healthy={healthy}; obligations met={categorySafe}." - : knownPublic && !legacy ? "Public torrent without configured HnR obligations." : "Unmatched private/unknown tracker: preserve and report." - }); + State = knownPublic && !legacy ? "eligible" : "unknown", + Reason = knownPublic && !legacy ? "Public torrent without configured HnR obligations." : "Unmatched private/unknown tracker: preserve and report." + }; + decision.ClientInstanceId = torrent.ClientInstanceId; + decision.TorrentHash = torrent.Hash; + decision.TorrentName = torrent.Name; + decisions.Add(decision); } // Configured category protection applies even when no tracker override supplies it. if (decisions.Count == 0) @@ -483,7 +461,7 @@ private async Task FreshAllowsMutationAsync(ITorrentClient client, Torrent .FirstOrDefault(t => t.Hash.Equals(torrent.Hash, StringComparison.OrdinalIgnoreCase)); if (current == null) return false; current.ClientInstanceId = torrent.ClientInstanceId; - return await HnrAllowsDeleteAsync(current, reason, ct); + return current.AddedOn == torrent.AddedOn && _config.Revision == _config.LatestRevision; } /// @@ -536,6 +514,30 @@ public async Task IsTrackerDeadAsync(TorrentInfo torrent, TrackerConfig co public Task IsHnRSafeToRemoveAsync(TorrentInfo torrent, TrackerConfig config, CancellationToken cancellationToken = default) => Task.FromResult(IsHnRSafeToRemove(torrent, config)); + private static ProtectionDecision LegacyProtectionDecision(TorrentInfo torrent, CategorySeedingConfig config, bool healthy) + { + var safe = IsHnRSafeToRemove(torrent, new TrackerConfig + { + HitAndRunMode = config.HitAndRunMode, + MinSeedRatio = config.MinSeedRatio, + MinSeedingTimeDays = config.MinSeedingTimeDays, + HitAndRunMinimumDownloadPercent = config.HitAndRunMinimumDownloadPercent, + HitAndRunPartialSeedRatio = config.HitAndRunPartialSeedRatio, + TrackerUpdateBuffer = config.TrackerUpdateBuffer + }); + var requiredSeconds = (long)config.MinSeedingTimeDays * 86400; + var timeApplies = IsHnREnabled(config.HitAndRunMode) && torrent.Progress >= 1 + && torrent.Progress >= config.HitAndRunMinimumDownloadPercent / 100.0 && requiredSeconds > 0; + return new() + { + State = safe && healthy ? "eligible" : healthy ? "protected" : "unknown", + LocalEstimate = false, + ObservedSeedSeconds = torrent.SeedingTime, + RemainingSeedSeconds = timeApplies ? Math.Max(0, requiredSeconds + (double)config.TrackerUpdateBuffer - torrent.SeedingTime) : 0, + Reason = $"Legacy mode={config.HitAndRunMode} ({(config.HitAndRunMode.Trim().Equals("and", StringComparison.OrdinalIgnoreCase) ? "all enabled ratio and time requirements" : "either enabled ratio or time requirement")}); ratio={torrent.Ratio:F3}/{config.MinSeedRatio}; client-reported seed={torrent.SeedingTime}s/{requiredSeconds}s; tracker update buffer={config.TrackerUpdateBuffer}s; minimum download={config.HitAndRunMinimumDownloadPercent}%; partial ratio={config.HitAndRunPartialSeedRatio} (partial downloads use ratio only); tracker healthy={healthy}; obligations met={safe}." + }; + } + private static bool IsHnRSafeToRemove(TorrentInfo torrent, TrackerConfig config) { if (!double.IsFinite(torrent.Ratio) || !double.IsFinite(torrent.Progress)) return false; @@ -599,16 +601,16 @@ public async Task HnrAllowsDeleteAsync(TorrentInfo torrent, string reason, if (client == null || torrent.AddedOn <= 0 || torrent.AddedOn > DateTimeOffset.UtcNow.ToUnixTimeSeconds() || DateTimeOffset.UtcNow.ToUnixTimeSeconds() - torrent.AddedOn < _config.Settings.IgnoreTorrentsYoungerThan) { - _logger.LogInformation("HnR check blocked {Reason} of {Hash} on {Client}: client unavailable or torrent age is unverified/too young", reason, torrent.Hash, torrent.ClientInstanceId); + _logger.LogDebug("HnR check blocked {Reason} of {Hash} on {Client}: client unavailable or torrent age is unverified/too young", reason, torrent.Hash, torrent.ClientInstanceId); return false; } if (!torrent.IsPrivate.HasValue) torrent.IsPrivate = (await client.GetTorrentPropertiesAsync(torrent.Hash, cancellationToken))?.IsPrivate; var trackerEvidence = await client.GetTorrentTrackersAsync(torrent.Hash, cancellationToken); var allowed = HnrAllowsDelete(torrent, trackerEvidence); - if (!allowed) _logger.LogInformation("HnR protection blocked {Reason} of {Hash}: {Evidence}", reason, torrent.Hash, + if (!allowed) _logger.LogDebug("HnR protection blocked {Reason} of {Hash}: {Evidence}", reason, torrent.Hash, DiagnosticText.Safe(string.Join("; ", GetProtectionDecisions(torrent, trackerEvidence).Where(d => d.State != "eligible").Select(d => $"{d.Tracker}: {d.State}; {d.Reason}")))); - if (allowed) _logger.LogInformation("HnR obligations cleared for {Reason} of {Hash} on {Client}", reason, torrent.Hash, torrent.ClientInstanceId); + if (allowed) _logger.LogDebug("HnR obligations cleared for {Reason} of {Hash} on {Client}", reason, torrent.Hash, torrent.ClientInstanceId); return allowed; } @@ -625,7 +627,7 @@ public async Task EvaluateRemovalAsync(TorrentInfo torrent, Tor void Block(string code, string evidence, string next) { blockers.Add(new(code, DiagnosticText.Safe(evidence), next)); - _logger.LogInformation("Removal blocked for [{Name}] ({Hash}) on {Client}, category={Category}, intent={Intent}: {Code}; {Evidence}", + _logger.LogDebug("Removal blocked for [{Name}] ({Hash}) on {Client}, category={Category}, intent={Intent}: {Code}; {Evidence}", torrent.Name, torrent.Hash, torrent.ClientInstanceId, torrent.Category, intent, code, DiagnosticText.Safe(evidence)); } var cfg = _config.GetTorrentClient(torrent.ClientInstanceId); @@ -644,7 +646,13 @@ void Block(string code, string evidence, string next) if (!torrent.IsPrivate.HasValue) torrent.IsPrivate = (await client.GetTorrentPropertiesAsync(torrent.Hash, ct))?.IsPrivate; var trackers = await client.GetTorrentTrackersAsync(torrent.Hash, ct); foreach (var decision in GetProtectionDecisions(torrent, trackers).Where(d => d.State != "eligible")) - Block("hnr." + decision.State, $"Tracker={decision.Tracker}; state={decision.State}; {decision.Reason} Observed seed={decision.ObservedSeedSeconds:F0}s; remaining={decision.RemainingSeedSeconds:F0}s; ratio={torrent.Ratio:F3}; progress={torrent.Progress:P1}; native ratioLimit={torrent.RatioLimit}, timeLimit={torrent.SeedingTimeLimit}", "Meet this tracker/category's configured obligations and verify healthy tracker accounting."); + { + var code = "hnr." + decision.State; + if (decision.NativeStopLimitRisk) code += ".native-stop-risk"; + if (decision.DeadlineRisk is "warning" or "critical" or "unachievable") code += ".deadline-" + decision.DeadlineRisk; + Block(code, $"Tracker={decision.Tracker}; state={decision.State}; {decision.Reason} {decision.SeedAccountingDescription()}; ratio={torrent.Ratio:F3}; progress={torrent.Progress:P1}; native ratioLimit={torrent.RatioLimit}, timeLimit={torrent.SeedingTimeLimit}; native stop risk={decision.NativeStopLimitRisk}; deadline risk={decision.DeadlineRisk}", + "Meet this tracker/category's configured obligations and verify healthy tracker accounting."); + } } var tags = torrent.Tags.Split(',', StringSplitOptions.TrimEntries | StringSplitOptions.RemoveEmptyEntries); foreach (var tag in tags.Where(t => t == IgnoredTag || t == FreeSpacePausedTag)) @@ -657,7 +665,7 @@ void Block(string code, string evidence, string next) var requiredAge = Math.Max(age, _config.Settings.IgnoreTorrentsYoungerThan); if (age < 0 || now - torrent.AddedOn < requiredAge) Block("torrent.minimum-age", $"Age={now - torrent.AddedOn}s; effective IgnoreTorrentsYoungerThan={requiredAge}s; category/Arr value={age}s", "Wait until the minimum age is met or correct the effective age policy."); - if (intent == TorrentRemovalIntent.RejectedByAllowlist) return new(blockers); + if (intent is TorrentRemovalIntent.RejectedByAllowlist or TorrentRemovalIntent.RejectedByCustomFormat) return new(blockers); if (intent is not (TorrentRemovalIntent.SeedingLimit or TorrentRemovalIntent.StalledDownload)) { Block("removal.unsupported-intent", $"Unknown removal intent '{intent}'", "Use a supported removal intent."); return new(blockers); } if (intent == TorrentRemovalIntent.SeedingLimit) @@ -716,6 +724,14 @@ internal static bool SameRemovalCandidate(TorrentInfo expected, TorrentInfo curr /// When HnR is enabled, returns false until real seeding_time / ratio obligations are met. /// Callers should trust this result and not repeat . /// + public async Task SeedingLimitsMetAsync(TorrentInfo torrent, CancellationToken cancellationToken = default) + { + if (!double.IsFinite(torrent.Ratio) || torrent.Ratio < 0 || !double.IsFinite(torrent.Progress) + || torrent.Progress is < 0 or > 1 || torrent.SeedingTime < 0 || !IsUploadingState(torrent.State)) return false; + _stalledUploads.Touch(torrent.ClientInstanceId, torrent.Hash, IsStalledUploadState(torrent.State)); + return RemovalLimitsMet(torrent, GetEffectiveSeedingConfig(torrent, await GetTrackerConfigAsync(torrent, cancellationToken))); + } + public async Task ShouldRemoveTorrentAsync(TorrentInfo torrent, CancellationToken cancellationToken = default) { if (MaintenanceRecovery.BlocksAutomaticRemoval(_dbContext, torrent, _config.GetTorrentClient(torrent.ClientInstanceId)?.Maintenance.ErrorRecovery ?? new ErrorRecoveryConfig())) @@ -1060,39 +1076,27 @@ public async Task UpdateSeedingTagsAsync(string category, IReadOnlySet? if (failures.Count > 0) throw new AggregateException("Some seeding maintenance could not be completed.", failures); } - private bool HasConfiguredProtection(TorrentInfo torrent) => - (_config.GetTorrentClient(torrent.ClientInstanceId)?.Trackers ?? []).Concat(_config.ArrInstances.Values - .Where(a => CategoryOwnershipHelper.ArrOwnsTorrent(_config, a, torrent)).SelectMany(a => a.Torrent.Trackers)) - .Any(t => t.Protection != null || IsHnREnabled(t.HitAndRunMode)) || IsHnREnabled(GetSeedingConfig(torrent).HitAndRunMode); - public async Task ReconcileProtectionLimitsAsync(TorrentInfo torrent, IReadOnlyList trackers, CancellationToken ct) { var client = GetClient(torrent); if (client == null || !client.Capabilities.PerTorrentLimits || _config.Revision != _config.LatestRevision) return; - if (!HasConfiguredProtection(torrent)) return; - var protection = GetProtectionDecisions(torrent, trackers, observe: true); + // H&R observes and restricts deletion only; recover native limits overridden by older versions. var key = MaintenancePersistence.Key(torrent.ClientInstanceId, torrent.Hash, "native-limits"); var state = MaintenancePersistence.Read(_dbContext, key); - if (state.AddedOn != 0 && state.AddedOn != torrent.AddedOn) state = new(); + if (state.AddedOn != 0 && state.AddedOn != torrent.AddedOn) + { + MaintenancePersistence.Write(_dbContext, key, new ProtectionObservation()); + return; + } var unlimited = torrent.RatioLimit == -1 && torrent.SeedingTimeLimit == -1 && (torrent.InactiveSeedingTimeLimit == null || torrent.InactiveSeedingTimeLimit == -1); async Task Set(double ratio, long time, long? inactive) => inactive.HasValue ? await client.SetShareLimitsAsync(torrent.Hash, ratio, time, inactive.Value, ct) : await client.SetShareLimitsAsync(torrent.Hash, ratio, time, ct); - if (protection.Any(d => d.State != "eligible") && !unlimited) - { - state.AddedOn = torrent.AddedOn; - state.PreviousRatioLimit = torrent.RatioLimit; - state.PreviousTimeLimit = torrent.SeedingTimeLimit; - state.PreviousInactiveTimeLimit = torrent.InactiveSeedingTimeLimit; - MaintenancePersistence.Write(_dbContext, key, state); - if (!await Set(-1, -1, torrent.InactiveSeedingTimeLimit.HasValue ? -1 : null)) - _logger.LogWarning("Unable to clear conflicting native stop limits for {Hash}; protection remains active", torrent.Hash); - } - else if (protection.All(d => d.State == "eligible") && state.PreviousRatioLimit.HasValue) + if (state.PreviousRatioLimit.HasValue) { - // Restore only values still owned by Torrentarr, preserving later user changes. - if (!unlimited || await Set(state.PreviousRatioLimit.Value, state.PreviousTimeLimit ?? -1, state.PreviousInactiveTimeLimit)) + if (state.AddedOn != torrent.AddedOn || !unlimited + || await Set(state.PreviousRatioLimit.Value, state.PreviousTimeLimit ?? -1, state.PreviousInactiveTimeLimit)) MaintenancePersistence.Write(_dbContext, key, new ProtectionObservation()); } } @@ -1108,8 +1112,7 @@ public async Task ApplySeedingLimitsAsync(TorrentInfo torrent, CancellationToken var trackerConfig = await GetTrackerConfigAsync(torrent, cancellationToken); var config = GetEffectiveSeedingConfig(torrent, trackerConfig); - if (HasConfiguredProtection(torrent)) - await ReconcileProtectionLimitsAsync(torrent, await client.GetTorrentTrackersAsync(torrent.Hash, cancellationToken), cancellationToken); + await ReconcileProtectionLimitsAsync(torrent, [], cancellationToken); var dlLimit = config.DownloadRateLimitPerTorrent; @@ -1395,6 +1398,7 @@ private async Task ApplyTrackerActionsAsync( .Select(t => t.Trim()) .ToHashSet(StringComparer.OrdinalIgnoreCase); var tagsToAdd = new HashSet(StringComparer.OrdinalIgnoreCase); + bool? superSeedMode = null; foreach (var cfg in actionTrackers) { @@ -1441,13 +1445,13 @@ private async Task ApplyTrackerActionsAsync( tagsToAdd.Add(tag); } - // §3.5: SuperSeedMode — set or clear super-seed mode when active tracker matches - if (client.Capabilities.SuperSeeding && cfg.SuperSeedMode.HasValue && isPresent) - { - _logger.LogInformation("SuperSeedMode={Mode}: applying to [{Name}]", cfg.SuperSeedMode.Value, torrent.Name); - if (!await client.SetSuperSeedingAsync(torrent.Hash, cfg.SuperSeedMode.Value, ct)) throw new IOException("Torrent client rejected super-seeding update."); - _logger.LogInformation("Client accepted super-seeding update for {Hash} on {Client}", torrent.Hash, torrent.ClientInstanceId); - } + if (cfg.SuperSeedMode.HasValue && isPresent) superSeedMode = cfg.SuperSeedMode.Value; + } + if (client.Capabilities.SuperSeeding && superSeedMode.HasValue && torrent.SuperSeeding != superSeedMode.Value) + { + if (!await client.SetSuperSeedingAsync(torrent.Hash, superSeedMode.Value, ct)) throw new IOException("Torrent client rejected super-seeding update."); + torrent.SuperSeeding = superSeedMode.Value; + _logger.LogInformation("Client accepted super-seeding update for {Hash} on {Client}: enabled={Enabled}", torrent.Hash, torrent.ClientInstanceId, superSeedMode.Value); } // Apply new tags (only add, never remove — qBitrr pattern) diff --git a/src/Torrentarr.Infrastructure/Services/SonarrSearchScope.cs b/src/Torrentarr.Infrastructure/Services/SonarrSearchScope.cs index 6a51f561..1b3f520b 100644 --- a/src/Torrentarr.Infrastructure/Services/SonarrSearchScope.cs +++ b/src/Torrentarr.Infrastructure/Services/SonarrSearchScope.cs @@ -21,7 +21,7 @@ internal static List Candidates(IEnumerable if ((!config.Unmonitored && episode.Monitored != true) || (episode.SeasonNumber == 0 && !specialsAllowed.Contains(episode.ArrSeriesId))) { - logger?.LogInformation("Search skipped for {Title} S{Season}E{Episode} [id={ArrId}, instance={Instance}]: monitored={Monitored}, specialsAllowed={Specials}", + logger?.LogDebug("Search skipped for {Title} S{Season}E{Episode} [id={ArrId}, instance={Instance}]: monitored={Monitored}, specialsAllowed={Specials}", episode.SeriesTitle, episode.SeasonNumber, episode.EpisodeNumber, episode.ArrId, episode.ArrInstance, episode.Monitored, specialsAllowed.Contains(episode.ArrSeriesId)); continue; diff --git a/src/Torrentarr.Infrastructure/Services/TorrentAutomationService.cs b/src/Torrentarr.Infrastructure/Services/TorrentAutomationService.cs index ada36b5d..9a05279c 100644 --- a/src/Torrentarr.Infrastructure/Services/TorrentAutomationService.cs +++ b/src/Torrentarr.Infrastructure/Services/TorrentAutomationService.cs @@ -181,16 +181,6 @@ await Task.WhenAll(torrents.Select(async torrent => } _protection[key] = decisions; } - if ((cfg.Trackers.Any(t => t.Protection != null) || _config.ArrInstances.Values.Any(a => CategoryOwnershipHelper.ArrOwnsTorrent(_config, a, torrent) && a.Torrent.Trackers.Any(t => t.Protection != null))) && torrent.Progress >= 1 && (torrent.IsStopped || torrent.State.Contains("paused", StringComparison.OrdinalIgnoreCase)) - && !SplitTags(torrent.Tags).Contains("qBitrr-free_space_paused") && !SplitTags(torrent.Tags).Contains("qBitrr-ignored")) - { - var pauseKey = MaintenancePersistence.Key(id, torrent.Hash, "routine-pause"); - var pause = MaintenancePersistence.Read(db, pauseKey); - if (pause.Owned && pause.AddedOn == torrent.AddedOn - && _protection.Values.SelectMany(decisions => decisions).Any(d => d.ClientInstanceId == id && d.TorrentHash == torrent.Hash && d.State != "eligible") - && await client.ResumeTorrentsAsync([torrent.Hash], inspectionCt)) - MaintenancePersistence.Write(db, pauseKey, new PauseOwnership()); - } _trackerInspections[key] = (fetchedAt, trackers, torrent.IsPrivate); } finally { observations.Release(); } diff --git a/src/Torrentarr.Infrastructure/Services/TorrentCacheService.cs b/src/Torrentarr.Infrastructure/Services/TorrentCacheService.cs index f8ab1955..13d9d446 100644 --- a/src/Torrentarr.Infrastructure/Services/TorrentCacheService.cs +++ b/src/Torrentarr.Infrastructure/Services/TorrentCacheService.cs @@ -15,8 +15,7 @@ public class TorrentCacheService : ITorrentCacheService private readonly Dictionary _ignoreCache = new(StringComparer.OrdinalIgnoreCase); private readonly HashSet _filePrioritiesAppliedHashes = new(StringComparer.Ordinal); private readonly HashSet _fileFilteredHashes = new(StringComparer.Ordinal); - private readonly Dictionary _allowlistWarningCache = new(StringComparer.Ordinal); - private readonly Dictionary _removalBlocks = new(StringComparer.Ordinal); + private readonly Dictionary> _diagnostics = new(StringComparer.Ordinal); private readonly object _lock = new(); private DateTime _lastFullClearUtc = DateTime.MinValue; private static readonly TimeSpan FullClearInterval = TimeSpan.FromHours(24); @@ -126,36 +125,24 @@ public void MarkFileFiltered(string qBitInstanceName, string hash) } } - public bool ShouldLogAllowlistWarning( - string qBitInstanceName, - string hash, - DateTimeOffset timestamp, - TimeSpan throttlePeriod) + public bool DiagnosticChanged(string instanceId, string hash, string stream, string? evidence) { lock (_lock) { - var key = FileStateKey(qBitInstanceName, hash); - if (_allowlistWarningCache.TryGetValue(key, out var last) - && timestamp - last < throttlePeriod) + var key = FileStateKey(instanceId, hash); + if (!_diagnostics.TryGetValue(key, out var streams)) { - return false; + if (evidence == null) return false; + _diagnostics[key] = streams = new(StringComparer.Ordinal); } - - _allowlistWarningCache[key] = timestamp; - return true; - } - } - - public bool ShouldLogRemovalBlock(string instanceId, string hash, string reason, - DateTimeOffset timestamp, TimeSpan throttlePeriod) - { - lock (_lock) - { - var key = FileStateKey(instanceId, hash); - if (_removalBlocks.TryGetValue(key, out var previous) - && previous.Reason == reason && timestamp - previous.At < throttlePeriod) - return false; - _removalBlocks[key] = (reason, timestamp); + if (evidence == null) + { + var removed = streams.Remove(stream); + if (streams.Count == 0) _diagnostics.Remove(key); + return removed; + } + if (streams.TryGetValue(stream, out var previous) && previous == evidence) return false; + streams[stream] = evidence; return true; } } @@ -166,7 +153,7 @@ public void InvalidateTorrent(string instanceId, string hash) { var key = FileStateKey(instanceId, hash); _categoryCache.Remove(key); _nameCache.Remove(key); _ignoreCache.Remove(key); - _filePrioritiesAppliedHashes.Remove(key); _fileFilteredHashes.Remove(key); _allowlistWarningCache.Remove(key); _removalBlocks.Remove(key); + _filePrioritiesAppliedHashes.Remove(key); _fileFilteredHashes.Remove(key); _diagnostics.Remove(key); } } @@ -179,8 +166,7 @@ public void Clear() _ignoreCache.Clear(); _filePrioritiesAppliedHashes.Clear(); _fileFilteredHashes.Clear(); - _allowlistWarningCache.Clear(); - _removalBlocks.Clear(); + _diagnostics.Clear(); _lastFullClearUtc = DateTime.UtcNow; _logger.LogTrace("All caches cleared"); } @@ -198,8 +184,7 @@ public void CleanExpired() _ignoreCache.Clear(); _filePrioritiesAppliedHashes.Clear(); _fileFilteredHashes.Clear(); - _allowlistWarningCache.Clear(); - _removalBlocks.Clear(); + _diagnostics.Clear(); _lastFullClearUtc = now; _logger.LogInformation("Periodic full cache clear (every {Hours}h) to bound memory growth", (int)FullClearInterval.TotalHours); } diff --git a/src/Torrentarr.Infrastructure/Services/TorrentProcessor.cs b/src/Torrentarr.Infrastructure/Services/TorrentProcessor.cs index 43d3e90e..372741ce 100644 --- a/src/Torrentarr.Infrastructure/Services/TorrentProcessor.cs +++ b/src/Torrentarr.Infrastructure/Services/TorrentProcessor.cs @@ -35,8 +35,7 @@ public class TorrentProcessor : ITorrentProcessor private readonly StalledUploadTracker? _stalledUploads; private readonly ISafeDeletionService? _safeDeletion; private readonly IOperationalIssueReporter? _issues; - private static readonly TimeSpan AllowlistWarningLifetime = TimeSpan.FromMinutes(5); - private static readonly TimeSpan RemovalFailureWarningLifetime = TimeSpan.FromMinutes(5); + private readonly HashSet _activeRemovalBlocks = []; private readonly HashSet _specialCategories; private readonly Dictionary _recoveryRequests = new(StringComparer.OrdinalIgnoreCase); @@ -78,11 +77,6 @@ public TorrentProcessor( private async Task MayPauseAsync(ITorrentClient client, TorrentInfo torrent, CancellationToken ct) { - if (_seedingService == null) - { - _logger.LogInformation("Pause withheld for {Hash}: seeding protection service is unavailable", torrent.Hash); - return false; - } var current = (await client.GetTorrentsAsync(cancellationToken: ct)).FirstOrDefault(t => t.Hash.Equals(torrent.Hash, StringComparison.OrdinalIgnoreCase)); if (current == null) { @@ -90,7 +84,8 @@ private async Task MayPauseAsync(ITorrentClient client, TorrentInfo torren return false; } current.ClientInstanceId = torrent.ClientInstanceId; - return await _seedingService.HnrAllowsDeleteAsync(current, "routine pause", ct); + return SeedingService.SameRemovalCandidate(torrent, current) && !current.SuperSeeding + && !current.State.Equals("forcedUP", StringComparison.OrdinalIgnoreCase); } private bool CanResumeAutomatically(TorrentInfo torrent) @@ -131,15 +126,8 @@ public async Task ProcessTorrentsAsync(string category, IReadOnlySet? cl { _logger.LogInformation("Starting torrent processing for category {Category}", category); + cancellationToken.ThrowIfCancellationRequested(); _recoveryRequests.Clear(); - var allClients = _qbitManager.GetAllClients().Where(pair => clientIds == null || clientIds.Contains(pair.Key)) - .ToDictionary(pair => pair.Key, pair => pair.Value, StringComparer.OrdinalIgnoreCase); - if (allClients.Count == 0) - { - _logger.LogWarning("No qBittorrent client available"); - return; - } - // Skip special categories - they are handled globally by the Host orchestrator if (_specialCategories.Contains(category)) { @@ -147,6 +135,18 @@ public async Task ProcessTorrentsAsync(string category, IReadOnlySet? cl return; } + var allClients = _qbitManager.GetAllClients().Where(pair => clientIds == null || clientIds.Contains(pair.Key)) + .ToDictionary(pair => pair.Key, pair => pair.Value, StringComparer.OrdinalIgnoreCase); + if (allClients.Count == 0) + { + throw new TorrentProcessingUnavailableException("No connected torrent client is available for the requested scan."); + } + + var missingClients = _config.GetAllTorrentClients().Where(p => !p.Value.Disabled + && !string.IsNullOrWhiteSpace(p.Value.Host) && p.Value.Host != "CHANGE_ME" + && (clientIds == null || clientIds.Contains(p.Key)) + && CategoryOwnershipHelper.ResolveOwningCategory(_config, category, p.Key) == category + && !allClients.ContainsKey(p.Key)).Select(p => p.Key).ToList(); try { // Ensure tags exist on all clients (skip in Tagless mode — §1.6) @@ -206,12 +206,18 @@ public async Task ProcessTorrentsAsync(string category, IReadOnlySet? cl catch (Exception ex) when (ex is not OperationCanceledException) { failures.Add(ex); } } if (failures.Count > 0) throw new AggregateException("Some torrent work could not be completed.", failures); + if (missingClients.Count > 0) throw new TorrentProcessingUnavailableException($"Torrent clients could not be inspected: {string.Join(", ", missingClients)}"); if (_issues != null) await _issues.ResolveAsync(ProcessingIssue(category), cancellationToken); _logger.LogInformation( "Processed {Total} torrents in {Category}: {Downloading} downloading, {Completed} completed, {Seeding} seeding, {Failed} failed, {Ignored} ignored", stats.TotalTorrents, category, stats.Downloading, stats.Completed, stats.Seeding, stats.Failed, stats.Ignored); } + catch (TorrentProcessingUnavailableException ex) + { + if (_issues != null) await _issues.ReportAsync(ProcessingIssue(category).WithEvidence(ex.Message) with { Severity = "warning" }, cancellationToken); + throw; + } catch (Exception ex) when (ex is not OperationCanceledException) { _logger.LogError(ex, "Error processing torrents for category {Category}", category); @@ -369,6 +375,7 @@ public async Task ImportTorrentAsync(string hash, string? qbitInstance = null, C } } + using var activityClient = ArrActivityTracking.ForClient(torrent.QBitInstanceName); var result = await _importService.TriggerImportAsync( hash, torrent.ContentPath, owner, cancellationToken); @@ -470,8 +477,22 @@ private async Task ProcessObservedTorrentAsync(TorrentInfo torrent, string categ try { if (_issues != null) await _issues.ResolveAsync(OwnershipIssue(torrent), ct); + _activeRemovalBlocks.Clear(); await ProcessSingleTorrentAsync(torrent, category, stats, ct); - if (_issues != null) await _issues.ResolveAsync(issue, ct); + foreach (var intent in Enum.GetValues().Where(i => !_activeRemovalBlocks.Contains(i))) + await ClearRemovalBlockAsync(torrent, intent); + if (_issues != null) + { + // Reconcile persisted warnings too: a restart may have cleared their cache entries. + var outstanding = await _dbContext.OperationalIssues.AsNoTracking().Where(i => i.ResolvedAt == null + && i.Code.StartsWith("torrent.removal.") && i.ClientInstance == torrent.ClientInstanceId + && i.SubjectType == "torrent" && i.SubjectId == torrent.Hash).ToListAsync(ct); + foreach (var previous in outstanding.Where(i => !_activeRemovalBlocks.Any(intent => i.Code == "torrent.removal." + intent))) + await _issues.ResolveAsync(new(previous.Code, previous.Category, previous.Title, previous.Impact, + previous.Remediation, previous.ArrInstance, previous.ClientInstance, previous.SubjectType, + previous.SubjectId, previous.Severity, previous.Target), ct); + await _issues.ResolveAsync(issue, ct); + } } catch (HttpRequestException ex) when (ex.StatusCode == System.Net.HttpStatusCode.NotFound) { @@ -604,6 +625,25 @@ await ignClient.RemoveTagsAsync(new List { torrent.Hash }, return; } + if (_pathTracker?.IsHashAlreadyScanned(torrent.ClientInstanceId, torrent.Hash) == true) + { + await TryFinalizeImportedTorrentAsync(torrent, ct); + return; + } + + var completedForImport = arrCfg != null && torrent.AddedOn > 0 + && torrent.CompletionOn > 0 + && torrent.AmountLeft == 0 + && IsCompleteState(state) + && !string.IsNullOrEmpty(torrent.ContentPath) + && torrent.CompletionOn < timeNow - 60; + if (completedForImport && !_cache.IsFileFiltered(torrent.ClientInstanceId, torrent.Hash)) + { + var filterResult = await ApplyFileFilterAsync(torrent, arrCfg!, client, completed: true, ct); + if (filterResult.Deleted || !filterResult.Ready) return; + if (!filterResult.RetryCleanup) _cache.MarkFileFiltered(torrent.ClientInstanceId, torrent.Hash); + } + // ================================================================================ // STATE MACHINE — if/elif chain matching qBitrr's _process_single_torrent ordering // ================================================================================ @@ -621,10 +661,23 @@ await ignClient.RemoveTagsAsync(new List { torrent.Hash }, _logger.LogInformation("Removal candidate (custom format unmet): [{Name}] | Hash[{Hash}]", torrent.Name, torrent.Hash); if (await DeleteTorrentFromClientAsync(client, torrent, deleteFiles: true, ct, - stillApplies: current => _importService.IsCustomFormatUnmetAsync(current.Hash, ResolveOwner(current) ?? "", ct))) + stillApplies: current => _importService.IsCustomFormatUnmetAsync(current.Hash, ResolveOwner(current) ?? "", ct), + intent: TorrentRemovalIntent.RejectedByCustomFormat)) stats.Failed++; } } + // Import allowed completed content before routine seeding cleanup; H&R only authorizes deletion. + else if (completedForImport + && (await FindTorrentLibraryEntryAsync(torrent.Hash, torrent.ClientInstanceId, ct))?.Imported != true) + { + stats.Completed++; + if (await IsReadyForImportAsync(torrent.Hash, torrent.QBitInstanceName, ct)) + { + _logger.LogInformation("Completed torrent ready for import: [{Name}] | Progress[{Progress:P1}] | Hash[{Hash}]", + torrent.Name, torrent.Progress, torrent.Hash); + await ImportTorrentAsync(torrent.Hash, torrent.QBitInstanceName, ct); + } + } // Branch 2: Ratio/seed limit met AND not leave_alone AND fully downloaded → delete // (qBitrr line 6106-6109: remove_torrent and not leave_alone and amount_left==0) else if (!recoveryBlocksRemoval && removeTorrent && !leaveAlone && torrent.AmountLeft == 0) @@ -785,35 +838,6 @@ await ProcessStalledTorrentAsync( _logger.LogInformation("Re-checking errored torrent: [{Name}] | Hash[{Hash}]", torrent.Name, torrent.Hash); _logger.LogInformation("Keeping errored torrent {Hash}; recovery never authorizes removal", torrent.Hash); } - // Branch 16: Fully completed (60s grace period) → trigger import (qBitrr line 6196-6205) - // Python: if leave_alone → allow seeding; elif not tagged qBitrr-imported → dispatch to import_torrents - else if (torrent.AddedOn > 0 - && torrent.CompletionOn > 0 - && torrent.AmountLeft == 0 - && IsCompleteState(state) - && !string.IsNullOrEmpty(torrent.ContentPath) - && torrent.CompletionOn < timeNow - 60) - { - stats.Completed++; - if (arrCfg != null && !_cache.IsFileFiltered(torrent.QBitInstanceName, torrent.Hash)) - { - var filterResult = await ApplyFileFilterAsync(torrent, arrCfg, client, completed: true, ct); - if (filterResult.Deleted || !filterResult.Ready) - return; - - _cache.MarkFileFiltered(torrent.QBitInstanceName, torrent.Hash); - } - if (leaveAlone && !recoveryBlocksRemoval || state == TorrentState.ForcedUploading) - { - _logger.LogInformation("Completed torrent — allowing seeding: [{Name}]", torrent.Name); - } - else if (await IsReadyForImportAsync(torrent.Hash, torrent.QBitInstanceName, ct)) - { - _logger.LogInformation("Completed torrent ready for import: [{Name}] | Progress[{Progress:P1}] | Hash[{Hash}]", - torrent.Name, torrent.Progress, torrent.Hash); - await ImportTorrentAsync(torrent.Hash, torrent.QBitInstanceName, ct); - } - } // Branch 17: Uploading + seeding limits configured → pause if !leave_alone (qBitrr line 6207-6215) else if (IsUploadingState(state) && torrent.SeedingTime > 1 @@ -821,7 +845,7 @@ await ProcessStalledTorrentAsync( && torrent.AddedOn > 0 && !string.IsNullOrEmpty(torrent.ContentPath) && GetRemoveMode(torrent, arrCfg) != -1 - && _cache.IsFileFiltered(torrent.QBitInstanceName, torrent.Hash)) + && (completedForImport || _cache.IsFileFiltered(torrent.QBitInstanceName, torrent.Hash))) { if (leaveAlone || state == TorrentState.ForcedUploading) { @@ -949,7 +973,7 @@ await ProcessStalledTorrentAsync( torrent.Hash, defaultMaxEta, trackerCfg?.MaxETA, maxEta, torrent.Eta); // Check if torrent should be removed (ratio/time limits met, including stalledUP idle) - var shouldRemove = await _seedingService.ShouldRemoveTorrentAsync(torrent, ct); + var shouldRemove = await _seedingService.SeedingLimitsMetAsync(torrent, ct); // leave_alone = NOT (isUploading AND shouldRemove) — qBitrr line 5862-5864 var leaveAlone = !(isUploading && shouldRemove); @@ -987,18 +1011,34 @@ await ProcessStalledTorrentAsync( ArrInstance: ResolveOwner(torrent) ?? "", ClientInstance: torrent.ClientInstanceId, SubjectType: "torrent", SubjectId: torrent.Hash, Severity: "warning", Target: "maintenance"); - private async Task LogRemovalBlocked(TorrentInfo torrent, TorrentRemovalIntent intent, string reason, string? cause = null) + private async Task ClearRemovalBlockAsync(TorrentInfo torrent, TorrentRemovalIntent intent) { - if (!_cache.ShouldLogRemovalBlock(torrent.ClientInstanceId, torrent.Hash, $"{intent}:{_config.Revision}: {cause ?? System.Text.RegularExpressions.Regex.Replace(reason, @"\b\d+(?:[.,]\d+)*\b", "#")}", - DateTimeOffset.UtcNow, RemovalFailureWarningLifetime)) return; - _logger.LogWarning("Torrent removal blocked: [{Name}] | Hash[{Hash}] | Client[{Client}] | Arr[{Arr}] | Category[{Category}] | Intent[{Intent}] | Mode[{Mode}] | Reason[{Reason}]", - torrent.Name, torrent.Hash, torrent.ClientInstanceId, ResolveOwner(torrent), torrent.Category, intent, - _config.GetTorrentClient(torrent.ClientInstanceId)?.Maintenance.RecycleBin.Enabled == true ? "recycle" : "native client deletion", - DiagnosticText.Safe(reason)); - if (_issues != null) await _issues.ReportAsync(RemovalIssue(torrent, intent) with - { - Impact = DiagnosticText.Safe($"Torrent={torrent.Name}; hash={torrent.Hash}; client={torrent.ClientInstanceId}; category={torrent.Category}; intent={intent}; mode={(_config.GetTorrentClient(torrent.ClientInstanceId)?.Maintenance.RecycleBin.Enabled == true ? "recycle" : "native deletion")}. {reason}") - }); + var cleared = _cache.DiagnosticChanged(torrent.ClientInstanceId, torrent.Hash, "removal:" + intent, null); + if (cleared && _issues != null) await _issues.ResolveAsync(RemovalIssue(torrent, intent)); + } + + private async Task LogRemovalBlocked(TorrentInfo torrent, TorrentRemovalIntent intent, string reason, + string? cause = null, RemovalDecision? decision = null) + { + _activeRemovalBlocks.Add(intent); + var expectedWait = decision?.IsExpectedProtectionWait() == true; + var identity = decision == null ? cause ?? Regex.Replace(reason, @"\b\d+(?:[.,]\d+)*\b", "#") + : string.Join(",", decision.Blockers.Select(b => b.Code).Distinct().Order()); + var changed = _cache.DiagnosticChanged(torrent.ClientInstanceId, torrent.Hash, "removal:" + intent, + $"{_config.Revision}:{torrent.Category}:{identity}"); + _logger.Log(changed ? expectedWait ? LogLevel.Information : LogLevel.Warning : LogLevel.Debug, + "Torrent removal deferred: [{Name}] | Hash[{Hash}] | Client[{Client}] | Arr[{Arr}] | Category[{Category}] | Intent[{Intent}] | Mode[{Mode}] | Reason[{Reason}]", + torrent.Name, torrent.Hash, torrent.ClientInstanceId, ResolveOwner(torrent), torrent.Category, intent, + _config.GetTorrentClient(torrent.ClientInstanceId)?.Maintenance.RecycleBin.Enabled == true ? "recycle" : "native client deletion", + DiagnosticText.Safe(reason)); + if (_issues != null) + { + if (expectedWait) await _issues.ResolveAsync(RemovalIssue(torrent, intent)); + else await _issues.ReportAsync(RemovalIssue(torrent, intent) with + { + Impact = DiagnosticText.Safe($"Torrent={torrent.Name}; hash={torrent.Hash}; client={torrent.ClientInstanceId}; category={torrent.Category}; intent={intent}. {reason}") + }); + } } private async Task DeleteTorrentFromClientAsync( @@ -1006,8 +1046,8 @@ private async Task DeleteTorrentFromClientAsync( Func>? stillApplies = null, TorrentRemovalIntent intent = TorrentRemovalIntent.SeedingLimit, string? triggerEvidence = null) { - Task Block(string reason, string? cause = null) => LogRemovalBlocked(torrent, intent, - triggerEvidence == null ? reason : $"{triggerEvidence} Blocker: {reason}", cause); + Task Block(string reason, string? cause = null, RemovalDecision? decision = null) => LogRemovalBlocked(torrent, intent, + triggerEvidence == null ? reason : $"{triggerEvidence} Cleanup deferred: {reason}", cause, decision); if (MaintenanceRecovery.BlocksAutomaticRemoval(_dbContext, torrent, _config.GetTorrentClient(torrent.ClientInstanceId)?.Maintenance.ErrorRecovery ?? new ErrorRecoveryConfig())) { @@ -1040,7 +1080,7 @@ Task Block(string reason, string? cause = null) => LogRemovalBlocked(torrent, in if (!cleared) { await Block(decision?.Reason ?? "Protection service denied removal without supplying a diagnostic decision; inspect the protection implementation.", - decision == null ? "protection.unknown" : string.Join(",", decision.Blockers.Select(b => b.Code))); + decision == null ? "protection.unknown" : string.Join(",", decision.Blockers.Select(b => b.Code)), decision); return false; } if (stillApplies != null && !await stillApplies(current)) @@ -1627,9 +1667,9 @@ var s when s.Contains("checking") => TorrentState.CheckingUploading, /// /// §2.1: Apply file filtering to a downloading torrent. /// Sets excluded files to priority 0; deletes torrent if all files are excluded. - /// For completed torrents, disallowed files must be absent before import. When AutoDelete - /// is enabled only torrent-owned paths are removed. The result is not ready when qBittorrent - /// rejects a priority update or disallowed files remain, so the next processing cycle retries. + /// Completed torrents with allowed files can import while optional excluded-file cleanup retries. + /// Only owned paths may be removed, with H&R clearance; invalid paths and rejected priority updates + /// remain pending. Cleanup never gates an otherwise eligible import. /// private async Task ApplyFileFilterAsync( TorrentInfo torrent, @@ -1639,6 +1679,11 @@ private async Task ApplyFileFilterAsync( CancellationToken ct) { var cfg = arrCfg.Torrent; + FileFilterResult Ready(bool retryCleanup = false) + { + _cache.DiagnosticChanged(torrent.ClientInstanceId, torrent.Hash, "allowlist", null); + return FileFilterResult.ReadyResult with { RetryCleanup = retryCleanup }; + } // No filtering configured — fast exit if (cfg.FolderExclusionRegex.Count == 0 && @@ -1646,7 +1691,7 @@ private async Task ApplyFileFilterAsync( cfg.FileExtensionAllowlist.Count == 0) { _logger.LogInformation("File filtering skipped for [{Name}]: no exclusion or allowlist rules configured", torrent.Name); - return FileFilterResult.ReadyResult; + return Ready(); } var files = await client.GetTorrentFilesAsync(torrent.Hash, ct); @@ -1669,7 +1714,6 @@ private async Task ApplyFileFilterAsync( file.Name, torrent.Name, excluded, reason ?? "allowed by configured rules", file.Priority, file.Progress, file.Size, string.Join("; ", cfg.FolderExclusionRegex), string.Join("; ", cfg.FileNameExclusionRegex), string.Join("; ", cfg.FileExtensionAllowlist), cfg.CaseSensitiveMatches); (excluded ? excludedFiles : allowedFiles).Add(file); } - var excludedIds = excludedFiles.Select(f => f.Index).ToArray(); var prioritiesToApply = excludedFiles .Where(f => f.Priority != 0) .Select(f => f.Index) @@ -1702,13 +1746,13 @@ private async Task ApplyFileFilterAsync( if (allowedFiles.Count == 0) { - WarnAllowlistBlocked(torrent, "no allowed files remain", excludedFiles); + WarnAllowlistBlocked(torrent, "no allowed files remain", excludedFiles, cfg); var hnrAllows = _seedingService != null && await _seedingService.HnrAllowsDeleteAsync(torrent, "all files excluded by import allowlist", ct); if (!hnrAllows) { var decision = _seedingService == null ? null : await _seedingService.EvaluateRemovalAsync(torrent, TorrentRemovalIntent.RejectedByAllowlist, ct); - await LogRemovalBlocked(torrent, TorrentRemovalIntent.RejectedByAllowlist, decision?.Reason ?? "HnR inspection could not authorize removal.", "allowlist.hnr"); + await LogRemovalBlocked(torrent, TorrentRemovalIntent.RejectedByAllowlist, decision?.Reason ?? "HnR inspection could not authorize removal.", "allowlist.hnr", decision); return FileFilterResult.PendingResult; } @@ -1717,19 +1761,29 @@ private async Task ApplyFileFilterAsync( meaningfulFiles.Count, torrent.Name, torrent.Hash); return await DeleteTorrentFromClientAsync(client, torrent, deleteFiles: true, ct, intent: TorrentRemovalIntent.RejectedByAllowlist, - triggerEvidence: $"All {meaningfulFiles.Count} meaningful files were excluded. Files={string.Join("; ", excludedFiles.Select(f => f.Name))}; effective extension allowlist={string.Join("; ", cfg.FileExtensionAllowlist)}; filename exclusions={string.Join("; ", cfg.FileNameExclusionRegex)}; folder exclusions={string.Join("; ", cfg.FolderExclusionRegex)}.") + triggerEvidence: $"Import rejected: all {meaningfulFiles.Count} meaningful files were excluded. {AllowlistEvidence(excludedFiles, cfg)}") ? FileFilterResult.DeletedResult : FileFilterResult.PendingResult; } - if (excludedIds.Length == 0) - return FileFilterResult.ReadyResult; + if (excludedFiles.Count == 0) + return Ready(); if (!completed) - return FileFilterResult.ReadyResult; + return Ready(); + + try + { + foreach (var file in meaningfulFiles) _ = ResolveTorrentOwnedPath(torrent, file.Name); + } + catch (Exception ex) when (ex is not OperationCanceledException) + { + WarnAllowlistBlocked(torrent, "unsafe or invalid content path", excludedFiles, cfg); + return FileFilterResult.PendingResult; + } if (!TorrentStorageAvailable(torrent, allowedFiles)) { - WarnAllowlistBlocked(torrent, "content storage is unavailable", excludedFiles); + WarnAllowlistBlocked(torrent, "content storage is unavailable", excludedFiles, cfg); return FileFilterResult.PendingResult; } @@ -1738,80 +1792,36 @@ private async Task ApplyFileFilterAsync( { var hnrAllows = _seedingService != null && await _seedingService.HnrAllowsDeleteAsync(torrent, "excluded file cleanup by import allowlist", ct); - if (!hnrAllows) - { - // H&R must prevent deleting owned files, but does not need to block - // importing valid media when only harmless metadata sidecars remain. - if (CanImportWithPreservedSidecars(torrent, remaining)) - { - _logger.LogInformation("Import permitted for {Hash}; preserving {Count} excluded .nfo/.txt sidecar files due to H&R obligations", - torrent.Hash, remaining.Count); - return FileFilterResult.ReadyResult; - } - WarnAllowlistBlocked(torrent, "H&R obligations prevent excluded file cleanup", remaining); - return FileFilterResult.PendingResult; - } - - foreach (var file in remaining) - await TryDeleteTorrentOwnedFileAsync(torrent, file.Name, ct); - - remaining = excludedFiles.Where(f => TorrentOwnedFileExists(torrent, f.Name)).ToList(); - } - - if (remaining.Count == 0) - return FileFilterResult.ReadyResult; - - if (CanImportWithPreservedSidecars(torrent, remaining)) - { - _logger.LogInformation("Import permitted for {Hash}; preserving {Count} excluded .nfo/.txt metadata sidecars", - torrent.Hash, remaining.Count); - return FileFilterResult.ReadyResult; + if (hnrAllows) + foreach (var file in remaining) await TryDeleteTorrentOwnedFileAsync(torrent, file.Name, ct); + else + _logger.LogDebug("Excluded-file cleanup deferred for {Hash}; H&R does not block import", torrent.Hash); } - - WarnAllowlistBlocked(torrent, $"{remaining.Count} disallowed file(s) remain", remaining); - return FileFilterResult.PendingResult; + return Ready(retryCleanup: cfg.AutoDelete && remaining.Any(f => TorrentOwnedFileExists(torrent, f.Name))); } - private bool CanImportWithPreservedSidecars(TorrentInfo torrent, IReadOnlyCollection remaining) + private static string AllowlistEvidence(IReadOnlyList files, TorrentConfig cfg) { - if (remaining.Count == 0 || MaintenanceRecovery.BlocksAutomaticRemoval(_dbContext, torrent, - _config.GetTorrentClient(torrent.ClientInstanceId)?.Maintenance.ErrorRecovery ?? new ErrorRecoveryConfig())) - return false; - - foreach (var file in remaining) - { - var extension = Path.GetExtension(file.Name); - if (!extension.Equals(".nfo", StringComparison.OrdinalIgnoreCase) - && !extension.Equals(".txt", StringComparison.OrdinalIgnoreCase)) - return false; - try - { - // Do not relax the import barrier for symlink, traversal, or invalid paths. - _ = ResolveTorrentOwnedPath(torrent, file.Name); - } - catch - { - return false; - } - } - return true; + var options = cfg.CaseSensitiveMatches ? RegexOptions.None : RegexOptions.IgnoreCase; + var examples = files.Take(5).Select(f => + $"{(f.Name.Length > 200 ? f.Name[..200] + "…" : f.Name)} ({FileExclusionReason(f.Name, cfg, options)})"); + var archives = files.Any(f => Regex.IsMatch(f.Name, @"\.(?:rar|r\d{2,3})$", RegexOptions.IgnoreCase)); + return $"Excluded files ({files.Count} total, {Math.Max(0, files.Count - 5)} more): {string.Join("; ", examples)}. " + + (archives ? "Rejected content includes RAR archive files; archive contents cannot be imported through this allowlist workflow. " : "") + + $"Effective extension allowlist={string.Join("; ", cfg.FileExtensionAllowlist.Distinct())}; filename exclusions={string.Join("; ", cfg.FileNameExclusionRegex.Distinct())}; folder exclusions={string.Join("; ", cfg.FolderExclusionRegex.Distinct())}."; } - private void WarnAllowlistBlocked(TorrentInfo torrent, string reason, IReadOnlyList excludedFiles) + private void WarnAllowlistBlocked(TorrentInfo torrent, string reason, IReadOnlyList excludedFiles, TorrentConfig cfg) { - var now = DateTimeOffset.UtcNow; - if (!_cache.ShouldLogAllowlistWarning( - torrent.QBitInstanceName, - torrent.Hash, - now, - AllowlistWarningLifetime)) - { - return; - } - - _logger.LogWarning("Import blocked by file allowlist for [{Name}] ({Hash}): {Reason}; excluded files ({Count} total, {Remaining} more): {Files}", - torrent.Name, torrent.Hash, reason, excludedFiles.Count, Math.Max(0, excludedFiles.Count - 5), - string.Join(", ", excludedFiles.Take(5).Select(f => f.Name.Length > 200 ? f.Name[..200] + "…" : f.Name))); + // File progress and seeding counters do not change the rejection itself. + var fingerprint = Convert.ToHexString(System.Security.Cryptography.SHA256.HashData( + System.Text.Encoding.UTF8.GetBytes(System.Text.Json.JsonSerializer.Serialize( + excludedFiles.Select(f => new { f.Name, f.Size }).OrderBy(f => f.Name))))); + var changed = _cache.DiagnosticChanged(torrent.ClientInstanceId, torrent.Hash, "allowlist", + $"{_config.Revision}:{reason}:{fingerprint}"); + _logger.Log(changed ? LogLevel.Warning : LogLevel.Debug, + "Import blocked by file allowlist for [{Name}] ({Hash}): {Reason}; {Evidence}", + torrent.Name, torrent.Hash, reason, DiagnosticText.Safe(AllowlistEvidence(excludedFiles, cfg))); } internal static (int StalledDelay, int IgnoreTorrentsYoungerThan) ResolveStalledSettings( @@ -1848,7 +1858,7 @@ private async Task TryDeleteTorrentOwnedFileAsync(TorrentInfo torrent, string re { var path = ResolveTorrentOwnedPath(torrent, relativePath); if (File.Exists(path) && (_safeDeletion == null || !await _safeDeletion.DeleteOwnedFileAsync(torrent.ClientInstanceId, torrent.Hash, path, ct))) - _logger.LogWarning("Excluded-file cleanup was not authorized for {Hash}: {Path}; import remains pending", torrent.Hash, path); + _logger.LogWarning("Excluded-file cleanup was not authorized for {Hash}: {Path}; import is permitted after configured filtering", torrent.Hash, path); } catch (Exception ex) when (ex is not OperationCanceledException) { @@ -1942,7 +1952,7 @@ private static bool IsIgnoredAuxiliaryFile(string path) || name.EndsWith(".parts", StringComparison.OrdinalIgnoreCase); } - private readonly record struct FileFilterResult(bool Ready, bool Deleted) + private readonly record struct FileFilterResult(bool Ready, bool Deleted, bool RetryCleanup = false) { public static FileFilterResult ReadyResult => new(true, false); public static FileFilterResult PendingResult => new(false, false); diff --git a/src/Torrentarr.Infrastructure/Services/TransferHistoryService.cs b/src/Torrentarr.Infrastructure/Services/TransferHistoryService.cs new file mode 100644 index 00000000..e2e9a85c --- /dev/null +++ b/src/Torrentarr.Infrastructure/Services/TransferHistoryService.cs @@ -0,0 +1,147 @@ +using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Routing; +using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Hosting; +using Microsoft.Extensions.Logging; +using Torrentarr.Core.Configuration; +using Torrentarr.Core.Interfaces; +using Torrentarr.Infrastructure.Database; +using Torrentarr.Infrastructure.Database.Models; + +namespace Torrentarr.Infrastructure.Services; + +public sealed record TransferBucket(DateTimeOffset Timestamp, double? DownloadRate, double? UploadRate, int Samples, int ExpectedSamples); +public sealed record TransferSeries(string Instance, List Points); +public sealed record TransferHistoryResponse(string Range, int BucketMinutes, DateTimeOffset GeneratedAt, List Series); + +/// One lightweight transfer request per enabled client per minute; no torrent scans. +public sealed class TransferHistoryCollector( + IServiceScopeFactory scopes, TorrentarrConfig config, ITorrentClientRegistry registry, + ILogger logger) : BackgroundService +{ + private long _lastPruneMinute = long.MinValue; + + protected override async Task ExecuteAsync(CancellationToken stoppingToken) + { + using var timer = new PeriodicTimer(TimeSpan.FromMinutes(1)); + do + { + try { await CollectAsync(DateTimeOffset.UtcNow, stoppingToken); } + catch (OperationCanceledException) when (stoppingToken.IsCancellationRequested) { break; } + catch (Exception ex) { logger.LogWarning(ex, "Transfer history collection failed"); } + } while (await timer.WaitForNextTickAsync(stoppingToken)); + } + + internal async Task CollectAsync(DateTimeOffset now, CancellationToken ct) + { + var minute = now.ToUnixTimeSeconds() / 60; + using var scope = scopes.CreateScope(); + var db = scope.ServiceProvider.GetRequiredService(); + foreach (var (name, settings) in config.GetAllTorrentClients().ToArray()) + { + if (settings.Disabled || registry.GetClient(name) is not { } client) continue; + try + { + using var timeout = CancellationTokenSource.CreateLinkedTokenSource(ct); + timeout.CancelAfter(TimeSpan.FromSeconds(10)); + var transfer = await client.GetTransferInfoAsync(timeout.Token); + if (transfer == null || transfer.DownloadSpeed < 0 || transfer.UploadSpeed < 0) continue; + if (await db.TransferSamples.AnyAsync(s => s.ClientInstance == name && s.Minute == minute, ct)) continue; + db.TransferSamples.Add(new TransferSample + { + ClientInstance = name, + Minute = minute, + DownloadRate = transfer.DownloadSpeed, + UploadRate = transfer.UploadSpeed + }); + } + catch (OperationCanceledException) when (ct.IsCancellationRequested) { throw; } + catch (Exception ex) { logger.LogDebug(ex, "Transfer history unavailable for {Client}", name); } + } + await db.SaveChangesAsync(ct); + if (_lastPruneMinute == long.MinValue || minute - _lastPruneMinute >= 60) + { + await db.TransferSamples.Where(s => s.Minute < minute - 7 * 24 * 60).ExecuteDeleteAsync(ct); + _lastPruneMinute = minute; + } + } +} + +public static class TransferHistoryApi +{ + public static void MapTransferHistory(this IEndpointRouteBuilder app) + { + app.MapGet("/web/statistics/catalog", async (string kind, string? instance, + TorrentarrConfig config, CatalogRollupService rollups, TorrentarrDbContext db, CancellationToken ct) => + { + if (kind is not ("radarr" or "sonarr" or "lidarr" or "readarr")) + return Results.BadRequest(new { error = "Unknown catalog kind" }); + var keys = config.ArrInstances.Where(a => a.Value.Type == kind && + (string.IsNullOrEmpty(instance) || instance == "aggregate" || a.Key.Equals(instance, StringComparison.OrdinalIgnoreCase))) + .SelectMany(a => ArrCatalogIdentity.QueryKeys(a)).Distinct().ToList(); + if (!string.IsNullOrEmpty(instance) && instance != "aggregate" && keys.Count == 0) + return Results.BadRequest(new { error = "Unknown catalog instance" }); + CatalogRollupService.Counts counts; + int total; + string unit; + switch (kind) + { + case "radarr": + (counts, total) = await rollups.GetRadarrRollupsAsync(keys, ct); unit = "movies"; break; + case "sonarr": + (counts, _) = await rollups.GetSonarrRollupsAsync(keys, ct); + total = await db.Episodes.CountAsync(e => keys.Contains(e.ArrInstance), ct); unit = "episodes"; break; + case "lidarr": + (counts, total, _) = await rollups.GetLidarrRollupsAsync(keys, ct); unit = "albums"; break; + default: + (counts, total) = await rollups.GetReadarrRollupsAsync(keys, ct); unit = "books"; break; + } + return Results.Ok(new { unit, total, counts.Available, counts.Monitored, counts.Missing }); + }); + app.MapGet("/web/statistics/transfers", async (string? instance, string? range, + TorrentarrConfig config, TorrentarrDbContext db, CancellationToken ct) => + { + range ??= "24h"; + if (range is not ("1h" or "24h" or "7d")) + return Results.BadRequest(new { error = "range must be 1h, 24h, or 7d" }); + var names = config.GetAllTorrentClients().Where(c => !c.Value.Disabled).Select(c => c.Key).ToList(); + if (!string.IsNullOrEmpty(instance) && instance != "all") + { + var match = names.Find(n => n.Equals(instance, StringComparison.OrdinalIgnoreCase)); + if (match == null) return Results.BadRequest(new { error = "Unknown client instance" }); + names = [match]; + } + return Results.Ok(await QueryAsync(db, names, range, DateTimeOffset.UtcNow, ct)); + }); + } + + public static async Task QueryAsync(TorrentarrDbContext db, + List names, string range, DateTimeOffset now, CancellationToken ct = default) + { + var (minutes, bucket) = range switch + { + "1h" => (60, 1), + "24h" => (1440, 5), + "7d" => (10080, 30), + _ => throw new ArgumentOutOfRangeException(nameof(range)) + }; + // Use completed wall-clock buckets so the expected coverage is unambiguous. + var end = now.ToUnixTimeSeconds() / 60 / bucket * bucket; + var start = end - minutes; + var rows = await db.TransferSamples.AsNoTracking().Where(s => names.Contains(s.ClientInstance) + && s.Minute >= start && s.Minute < end).ToListAsync(ct); + var lookup = rows.ToLookup(s => (s.ClientInstance, Bucket: s.Minute / bucket * bucket)); + var series = names.Select(name => new TransferSeries(name, + Enumerable.Range(0, minutes / bucket).Select(i => + { + var minute = start + i * bucket; + var samples = lookup[(name, minute)].ToList(); + return new TransferBucket(DateTimeOffset.FromUnixTimeSeconds(minute * 60), + samples.Count == 0 ? null : samples.Average(s => (double)s.DownloadRate), + samples.Count == 0 ? null : samples.Average(s => (double)s.UploadRate), samples.Count, bucket); + }).ToList())).ToList(); + return new(range, bucket, now, series); + } +} diff --git a/src/Torrentarr.Infrastructure/Services/WorkerProcessSupervisor.cs b/src/Torrentarr.Infrastructure/Services/WorkerProcessSupervisor.cs index 48d7f58f..c4399916 100644 --- a/src/Torrentarr.Infrastructure/Services/WorkerProcessSupervisor.cs +++ b/src/Torrentarr.Infrastructure/Services/WorkerProcessSupervisor.cs @@ -55,7 +55,7 @@ protected override async Task ExecuteAsync(CancellationToken stoppingToken) { while (!stoppingToken.IsCancellationRequested) { - MonitorWorkers(); + await MonitorWorkersAsync(stoppingToken); await StopRemovedWorkersAsync(stoppingToken); await Task.Delay(TimeSpan.FromSeconds(5), stoppingToken); } @@ -108,18 +108,19 @@ private async Task StopWorkersAsync(CancellationToken cancellationToken) finally { _lifecycle.Release(); } } - private async Task StopWorkerAsync(string instanceName, CancellationToken cancellationToken) + private async Task StopWorkerAsync(string instanceName, CancellationToken cancellationToken, Process? expected = null) { Process? process; lock (_gate) { + if (expected != null && (!_processes.TryGetValue(instanceName, out var current) || !ReferenceEquals(current, expected))) return false; CancelRetry(instanceName); _processes.Remove(instanceName, out process); if (process != null) _processRevisions.Remove(process); _statusFiles.Remove(instanceName); _reportedWorkers.Remove(instanceName); } - if (process == null) return; + if (process == null) return false; try { if (!process.HasExited) @@ -132,12 +133,14 @@ private async Task StopWorkerAsync(string instanceName, CancellationToken cancel { if (!process.HasExited) { + _logger.LogWarning("Worker {Instance} did not stop gracefully; terminating it", instanceName); try { process.Kill(true); } catch (InvalidOperationException) { } await process.WaitForExitAsync(CancellationToken.None); } } finally { process.Dispose(); } + return true; } private void CancelRetry(string name) @@ -519,22 +522,28 @@ private void ScheduleRetry(string name) _ = task.ContinueWith(_ => { lock (_gate) _retryTasks.Remove(task); }, TaskScheduler.Default); } - internal void MonitorWorkers() + internal async Task MonitorWorkersAsync(CancellationToken ct = default) { - lock (_gate) + await _lifecycle.WaitAsync(ct); + try { - foreach (var (name, process) in _processes.ToArray()) + KeyValuePair[] unhealthy; + lock (_gate) + unhealthy = _processes.Where(p => !p.Value.HasExited) + .Where(p => { var status = ReadProcessStatus(p.Key, p.Value); return !status.IsAlive || status.RestartRequested; }) + .ToArray(); + foreach (var (name, process) in unhealthy) { - if (process.HasExited) continue; - var status = ReadProcessStatus(name, process); - if (!status.IsAlive || status.RestartRequested) + _logger.LogWarning("Worker {Instance} timed out or requested a restart; requesting graceful shutdown", name); + if (!await StopWorkerAsync(name, ct, process)) continue; + lock (_gate) { - _logger.LogWarning("Worker {Instance} timed out or requested a restart; terminating it", name); - try { process.Kill(true); } - catch (InvalidOperationException) { } + _lastFailures[name] = (null, "Worker timed out or requested a restart."); + ScheduleRetry(name); } } } + finally { _lifecycle.Release(); } } internal static ProcessStartInfo ResolveWorkerStartInfo(string instanceName, string? baseDirectory = null) diff --git a/src/Torrentarr.WebUI/Program.cs b/src/Torrentarr.WebUI/Program.cs index 8ced8cee..3dcd0557 100644 --- a/src/Torrentarr.WebUI/Program.cs +++ b/src/Torrentarr.WebUI/Program.cs @@ -166,6 +166,8 @@ [new OpenApiSecuritySchemeReference("Bearer", document)] = [] builder.Services.AddSingleton(); builder.Services.AddHostedService(sp => sp.GetRequiredService()); builder.Services.AddSingleton(sp => sp.GetRequiredService()); +builder.Services.AddHostedService(); +builder.Services.AddHostedService(); builder.Services.AddSingleton(); builder.Services.AddSingleton(); builder.Services.AddSingleton(); @@ -1233,6 +1235,9 @@ static bool IsValidLogFileName(string name) => app.MapTorrentMaintenanceEndpoints("/web", standalone: true); app.MapTorrentMaintenanceEndpoints("/api", standalone: true); +app.MapTransferHistory(); +app.MapArrActivity(); + app.MapGet("/web/qbit/overview", async (HttpRequest request, TorrentarrConfig cfg, ITorrentClientRegistry clientRegistry, CancellationToken ct) => { var instance = request.Query["instance"].FirstOrDefault(); diff --git a/src/Torrentarr.Workers/Program.cs b/src/Torrentarr.Workers/Program.cs index 80e52156..58819825 100644 --- a/src/Torrentarr.Workers/Program.cs +++ b/src/Torrentarr.Workers/Program.cs @@ -457,11 +457,23 @@ private async Task RunTorrentLoopAsync(CancellationToken cancellationToken) if (due.Count > 0) await ProcessTorrentsAsync(cycle.Token, due); else await RunPeriodicCommandsAsync(cycle.Token); _consecutiveErrors = 0; - _runtimeMetrics.Torrent = _runtimeMetrics.Torrent with { LastSuccess = DateTimeOffset.UtcNow }; - await ObserveLoopAsync("torrent", true, cycle.Token); - outcome = "success"; + if (due.Count > 0) + { + _runtimeMetrics.Torrent = _runtimeMetrics.Torrent with { LastSuccess = DateTimeOffset.UtcNow }; + await ObserveLoopAsync("torrent", true, cycle.Token); + } + outcome = due.Count > 0 ? "success" : "idle"; } catch (OperationCanceledException) when (cancellationToken.IsCancellationRequested) { break; } + catch (TorrentProcessingUnavailableException ex) + { + outcome = "waiting for torrent client"; + _logger.LogInformation("Torrent processing deferred for {Instance}: {Reason}", _context.InstanceName, ex.Message); + await ObserveLoopAsync("torrent", false, cancellationToken); + scans.Reset(); + await SleepLoopAsync("Torrent", "Waiting for client", TimeSpan.FromSeconds(5), cancellationToken, connectionRevision); + continue; + } catch (ConfigurationBusyException) { outcome = "configuration pending"; diff --git a/tests/Torrentarr.Core.Tests/Configuration/StorageMigrationTests.cs b/tests/Torrentarr.Core.Tests/Configuration/StorageMigrationTests.cs index f7b417ad..516dfea9 100644 --- a/tests/Torrentarr.Core.Tests/Configuration/StorageMigrationTests.cs +++ b/tests/Torrentarr.Core.Tests/Configuration/StorageMigrationTests.cs @@ -174,6 +174,32 @@ public void MultipleClientsUseTheirOwnPaths_AndWarnForAmbiguousPaths() config.QBitInstances["qBit-one"].FreeSpace.Single().Enabled.Should().BeTrue(); config.TorrentClients["two"].DownloadPath.Should().BeNull(); config.TorrentClients["two"].FreeSpace.Should().OnlyContain(m => !m.Enabled); + config.QBitInstances["qBit-one"].Migration.Notices.Should().BeEmpty(); + config.TorrentClients["two"].Migration.Notices.Should().ContainSingle().Which.Should().Contain("ambiguous"); + var persisted = File.ReadAllText(_file); + var loader = new ConfigurationLoader(_file); + loader.Load().TorrentClients["two"].Migration.Notices.Should().Equal(config.TorrentClients["two"].Migration.Notices); + File.ReadAllText(_file).Should().Be(persisted); + config.TorrentClients["two"].Migration.Notices.Clear(); + loader.SaveConfig(config); + loader.Load().TorrentClients["two"].Migration.Notices.Should().BeEmpty(); + } + + [Theory] + [InlineData("qBit")] + [InlineData("TorrentClient.named")] + public void MissingLegacyStoragePaths_PersistAnActionableMigrationNotice(string section) + { + var config = Load($""" + [Settings] + FreeSpace = "10G" + [{section}] + Host = "localhost" + """); + var client = config.GetAllTorrentClients().Values.Single(); + client.FreeSpace.Should().BeEmpty(); + client.Migration.Notices.Should().ContainSingle().Which.Should().Contain("free-space protection remains disabled until configured"); + new ConfigurationLoader(_file).Load().GetAllTorrentClients().Values.Single().Migration.Notices.Should().Equal(client.Migration.Notices); } [Theory] diff --git a/tests/Torrentarr.Core.Tests/Configuration/TorrentClientMaintenanceTests.cs b/tests/Torrentarr.Core.Tests/Configuration/TorrentClientMaintenanceTests.cs index 8510714f..a17466ba 100644 --- a/tests/Torrentarr.Core.Tests/Configuration/TorrentClientMaintenanceTests.cs +++ b/tests/Torrentarr.Core.Tests/Configuration/TorrentClientMaintenanceTests.cs @@ -7,6 +7,13 @@ namespace Torrentarr.Core.Tests.Configuration; public sealed class TorrentClientMaintenanceTests { + [Theory] + [InlineData(Torrentarr.Core.Models.MaintenanceOperation.CategoryReconcile)] + [InlineData(Torrentarr.Core.Models.MaintenanceOperation.AutomaticManagement)] + [InlineData(Torrentarr.Core.Models.MaintenanceOperation.RepairPaused)] + public void NonDeletionMaintenance_DoesNotRequireHnrEvidence(Torrentarr.Core.Models.MaintenanceOperation operation) + => MaintenanceRequirements.For(operation, new()).Should().Be(Torrentarr.Core.Models.InventoryRequirements.Metadata); + [Fact] public void CanonicalClient_RoundTripsWithoutChangingLegacySections() { diff --git a/tests/Torrentarr.Host.Tests/Api/StatisticsEndpointTests.cs b/tests/Torrentarr.Host.Tests/Api/StatisticsEndpointTests.cs new file mode 100644 index 00000000..7c5a0c0c --- /dev/null +++ b/tests/Torrentarr.Host.Tests/Api/StatisticsEndpointTests.cs @@ -0,0 +1,93 @@ +using FluentAssertions; +using System.Net; +using Microsoft.Extensions.DependencyInjection; +using Torrentarr.Infrastructure.Database; +using System.Text.Json; +using Xunit; + +namespace Torrentarr.Host.Tests.Api; + +[Collection("HostWeb")] +public class StatisticsEndpointTests(TorrentarrWebApplicationFactory factory) +{ + [Theory] + [InlineData("/web/statistics/transfers?range=invalid")] + [InlineData("/web/statistics/arr-activity?range=invalid")] + [InlineData("/web/statistics/arr-activity?worker=does-not-exist")] + [InlineData("/web/statistics/transfers?instance=does-not-exist")] + [InlineData("/web/statistics/catalog?kind=invalid")] + [InlineData("/web/statistics/catalog?kind=radarr&instance=does-not-exist")] + public async Task RejectsInvalidSelections(string path) + { + factory.SetConfigEnv(); + using var client = factory.CreateClientWithApiToken(); + (await client.GetAsync(path)).StatusCode.Should().Be(HttpStatusCode.BadRequest); + } + + [Fact] + public async Task Transfers_DefaultsTo24HoursAndEmptySeriesWithoutClients() + { + factory.SetConfigEnv(); + using var client = factory.CreateClientWithApiToken(); + var response = await client.GetAsync("/web/statistics/transfers"); + response.StatusCode.Should().Be(HttpStatusCode.OK); + using var json = JsonDocument.Parse(await response.Content.ReadAsStringAsync()); + json.RootElement.GetProperty("range").GetString().Should().Be("24h"); + json.RootElement.GetProperty("bucketMinutes").GetInt32().Should().Be(5); + } + [Fact] + public async Task ArrActivity_ReturnsBoundedTypedHistory() + { + factory.SetConfigEnv(); using var client = factory.CreateClientWithApiToken(); + var response = await client.GetAsync("/web/statistics/arr-activity?range=1h"); + response.StatusCode.Should().Be(HttpStatusCode.OK); + using var json = JsonDocument.Parse(await response.Content.ReadAsStringAsync()); + json.RootElement.GetProperty("bucketMinutes").GetInt32().Should().Be(1); + json.RootElement.GetProperty("series").ValueKind.Should().Be(JsonValueKind.Array); + } + +} + +[Collection("HostWebAuth")] +public class StatisticsAuthTests(AuthEnabledWebApplicationFactory factory) +{ + [Theory] + [InlineData("/web/statistics/transfers")] + [InlineData("/web/statistics/arr-activity")] + [InlineData("/web/statistics/catalog?kind=radarr")] + public async Task RequiresConfiguredAuthentication(string path) + { + factory.SetConfigEnv(); + using var client = factory.CreateClientWithApiTokenNoRedirect(); + client.DefaultRequestHeaders.Authorization = null; + var response = await client.GetAsync(path); + response.StatusCode.Should().Be(HttpStatusCode.Redirect); + response.Headers.Location!.OriginalString.Should().Contain("/login"); + } +} + +[Collection("HostWebCatalog")] +public class CatalogStatisticsEndpointTests(ArrCatalogWebApplicationFactory factory) +{ + [Theory] + [InlineData("radarr", "Radarr", "movies")] + [InlineData("sonarr", "Sonarr", "episodes")] + [InlineData("lidarr", "Lidarr", "albums")] + [InlineData("readarr", "Readarr-Books", "books")] + public async Task ReturnsCompleteSelectedLibraryCounts(string kind, string instance, string unit) + { + factory.SetConfigEnv(); + using var client = factory.CreateClientWithApiToken(); + using var scope = factory.Services.CreateScope(); + var db = scope.ServiceProvider.GetRequiredService(); + await CatalogTestDataSeeder.SeedAllCatalogDataAsync(db); + await CatalogTestDataSeeder.SeedReadarrAuthorsAsync(db, clearFirst: false); + var response = await client.GetAsync($"/web/statistics/catalog?kind={kind}&instance={instance}"); + response.StatusCode.Should().Be(HttpStatusCode.OK); + using var json = JsonDocument.Parse(await response.Content.ReadAsStringAsync()); + json.RootElement.GetProperty("unit").GetString().Should().Be(unit); + json.RootElement.GetProperty("available").GetInt32().Should().Be(1); + json.RootElement.GetProperty("monitored").GetInt32().Should().Be(2); + json.RootElement.GetProperty("missing").GetInt32().Should().Be(1); + } +} diff --git a/tests/Torrentarr.Host.Tests/ProcessOrchestratorServiceTests.cs b/tests/Torrentarr.Host.Tests/ProcessOrchestratorServiceTests.cs index b2a3228b..fa9bb181 100644 --- a/tests/Torrentarr.Host.Tests/ProcessOrchestratorServiceTests.cs +++ b/tests/Torrentarr.Host.Tests/ProcessOrchestratorServiceTests.cs @@ -15,6 +15,32 @@ namespace Torrentarr.Host.Tests; public class ProcessOrchestratorServiceTests { + [Fact] + public async Task TrackerSort_ShutdownCancellationPropagatesWithoutErrorOrFallback() + { + using var cancellation = new CancellationTokenSource(); cancellation.Cancel(); + var client = new Mock(); + client.Setup(c => c.GetTorrentsAsync(null, "priority", It.IsAny())) + .ThrowsAsync(new OperationCanceledException(cancellation.Token)); + var registry = new Mock(); + registry.Setup(r => r.GetAllClients()).Returns(new Dictionary { ["one"] = client.Object }); + var entries = new Mock(); + var factory = new Mock(); + factory.Setup(f => f.CreateLogger(It.IsAny())).Returns(entries.Object); + var logger = new Microsoft.Extensions.Logging.Logger(factory.Object); + using var provider = new ServiceCollection().BuildServiceProvider(); + using var service = new ProcessOrchestratorService(logger, new(), registry.Object, + provider.GetRequiredService(), new ProcessStateManager(), Mock.Of()); + var method = typeof(ProcessOrchestratorService).GetMethod("SortManagedTorrentsByTrackerPriorityAsync", + System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.NonPublic)!; + await FluentActions.Awaiting(() => (Task)method.Invoke(service, + [Mock.Of(), new HashSet { "movies" }, new HashSet { "one" }, cancellation.Token])!) + .Should().ThrowAsync(); + client.Verify(c => c.GetTorrentsAsync(null, "added_on", It.IsAny()), Times.Never()); + entries.Invocations.Where(i => i.Method.Name == "Log").Should().NotContain(i => + (Microsoft.Extensions.Logging.LogLevel)i.Arguments[0]! == Microsoft.Extensions.Logging.LogLevel.Error); + } + [Fact] public async Task FreeSpace_ConfigSaveWakesSingleManagerAndRefreshesClientMonitors() { diff --git a/tests/Torrentarr.Host.Tests/WorkerLogEventSinkTests.cs b/tests/Torrentarr.Host.Tests/WorkerLogEventSinkTests.cs index a7132a8e..19024e8d 100644 --- a/tests/Torrentarr.Host.Tests/WorkerLogEventSinkTests.cs +++ b/tests/Torrentarr.Host.Tests/WorkerLogEventSinkTests.cs @@ -86,13 +86,15 @@ public void Exceptions_AreWrittenWithoutChangingRoutingOrPrefix(string message, finally { directory.Delete(true); } } [Fact] - public void RestartAppends_AndKeepsExistingHistory() + public void RestartAppends_KeepsRecentHistory_AndExpiresOldFiles() { var directory = Directory.CreateTempSubdirectory("torrentarr-log-restart-"); try { - var older = Path.Combine(directory.FullName, "torrentarr-20200101.log"); + var older = Path.Combine(directory.FullName, $"torrentarr-{DateTime.Now.AddDays(-1):yyyyMMdd}.log"); + var expired = Path.Combine(directory.FullName, "torrentarr-20200101.log"); File.WriteAllText(older, "old history\n"); + File.WriteAllText(expired, "expired history\n"); for (var i = 0; i < 2; i++) { using var sink = new WorkerLogEventSink(directory.FullName); @@ -104,6 +106,7 @@ public void RestartAppends_AndKeepsExistingHistory() var service = Directory.GetFiles(directory.FullName, "torrentarr-*.log").Single(p => p != older); File.ReadAllText(service).Should().Be(File.ReadAllText(aggregate)); File.ReadAllText(older).Should().Be("old history\n"); + File.Exists(expired).Should().BeFalse(); Directory.GetFiles(directory.FullName, "*.old").Should().BeEmpty(); } finally { directory.Delete(true); } @@ -232,8 +235,11 @@ await Task.WhenAll(processes.Select(async process => lines.Count(line => line.StartsWith("System.IO.IOException: concurrent-")).Should().Be(8); lines.Count(line => line.Contains("InvalidOperationException: inner cause")).Should().Be(8); Directory.GetFiles(directory.FullName, "worker-test-*.log").Should().HaveCount(2); + var serviceRecords = Directory.GetFiles(directory.FullName, "worker-test-*.log") + .SelectMany(File.ReadAllLines).Where(line => line.Contains("]entry-")).ToArray(); + serviceRecords.Should().BeEquivalentTo(records); var rolled = Directory.GetFiles(Path.Combine(directory.FullName, "rollover"), "all-*.log"); - rolled.Should().HaveCount(7); + rolled.Should().HaveCount(7, "the seven-day age limit applies before the eight-file maximum"); rolled.Select(Path.GetFileName).Should().BeEquivalentTo(Enumerable.Range(4, 7).Select(day => $"all-202601{day:00}.log")); foreach (var file in rolled) File.ReadAllLines(file).Should().ContainSingle(); } diff --git a/tests/Torrentarr.Infrastructure.Tests/ApiClients/SonarrApiDiscoveryTests.cs b/tests/Torrentarr.Infrastructure.Tests/ApiClients/SonarrApiDiscoveryTests.cs index 7ace5315..156a1cc8 100644 --- a/tests/Torrentarr.Infrastructure.Tests/ApiClients/SonarrApiDiscoveryTests.cs +++ b/tests/Torrentarr.Infrastructure.Tests/ApiClients/SonarrApiDiscoveryTests.cs @@ -39,6 +39,8 @@ public sealed class SonarrApiDiscoveryTests : IDisposable private string? _queueJson; private int _seriesProfile = 1; private string _commandsJson = "[]"; + private string _historyJson = "[]"; + private string? _commandResponseJson; private bool _limitAfterFirstSearch; private readonly string _key = Guid.NewGuid().ToString(); @@ -60,6 +62,45 @@ public SonarrApiDiscoveryTests() _loop = Task.Run(ListenAsync); } + + [Theory] + [InlineData("radarr", "v3")] + [InlineData("sonarr", "v3")] + [InlineData("sonarr", "v5")] + [InlineData("lidarr", "v1")] + [InlineData("readarr", "v1")] + public async Task ActivityEvidence_UsesClientApiAndPreservesBasePath(string type, string version) + { + _version = version; + _commandsJson = "[{\"id\":42,\"status\":\"completed\"}]"; + _historyJson = "[{\"id\":99,\"downloadId\":\"HASH\",\"eventType\":\"downloadFolderImported\"}]"; + IArrActivityClient client = type switch + { + "radarr" => new RadarrClient(_url, _key), + "sonarr" => new SonarrClient(_url, _key), + "lidarr" => new LidarrClient(_url, _key), + _ => new ReadarrClient(_url, _key) + }; + (await client.ReadActivityAsync("command", default)).Single()["id"]!.Value().Should().Be(42); + (await client.ReadActivityAsync("history/since?date=2026-11-04T12%3A00%3A00Z&eventType=3", default)).Single()["id"]!.Value().Should().Be(99); + _requests.Should().Contain(r => r.Path.StartsWith($"/sonarr/api/{version}/history/since?") && r.Path.Contains("eventType=3")); + } + + [Theory] + [InlineData("v3")] + [InlineData("v5")] + public async Task SearchTracking_CapturesRealAcceptedCommandWithoutChangingBooleanApi(string version) + { + _version = version; _commandResponseJson = "{\"id\":42}"; + using var db = new TorrentarrDbContext(new DbContextOptionsBuilder().UseSqlite("Data Source=:memory:").Options); + await db.Database.OpenConnectionAsync(); await db.Database.EnsureCreatedAsync(); + var config = new ArrInstanceConfig { Type = "sonarr", URI = _url, APIKey = _key }; + using (ArrActivityTracking.Begin(db, NullLogger.Instance, "Sonarr-test", config, "search")) + (await new SonarrClient(_url, _key).SearchEpisodeAsync([1, 2])).Should().BeTrue(); + var operation = await db.ArrActivityOperations.SingleAsync(); + operation.CommandId.Should().Be(42); operation.Instance.Should().Be("Sonarr-test"); operation.ConfirmedAt.Should().BeNull(); + } + [Theory] [InlineData("v3")] [InlineData("v5")] @@ -869,6 +910,8 @@ private async Task ListenAsync() var json = relative == "" ? _discoveryJson ?? $"{{\"current\":\"{_version}\"}}" : relative.EndsWith("/system/status") ? _statusJson ?? $"{{\"version\":\"{(_version == "v3" ? 4 : 5)}.0.0\"}}" : relative.Contains("/queue?") ? _queueJson ?? "{\"records\":[{\"id\":1,\"episodeIds\":[1,2],\"seasonNumbers\":[1],\"sizeLeft\":100}]}" + : relative.Contains("/history/since?") ? _historyJson + : relative.EndsWith("/command") && ctx.Request.HttpMethod == "POST" && _commandResponseJson != null ? _commandResponseJson : relative.Contains("/wanted/") ? "{\"records\":[]}" : relative.Contains("/episode?") && ctx.Request.QueryString["seriesId"] == "7" ? "[{\"id\":1,\"seriesId\":7,\"seasonNumber\":1,\"monitored\":true,\"airDateUtc\":\"2020-01-01T00:00:00Z\"},{\"id\":2,\"seriesId\":7,\"seasonNumber\":1,\"monitored\":true,\"airDateUtc\":\"2020-01-01T00:00:00Z\"}]" : relative.Contains("/episode?") ? "[{\"id\":1,\"episodeFile\":{\"id\":8}}]" diff --git a/tests/Torrentarr.Infrastructure.Tests/Database/DatabaseRetryExtensionsTests.cs b/tests/Torrentarr.Infrastructure.Tests/Database/DatabaseRetryExtensionsTests.cs new file mode 100644 index 00000000..b46f3f92 --- /dev/null +++ b/tests/Torrentarr.Infrastructure.Tests/Database/DatabaseRetryExtensionsTests.cs @@ -0,0 +1,47 @@ +using FluentAssertions; +using Microsoft.Data.Sqlite; +using Microsoft.EntityFrameworkCore; +using Torrentarr.Infrastructure.Database; +using Xunit; + +namespace Torrentarr.Infrastructure.Tests.Database; + +public sealed class DatabaseRetryExtensionsTests +{ + [Theory] + [InlineData(5)] + [InlineData(6)] + public async Task WrappedBusyErrors_RetryLocalWriteWithoutRepeatingSubmission(int code) + { + var error = new DbUpdateException("Save failed", new SqliteException("Busy database", code)); + using var db = new FailingContext(error, 2); + (await db.SaveChangesWithRetryAsync()).Should().Be(1); + db.Attempts.Should().Be(3); + } + + [Fact] + public async Task PermanentErrors_AreNotRetried() + { + using var db = new FailingContext(new DbUpdateException("Invalid data", new SqliteException("Constraint", 19)), 5); + await FluentActions.Awaiting(() => db.SaveChangesWithRetryAsync()).Should().ThrowAsync(); + db.Attempts.Should().Be(1); + } + + [Fact] + public async Task Cancellation_StopsBackoffWithoutAnotherWrite() + { + using var cancellation = new CancellationTokenSource(); + using var db = new FailingContext(new DbUpdateException("Save failed", new SqliteException("Busy", 5)), 5); + var save = db.SaveChangesWithRetryAsync(cancellationToken: cancellation.Token); + cancellation.Cancel(); + await FluentActions.Awaiting(() => save).Should().ThrowAsync(); + db.Attempts.Should().Be(1); + } + + private sealed class FailingContext(Exception failure, int failures) : DbContext + { + public int Attempts { get; private set; } + public override Task SaveChangesAsync(CancellationToken cancellationToken = default) + => ++Attempts <= failures ? Task.FromException(failure) : Task.FromResult(1); + } +} diff --git a/tests/Torrentarr.Infrastructure.Tests/Database/SchemaParityTests.cs b/tests/Torrentarr.Infrastructure.Tests/Database/SchemaParityTests.cs index cafb8ae2..a3c2b90c 100644 --- a/tests/Torrentarr.Infrastructure.Tests/Database/SchemaParityTests.cs +++ b/tests/Torrentarr.Infrastructure.Tests/Database/SchemaParityTests.cs @@ -43,7 +43,7 @@ ORDER BY name { "albumfilesmodel", "albumqueuemodel", "artistfilesmodel", "authorfilesmodel", "bookfilesmodel", "bookqueuemodel", "episodefilesmodel", "episodequeuemodel", - "filesqueued", "moviequeuemodel", "moviesfilesmodel", "searchactivity", "seriesfilesmodel", "torrentarr_maintenance_state", "torrentarr_operational_issues", "torrentarr_sonarr_search_retries", "torrentlibrary", + "filesqueued", "moviequeuemodel", "moviesfilesmodel", "searchactivity", "seriesfilesmodel", "torrentarr_arr_operations", "torrentarr_arr_samples", "torrentarr_maintenance_state", "torrentarr_operational_issues", "torrentarr_sonarr_search_retries", "torrentarr_transfer_samples", "torrentlibrary", "trackfilesmodel" }; tableNames.Should().Equal(expected); diff --git a/tests/Torrentarr.Infrastructure.Tests/Services/ActiveWorkerSearchTests.cs b/tests/Torrentarr.Infrastructure.Tests/Services/ActiveWorkerSearchTests.cs index b068abb4..f340be36 100644 --- a/tests/Torrentarr.Infrastructure.Tests/Services/ActiveWorkerSearchTests.cs +++ b/tests/Torrentarr.Infrastructure.Tests/Services/ActiveWorkerSearchTests.cs @@ -14,6 +14,44 @@ namespace Torrentarr.Infrastructure.Tests.Services; public class ActiveWorkerSearchTests { + [Fact] + public async Task UnavailableTorrentCycle_WaitsWithoutAdvancingLastSuccess() + { + var instance = new ArrInstanceConfig { Managed = true, URI = "http://fixture", Category = "movies", RssSyncTimer = 0, RefreshDownloadsTimer = 0 }; + var reloader = new Mock(); reloader.Setup(r => r.ReloadConfig()).Returns(true); + using var harness = new Harness(instance, skipSync: false, reloader: reloader.Object); + var config = harness.Provider.GetRequiredService(); + config.TorrentClients["missing"] = new() { Type = "fake", Host = "fixture" }; + Mock.Get(harness.Provider.GetRequiredService()) + .Setup(p => p.ProcessTorrentsAsync("movies", It.IsAny>(), It.IsAny())) + .ThrowsAsync(new TorrentProcessingUnavailableException("Client unavailable")); + var client = new Mock(); + client.Setup(c => c.LoginAsync(It.IsAny())).ReturnsAsync(false); + var factory = new Mock(); + factory.SetupGet(f => f.Type).Returns("fake"); + factory.Setup(f => f.Create(It.IsAny(), It.IsAny())).Returns(client.Object); + using var manager = new QBittorrentConnectionManager(NullLogger.Instance, [factory.Object]); + var connectivity = new Mock(); connectivity.Setup(c => c.IsConnectedAsync(It.IsAny())).ReturnsAsync(true); + using var worker = new ArrWorkerService(NullLogger.Instance, config, instance, + new WorkerContext { InstanceName = "Radarr" }, harness.Provider, manager, connectivity.Object, new SearchYearCursor(), harness.Metrics); + foreach (var field in new[] { "_initialized", "_qbitSetupComplete" }) + typeof(ArrWorkerService).GetField(field, System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.NonPublic)!.SetValue(worker, true); + var previous = DateTimeOffset.UtcNow.AddHours(-1); + harness.Metrics.Torrent = new("Sleeping", previous, LastSuccess: previous); + using var timeout = new CancellationTokenSource(TimeSpan.FromSeconds(10)); + reloader.Setup(r => r.WaitForChangeAsync(0, TimeSpan.FromSeconds(5), It.IsAny())) + .Returns((long _, TimeSpan _, CancellationToken ct) => + { + harness.Metrics.Torrent.Phase.Should().Be("Waiting for client"); + harness.Metrics.Torrent.LastSuccess.Should().Be(previous); + timeout.Cancel(); + return Task.FromCanceled(ct); + }); + var method = typeof(ArrWorkerService).GetMethod("RunTorrentLoopAsync", System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.NonPublic)!; + await FluentActions.Awaiting(() => (Task)method.Invoke(worker, [timeout.Token])!).Should().ThrowAsync(); + reloader.Verify(r => r.WaitForChangeAsync(0, TimeSpan.FromSeconds(5), It.IsAny()), Times.Once()); + } + [Fact] public async Task TorrentSleep_WakesOnRecoveryWithoutConfigurationChange() { @@ -351,7 +389,8 @@ public async Task BusyLoops_AcknowledgeCompletedRevisionWithoutSleeping() }); await (Task)typeof(ArrWorkerService).GetMethod($"Run{loop}LoopAsync", flags)!.Invoke(worker, [stop.Token])!; calls.Should().Be(2); - (loop == "Torrent" ? harness.Metrics.Torrent : harness.Metrics.Search).LastSuccess.Should().NotBeNull(); + if (loop == "Torrent") harness.Metrics.Torrent.LastSuccess.Should().BeNull("no client inventory was due for inspection"); + else harness.Metrics.Search.LastSuccess.Should().NotBeNull(); (loop == "Torrent" ? harness.Metrics.TorrentRevision : harness.Metrics.SearchRevision).Should().Be(42); } harness.Metrics.Snapshot("Radarr", false).ConfigRevision.Should().Be(42); diff --git a/tests/Torrentarr.Infrastructure.Tests/Services/ArrActivityTests.cs b/tests/Torrentarr.Infrastructure.Tests/Services/ArrActivityTests.cs new file mode 100644 index 00000000..ef9d4bb2 --- /dev/null +++ b/tests/Torrentarr.Infrastructure.Tests/Services/ArrActivityTests.cs @@ -0,0 +1,205 @@ +using System.Net; +using FluentAssertions; +using Microsoft.Data.Sqlite; +using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Logging.Abstractions; +using Newtonsoft.Json.Linq; +using RestSharp; +using Torrentarr.Core.Configuration; +using Torrentarr.Core.Interfaces; +using Torrentarr.Infrastructure.Database; +using Torrentarr.Infrastructure.Database.Models; +using Torrentarr.Infrastructure.Services; +using Xunit; + +namespace Torrentarr.Infrastructure.Tests.Services; + +public sealed class ArrActivityTests : IDisposable +{ + private readonly SqliteConnection _connection = new("Data Source=:memory:"); + private readonly ServiceProvider _services; + private static readonly DateTimeOffset Now = DateTimeOffset.Parse("2026-11-04T12:00:00Z"); + private readonly ArrInstanceConfig _settings = new() { Type = "sonarr", URI = "http://fixture", Managed = true }; + private readonly TorrentarrConfig _config = new(); + public ArrActivityTests() + { + _connection.Open(); + var services = new ServiceCollection(); + services.AddDbContext(o => o.UseSqlite(_connection)); + _services = services.BuildServiceProvider(); + using var scope = _services.CreateScope(); + scope.ServiceProvider.GetRequiredService().Database.EnsureCreated(); + _config.ArrInstances["Sonarr-one"] = _settings; + } + private TorrentarrDbContext Db() => new(new DbContextOptionsBuilder().UseSqlite(_connection).Options); + private ArrActivityOperation Operation(int id, string kind = "search", string hash = "") => new() + { + Instance = "Sonarr-one", + CommandId = id, + Type = "sonarr", + Kind = kind, + DownloadId = hash, + Server = ArrActivityTracking.ServerKey(_settings), + SubmittedAt = Now.AddMinutes(-5).ToUnixTimeSeconds() + }; + public void Dispose() { _services.Dispose(); _connection.Dispose(); } + + [Fact] + public async Task Tracking_AttributesNestedScopesAndPersistsWithoutChangingMainResponse() + { + await using var db = Db(); + var request = new RestRequest("/api/v5/command", Method.Post); + var response = new RestResponse { StatusCode = HttpStatusCode.Accepted, IsSuccessStatusCode = true, ResponseStatus = ResponseStatus.Completed, Content = "{\"id\":42}" }; + using (ArrActivityTracking.ForClient("client-one")) + using (ArrActivityTracking.Begin(db, NullLogger.Instance, "Sonarr-one", _settings, "import", "hash")) + { + await ArrActivityTracking.RecordAcceptedAsync(request, response, default); + await ArrActivityTracking.RecordAcceptedAsync(request, response, default); + } + response.Content = "{\"id\":43}"; + await ArrActivityTracking.RecordAcceptedAsync(request, response, default); + var operation = await db.ArrActivityOperations.SingleAsync(); + operation.CommandId.Should().Be(42); operation.ClientInstance.Should().Be("client-one"); + operation.DownloadId.Should().Be("HASH"); operation.Kind.Should().Be("import"); + db.Database.ExecuteSqlRaw("DROP TABLE torrentarr_arr_operations"); + using (ArrActivityTracking.Begin(db, NullLogger.Instance, "Sonarr-one", _settings, "search")) + await ArrActivityTracking.RecordAcceptedAsync(request, response, default); + response.IsSuccessful.Should().BeTrue(); + } + + [Theory] + [InlineData("started", null, false)] + [InlineData("completed", "successful", true)] + [InlineData("completed", "unsuccessful", false)] + [InlineData("failed", null, false)] + [InlineData("cancelled", null, false)] + [InlineData("completed", "unknown", false)] + [InlineData("completed", null, false)] + [InlineData("completed", "1", true)] + public async Task Search_RequiresSuccessfulCommandCompletion(string status, string? result, bool completed) + { + await using var db = Db(); var operation = Operation(1); db.Add(operation); await db.SaveChangesAsync(); + var commands = new JArray(new JObject { ["id"] = 1, ["status"] = status, ["result"] = result }); + await ArrActivityCollector.ReconcileAsync(db, [operation], commands, [], Now); + await db.SaveChangesAsync(); + operation.ConfirmedAt.HasValue.Should().Be(completed); + await ArrActivityCollector.ReconcileAsync(db, [operation], commands, [], Now.AddMinutes(1)); + if (completed) operation.ConfirmedAt.Should().Be(Now.ToUnixTimeSeconds()); + } + + [Fact] + public async Task Imports_RequireNewCorrelatedPositiveEvidenceAndDeduplicateBatchRetries() + { + await using var db = Db(); var first = Operation(1, "import", "HASH"); var retry = Operation(2, "import", "HASH"); + db.AddRange(first, retry); await db.SaveChangesAsync(); + var commands = JArray.Parse("[{\"id\":1,\"status\":\"completed\"},{\"id\":2,\"status\":\"completed\"}]"); + await ArrActivityCollector.ReconcileAsync(db, [first, retry], commands, [], Now); + first.ConfirmedAt.Should().BeNull(); + var history = new JArray( + new JObject { ["id"] = 5, ["downloadId"] = "OTHER", ["eventType"] = "downloadFolderImported", ["date"] = Now.ToString("O") }, + new JObject { ["id"] = 6, ["downloadId"] = "HASH", ["eventType"] = "grabbed", ["date"] = Now.ToString("O") }, + new JObject { ["id"] = 7, ["downloadId"] = "HASH", ["eventType"] = "downloadFolderImported", ["date"] = Now.AddDays(-1).ToString("O") }); + await ArrActivityCollector.ReconcileAsync(db, [first, retry], commands, history, Now); + first.ConfirmedAt.Should().BeNull(); + history.Add(new JObject { ["id"] = 8, ["downloadId"] = "hash", ["eventType"] = "downloadFolderImported", ["date"] = Now.ToString("O") }); + history.Add(new JObject { ["id"] = 9, ["downloadId"] = "HASH", ["eventType"] = "downloadFolderImported", ["date"] = Now.ToString("O") }); + await ArrActivityCollector.ReconcileAsync(db, [first, retry], commands, history, Now); + await db.SaveChangesAsync(); first.ConfirmedAt.Should().Be(Now.ToUnixTimeSeconds()); + retry.Outcome.Should().Be("duplicate"); retry.ConfirmedAt.Should().BeNull(); + } + + [Theory] + [InlineData("radarr", "downloadFolderImported", true)] + [InlineData("sonarr", "downloadFolderImported", true)] + [InlineData("lidarr", "downloadImported", true)] + [InlineData("readarr", "downloadImported", true)] + [InlineData("lidarr", "trackFileImported", false)] + [InlineData("readarr", "bookImportIncomplete", false)] + public void Imports_UseTypeSpecificBatchEvidence(string type, string eventType, bool expected) + { + var operation = Operation(1, "import", "HASH"); operation.Type = type; + var evidence = new JObject { ["id"] = 2, ["downloadId"] = "HASH", ["eventType"] = eventType, ["date"] = Now.ToString("O") }; + ArrActivityCollector.PositiveImport(evidence, operation).Should().Be(expected); + } + + [Fact] + public async Task Collector_RestartsWithPendingWorkAndSamplesFreshHeartbeatsOncePerMinute() + { + await using (var db = Db()) { db.Add(Operation(1)); await db.SaveChangesAsync(); } + var statuses = new Dictionary { ["Sonarr-one"] = new() { IsAlive = true, LastHeartbeat = Now.UtcDateTime, QueueCount = 7 } }; + var calls = 0; + Task Read(ArrInstanceConfig _, string resource, CancellationToken ct) { calls++; return Task.FromResult(JArray.Parse("[{\"id\":1,\"status\":\"completed\",\"result\":\"successful\"}]")); } + var collector = new ArrActivityCollector(_services.GetRequiredService(), _services, _config, NullLogger.Instance); + await collector.CollectAsync(Now, statuses, Read); + await collector.CollectAsync(Now.AddSeconds(10), statuses, Read); + calls.Should().Be(1); + await collector.CollectAsync(Now.AddMinutes(1), statuses, Read); + await using var check = Db(); + (await check.ArrActivityOperations.SingleAsync()).Outcome.Should().Be("completed"); + var rows = await check.ArrActivitySamples.OrderBy(s => s.Minute).ToListAsync(); + rows[0].QueueDepth.Should().Be(7); rows[0].CompletionObserved.Should().BeTrue(); + rows[1].QueueDepth.Should().BeNull(); rows[1].CompletionObserved.Should().BeFalse(); + } + + [Fact] + public async Task Query_AveragesQueuesSumsConfirmedEventsAndPreservesUnknownAndZero() + { + await using var db = Db(); var minute = Now.ToUnixTimeSeconds() / 60; + db.AddRange(new ArrActivitySample { Instance = "Sonarr-one", Type = "sonarr", Minute = minute - 5, QueueDepth = 4, CompletionObserved = true }, + new ArrActivitySample { Instance = "Sonarr-one", Type = "sonarr", Minute = minute - 4, QueueDepth = 8, CompletionObserved = true }); + var operation = Operation(1); operation.Outcome = "completed"; operation.ConfirmedAt = Now.AddMinutes(-2).ToUnixTimeSeconds(); db.Add(operation); + await db.SaveChangesAsync(); + var history = await ArrActivityApi.QueryAsync(db, _config, "24h", null, Now); + var point = history.Series.Single().Points.Last(); + point.QueueDepth.Should().Be(6); point.QueueSamples.Should().Be(2); point.ExpectedSamples.Should().Be(5); + point.SearchesCompleted.Should().Be(1); point.ImportsConfirmed.Should().Be(0); + history.Series.Single().Points.First().SearchesCompleted.Should().BeNull(); + foreach (var range in new[] { "1h", "7d" }) + (await ArrActivityApi.QueryAsync(db, _config, range, null, Now)).Series.Single().Points.Count.Should().Be(range == "1h" ? 60 : 336); + } + + [Fact] + public async Task Collector_PrunesExpiredHistoryAndMigrationIsRepeatable() + { + await using var db = Db(); + db.Add(new ArrActivitySample { Instance = "old", Type = "sonarr", Minute = Now.AddDays(-8).ToUnixTimeSeconds() / 60 }); + var old = Operation(10); old.SubmittedAt = Now.AddDays(-8).ToUnixTimeSeconds(); db.Add(old); await db.SaveChangesAsync(); + ManualSqliteMigrations.Apply(db); ManualSqliteMigrations.Apply(db); + var collector = new ArrActivityCollector(_services.GetRequiredService(), _services, _config, NullLogger.Instance); + await collector.CollectAsync(Now, new Dictionary(), (_, _, _) => throw new IOException("must not poll expired operations")); + db.ChangeTracker.Clear(); (await db.ArrActivitySamples.CountAsync(s => s.Instance == "old")).Should().Be(0); + (await db.ArrActivityOperations.CountAsync()).Should().Be(0); + } + + [Fact] + public async Task Collector_FailedEvidenceLeavesGapAndDoesNotBlockOtherWorkers() + { + await using var db = Db(); db.Add(Operation(1)); await db.SaveChangesAsync(); + _config.ArrInstances["Sonarr-two"] = new() { Type = "sonarr", URI = "http://fixture-two" }; + var statuses = new Dictionary + { + ["Sonarr-one"] = new() { IsAlive = true, LastHeartbeat = Now.UtcDateTime, QueueCount = 7 }, + ["Sonarr-two"] = new() { IsAlive = true, LastHeartbeat = Now.UtcDateTime, QueueCount = 3 } + }; + var collector = new ArrActivityCollector(_services.GetRequiredService(), _services, _config, NullLogger.Instance); + await collector.CollectAsync(Now, statuses, (_, _, _) => throw new IOException("unavailable")); + var rows = await db.ArrActivitySamples.OrderBy(s => s.Instance).ToListAsync(); + rows[0].QueueDepth.Should().Be(7); rows[0].CompletionObserved.Should().BeFalse(); + rows[1].QueueDepth.Should().Be(3); rows[1].CompletionObserved.Should().BeTrue(); + (await db.ArrActivityOperations.SingleAsync()).Outcome.Should().Be("pending"); + } + + [Fact] + public async Task Migration_AddsActivityTablesToExistingDatabaseWithoutLosingData() + { + await using var db = Db(); + db.Database.ExecuteSqlRaw("DROP TABLE torrentarr_arr_samples"); + db.Database.ExecuteSqlRaw("DROP TABLE torrentarr_arr_operations"); + ManualSqliteMigrations.Apply(db); ManualSqliteMigrations.Apply(db); + db.Add(Operation(1)); db.Add(new ArrActivitySample { Instance = "Sonarr-one", Type = "sonarr", Minute = 123 }); + await db.SaveChangesAsync(); ManualSqliteMigrations.Apply(db); + (await db.ArrActivityOperations.SingleAsync()).CommandId.Should().Be(1); + (await db.ArrActivitySamples.SingleAsync()).Minute.Should().Be(123); + } +} diff --git a/tests/Torrentarr.Infrastructure.Tests/Services/AutomationLoggingTests.cs b/tests/Torrentarr.Infrastructure.Tests/Services/AutomationLoggingTests.cs index 8f6a307d..1db6c8f6 100644 --- a/tests/Torrentarr.Infrastructure.Tests/Services/AutomationLoggingTests.cs +++ b/tests/Torrentarr.Infrastructure.Tests/Services/AutomationLoggingTests.cs @@ -67,10 +67,10 @@ public async Task TrackerActions_LogOnlyHost_AndReportAcceptance(string tracker) } [Fact] - public async Task RemovalProtection_ExplainsBlockersAtInformation() + public async Task RemovalProtection_ExplainsEvaluationAtDebug() { await using var db = new TorrentarrDbContext(new DbContextOptionsBuilder().UseInMemoryDatabase(Guid.NewGuid().ToString()).Options); - var logger = new ActivityLogger(); + var logger = new ActivityLogger(LogLevel.Debug); var registry = new Mock(); var service = new SeedingService(logger, db, new TorrentarrConfig(), registry.Object); var decision = await service.EvaluateRemovalAsync(new() { Hash = "protected", QBitInstanceName = "seedbox", Name = "Protected item" }, TorrentRemovalIntent.SeedingLimit); @@ -85,7 +85,7 @@ public async Task DatabaseChanges_AreLoggedAfterSuccessfulSave_AndNeverAfterFail await connection.OpenAsync(); await using var db = new TorrentarrDbContext(new DbContextOptionsBuilder().UseSqlite(connection).Options); await db.Database.EnsureCreatedAsync(); - var logger = new ActivityLogger(); + var logger = new ActivityLogger(LogLevel.Debug); var movie = new MoviesFilesModel { ArrInstance = "Radarr", ArrId = 17, TmdbId = 17, Title = "SaveChanges movie", Reason = "Missing" }; db.Movies.Add(movie); await db.SaveChangesWithRetryAsync(logger); @@ -127,7 +127,7 @@ public async Task SearchEligibility_LogsExcludedAndSelectedItems_WithoutChanging var executor = new Mock(); executor.Setup(e => e.ExecuteSearchesAsync("Arr-test", It.IsAny>(), It.IsAny())) .ReturnsAsync(new SearchResult()); - var logger = new ActivityLogger(); + var logger = new ActivityLogger(LogLevel.Debug); var service = new ArrMediaService(logger, db, config, executor.Object, new ArrSyncService(NullLogger.Instance, config, db, new())); await service.SearchMissingMediaAsync("media"); @@ -139,7 +139,7 @@ public async Task SearchEligibility_LogsExcludedAndSelectedItems_WithoutChanging [Fact] public void SonarrEligibility_LogsSpecialsAndUnairedDecisions() { - var logger = new ActivityLogger(); + var logger = new ActivityLogger(LogLevel.Debug); var candidates = SonarrSearchScope.Candidates([ new() { ArrInstance = "Sonarr", ArrId = 1, SeriesTitle = "Special", SeasonNumber = 0, Monitored = true }, new() { ArrInstance = "Sonarr", ArrId = 2, SeriesTitle = "Future", SeasonNumber = 1, Monitored = true, AirDateUtc = DateTime.UtcNow.AddDays(1) } diff --git a/tests/Torrentarr.Infrastructure.Tests/Services/LogRetentionTests.cs b/tests/Torrentarr.Infrastructure.Tests/Services/LogRetentionTests.cs new file mode 100644 index 00000000..7d5d2bd3 --- /dev/null +++ b/tests/Torrentarr.Infrastructure.Tests/Services/LogRetentionTests.cs @@ -0,0 +1,51 @@ +using FluentAssertions; +using Serilog.Events; +using Serilog.Parsing; +using Torrentarr.Infrastructure.Logging; +using Xunit; + +namespace Torrentarr.Infrastructure.Tests.Services; + +public sealed class LogRetentionTests +{ + [Theory] + [InlineData("all-20261104.log", true, 3)] + [InlineData("all-20261104_001.log", true, 3)] + [InlineData("all-20261104_001.log", false, 1)] + public void Search_IncludesNumberedSizeRotationsWithoutOtherStreams(string selected, bool includeRotated, int expected) + { + var directory = Directory.CreateTempSubdirectory("log-search-rotation-"); + try + { + foreach (var name in new[] { "all-20261104.log", "all-20261104_001.log", "all-20261104.log.1", "all-20261103.log", "worker-one-20261104.log" }) + File.WriteAllText(Path.Combine(directory.FullName, name), "marker\n"); + var result = Torrentarr.Infrastructure.Endpoints.LogFileApi.Search(directory.FullName, selected, + "marker", null, null, includeRotated ? "true" : "false", 10, 0); + var value = ((Microsoft.AspNetCore.Http.IValueHttpResult)result).Value; + var json = System.Text.Json.JsonSerializer.SerializeToElement(value); + json.GetProperty("matches").GetArrayLength().Should().Be(expected); + } + finally { directory.Delete(recursive: true); } + } + + [Fact] + public void SizeRotation_BoundsHistoryAndKeepsAggregateAndServiceRecordsConsistent() + { + var directory = Directory.CreateTempSubdirectory("log-retention-"); + try + { + using (var sink = new ServiceLogSink(directory.FullName, "Host", 256)) + for (var index = 0; index < 40; index++) + sink.Emit(new LogEvent(DateTimeOffset.Now, LogEventLevel.Information, null, + new MessageTemplateParser().Parse($"event-{index:D2}-{new string('x', 120)}"), [])); + var aggregate = Directory.GetFiles(directory.FullName, "all-*.log"); + var service = Directory.GetFiles(directory.FullName, "torrentarr-*.log"); + aggregate.Should().HaveCount(8); + service.Should().HaveCount(8); + aggregate.Concat(service).Should().OnlyContain(path => new FileInfo(path).Length <= 512, + "one complete event may cross the size rotation threshold"); + aggregate.SelectMany(File.ReadAllLines).Should().BeEquivalentTo(service.SelectMany(File.ReadAllLines)); + } + finally { directory.Delete(recursive: true); } + } +} diff --git a/tests/Torrentarr.Infrastructure.Tests/Services/MaintenanceSafetyTests.cs b/tests/Torrentarr.Infrastructure.Tests/Services/MaintenanceSafetyTests.cs index eab37a92..ad0464c8 100644 --- a/tests/Torrentarr.Infrastructure.Tests/Services/MaintenanceSafetyTests.cs +++ b/tests/Torrentarr.Infrastructure.Tests/Services/MaintenanceSafetyTests.cs @@ -958,6 +958,22 @@ public async Task Rollback_DoesNotOverwriteRecreatedSource() Directory.EnumerateFiles(Path.Combine(_root, ".recycle"), "*", SearchOption.AllDirectories).Should().ContainSingle(); } + [Fact] + public async Task RecyclingWithoutMappings_ExplainsClientPathAndDoesNotDeleteContent() + { + AddTorrent(); + ClientConfig.Maintenance.PathMappings.Clear(); + var result = await _deletion.RemoveTorrentAsync("one", "abc", true); + result.Success.Should().BeFalse(); + result.Error.Should().Contain("Recycling blocked").And.Contain("Client 'one'") + .And.Contain("/downloads/movie.mkv").And.Contain("Maintenance.PathMappings") + .And.Contain("mounted and accessible").And.Contain("Identical client/local paths"); + _client.Verify(c => c.DeleteTorrentsAsync(It.IsAny>(), It.IsAny(), It.IsAny()), Times.Never()); + File.Exists(Path.Combine(_root, "movie.mkv")).Should().BeTrue(); + ClientConfig.Maintenance.PathMappings.Add(new() { ClientPath = "/downloads", LocalPath = _root }); + (await _deletion.RemoveTorrentAsync("one", "abc", true)).Success.Should().BeTrue(); + } + [Fact] public async Task DisabledRecycle_MaintenanceUsesNativeDeletion() { diff --git a/tests/Torrentarr.Infrastructure.Tests/Services/OperationalIssueMonitorTests.cs b/tests/Torrentarr.Infrastructure.Tests/Services/OperationalIssueMonitorTests.cs index 92b2a51f..2dea5d6a 100644 --- a/tests/Torrentarr.Infrastructure.Tests/Services/OperationalIssueMonitorTests.cs +++ b/tests/Torrentarr.Infrastructure.Tests/Services/OperationalIssueMonitorTests.cs @@ -12,6 +12,82 @@ namespace Torrentarr.Infrastructure.Tests.Services; public sealed class OperationalIssueMonitorTests { + [Theory] + [InlineData(false)] + [InlineData(true)] + public async Task MigrationAndMissingMappings_CreateIssuesBeforeProcessing_AndResolveAfterRestart(bool removeClient) + { + using var root = new TemporaryDirectory(); + using var db = new Torrentarr.Infrastructure.Database.TorrentarrDbContext(new DbContextOptionsBuilder() + .UseSqlite("Data Source=:memory:").Options); + await db.Database.OpenConnectionAsync(); + await db.Database.EnsureCreatedAsync(); + var config = new TorrentarrConfig(); + config.TorrentClients["recycling"] = new() + { + Host = "localhost", + Migration = new() { Notices = ["Legacy monitor ownership is ambiguous; review FreeSpace."] }, + Maintenance = new() + { + RecycleBin = new() { Enabled = true }, + PathMappings = [new() { ClientPath = "/downloads", LocalPath = root.Path }] + } + }; + config.QBitInstances["qBit-other"] = new() { Host = "localhost" }; + await using var services = new ServiceCollection().AddSingleton(db).AddSingleton(config) + .AddSingleton().BuildServiceProvider(); + using var reporter = new OperationalIssueReporter(services.GetRequiredService(), NullLogger.Instance, config); + using var monitor = new OperationalIssueMonitor(services.GetRequiredService(), reporter, config, NullLogger.Instance); + await monitor.InspectAsync(default); + await monitor.InspectAsync(default); + var active = await db.OperationalIssues.AsNoTracking().Where(i => i.ResolvedAt == null).ToListAsync(); + active.Should().HaveCount(2).And.OnlyContain(i => i.Target == "config" && i.Occurrences == 2); + active.Single(i => i.Code == "config.storage").ClientInstance.Should().Be("qBit-other"); + active.Single(i => i.Code == "config.storage").Remediation.Should().Contain("Maintenance.PathMappings"); + active.Single(i => i.Code == "config.migration").Remediation.Should().Contain("Migration.Notices"); + config.TorrentClients["recycling"].Migration.Notices.Clear(); + if (removeClient) config.QBitInstances.Clear(); + else config.QBitInstances["qBit-other"].Maintenance.PathMappings.Add(new() { ClientPath = root.Path, LocalPath = root.Path }); + using var restarted = new OperationalIssueMonitor(services.GetRequiredService(), reporter, config, NullLogger.Instance); + await restarted.InspectAsync(default); + var history = await db.OperationalIssues.AsNoTracking().ToListAsync(); + history.Should().HaveCount(2).And.OnlyContain(i => i.ResolvedAt != null); + } + + [Fact] + public async Task InvalidOrMissingConfiguration_DirectsUserToConfigAndResolvesAfterCorrection() + { + using var root = new TemporaryDirectory(); + var config = new TorrentarrConfig(); + config.TorrentClients["client"] = new() + { + TorrentScanIntervalSeconds = 7, + Maintenance = new() + { + RecycleBin = new() { Enabled = true }, + PathMappings = [new() { ClientPath = "/downloads", LocalPath = System.IO.Path.Combine(root.Path, "missing-mount") }] + } + }; + var reports = new List(); + var resolved = new List(); + var reporter = new Mock(); + reporter.Setup(r => r.ReportAsync(It.IsAny(), It.IsAny())) + .Callback((r, _) => reports.Add(r)).Returns(Task.CompletedTask); + reporter.Setup(r => r.ResolveAsync(It.IsAny(), It.IsAny())) + .Callback((r, _) => resolved.Add(r)).Returns(Task.CompletedTask); + await using var services = new ServiceCollection().AddSingleton().BuildServiceProvider(); + using var monitor = new OperationalIssueMonitor(services.GetRequiredService(), reporter.Object, config, NullLogger.Instance); + await monitor.InspectAsync(default); + reports.Select(r => r.Code).Should().BeEquivalentTo(["config.validation", "config.client", "config.storage"]); + reports.Should().OnlyContain(r => r.Target == "config" && r.Remediation.Contains("Open Config")); + reports.Single(r => r.Code == "config.validation").Impact.Should().Contain("TorrentScanIntervalSeconds"); + config.TorrentClients["client"].Host = "localhost"; + config.TorrentClients["client"].TorrentScanIntervalSeconds = 30; + config.TorrentClients["client"].Maintenance.PathMappings[0].LocalPath = root.Path; + await monitor.InspectAsync(default); + resolved.Select(r => r.Code).Should().BeEquivalentTo(reports.Select(r => r.Code)); + } + [Theory] [InlineData(false, false)] [InlineData(true, false)] @@ -251,4 +327,10 @@ public async Task PendingConfigurationNeverClearsAnError_AndRecoveryRequiresSucc state = state with { State = "applied", Error = null }; online = true; low = false; await monitor.InspectAsync(default); resolved.Select(r => r.Code).Should().Contain(["config.apply", "network.internet", "storage.reserve"]); } + private sealed class TemporaryDirectory : IDisposable + { + public string Path { get; } = Directory.CreateTempSubdirectory("migration-issues-").FullName; + public void Dispose() => Directory.Delete(Path, recursive: true); + } + } diff --git a/tests/Torrentarr.Infrastructure.Tests/Services/ProtectionLimitTests.cs b/tests/Torrentarr.Infrastructure.Tests/Services/ProtectionLimitTests.cs index 2f84aa2e..999bd370 100644 --- a/tests/Torrentarr.Infrastructure.Tests/Services/ProtectionLimitTests.cs +++ b/tests/Torrentarr.Infrastructure.Tests/Services/ProtectionLimitTests.cs @@ -14,25 +14,18 @@ namespace Torrentarr.Infrastructure.Tests.Services; public sealed class ProtectionLimitTests { [Theory] - [InlineData(false, false, false, false)] - [InlineData(true, false, false, false)] - [InlineData(false, true, false, false)] - [InlineData(false, false, true, false)] - [InlineData(false, false, false, true)] - public async Task ProtectedLimits_ClearAllThree_AndPreserveSubsequentUserChanges(bool userChanged, bool revisedWhileProtected, bool clearRejected, bool restoreRejected) + [InlineData(false, false, false)] + [InlineData(true, false, false)] + [InlineData(false, true, false)] + [InlineData(false, false, true)] + public async Task Hnr_DoesNotOverrideLimits_AndRestoresOnlyOwnedLegacyValues(bool userChanged, bool replacedTorrent, bool restoreRejected) { using var db = new TorrentarrDbContext(new DbContextOptionsBuilder().UseSqlite("Data Source=:memory:").Options); db.Database.OpenConnection(); db.Database.EnsureCreated(); var config = new TorrentarrConfig(); - config.TorrentClients["client"] = new() - { - Trackers = [new() { Uri = "tracker.example", Protection = new() { - MinimumSeedHours = 48, AllowClientRatioClearance = true, AccountingGraceMinutes = 0, AdditionalSeedHours = 0 - } }] - }; + config.TorrentClients["client"] = new() { Trackers = [new() { Uri = "tracker.example", Protection = new() { MinimumSeedHours = 48 } }] }; var client = new Mock(); client.SetupGet(c => c.Capabilities).Returns(TorrentClientCapabilities.QBittorrent); - client.Setup(c => c.SetShareLimitsAsync(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny())).ReturnsAsync(true); var registry = new Mock(); registry.Setup(r => r.GetClient("client")).Returns(client.Object); var service = new SeedingService(NullLogger.Instance, db, config, registry.Object); @@ -41,51 +34,36 @@ public async Task ProtectedLimits_ClearAllThree_AndPreserveSubsequentUserChanges ClientInstanceId = "client", Hash = "hash", AddedOn = 1, - Progress = 1, IsPrivate = true, - Ratio = 0, + Progress = 1, RatioLimit = 1, SeedingTimeLimit = 60, InactiveSeedingTimeLimit = 30 }; List trackers = [new() { Url = "https://tracker.example/announce", Status = 2 }]; - var clearCalls = 0; - client.Setup(c => c.SetShareLimitsAsync("hash", -1, -1, -1, It.IsAny())) - .ReturnsAsync(() => !clearRejected || ++clearCalls > 1); await service.ReconcileProtectionLimitsAsync(torrent, trackers, default); - client.Verify(c => c.SetShareLimitsAsync("hash", -1, -1, -1, It.IsAny()), Times.Once); - if (clearRejected) - { - torrent.RatioLimit.Should().Be(1); - await service.ReconcileProtectionLimitsAsync(torrent, trackers, default); - clearCalls.Should().Be(2); - } - if (revisedWhileProtected) + client.Verify(c => c.SetShareLimitsAsync(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny()), Times.Never()); + torrent.RatioLimit.Should().Be(1); + var key = MaintenancePersistence.Key("client", "hash", "native-limits"); + MaintenancePersistence.Write(db, key, new ProtectionObservation { - torrent.RatioLimit = 2; torrent.SeedingTimeLimit = 120; torrent.InactiveSeedingTimeLimit = 45; - await service.ReconcileProtectionLimitsAsync(torrent, trackers, default); - } - var ratio = revisedWhileProtected ? 2 : 1; - var seedTime = revisedWhileProtected ? 120 : 60; - var inactiveTime = revisedWhileProtected ? 45 : 30; - var restoreCalls = 0; - client.Setup(c => c.SetShareLimitsAsync("hash", ratio, seedTime, inactiveTime, It.IsAny())) - .ReturnsAsync(() => !restoreRejected || ++restoreCalls > 1); - torrent.Ratio = 1; - torrent.RatioLimit = userChanged ? 2 : -1; - torrent.SeedingTimeLimit = -1; torrent.InactiveSeedingTimeLimit = -1; + AddedOn = replacedTorrent ? 2 : 1, + PreviousRatioLimit = 1, + PreviousTimeLimit = 60, + PreviousInactiveTimeLimit = 30 + }); + torrent.RatioLimit = userChanged ? 2 : -1; torrent.SeedingTimeLimit = -1; torrent.InactiveSeedingTimeLimit = -1; + client.Setup(c => c.SetShareLimitsAsync("hash", 1, 60, 30, It.IsAny())).ReturnsAsync(!restoreRejected); await service.ReconcileProtectionLimitsAsync(torrent, trackers, default); - client.Verify(c => c.SetShareLimitsAsync("hash", ratio, seedTime, inactiveTime, It.IsAny()), userChanged ? Times.Never() : Times.Once()); + client.Verify(c => c.SetShareLimitsAsync("hash", 1, 60, 30, It.IsAny()), userChanged || replacedTorrent ? Times.Never() : Times.Once()); + var pending = MaintenancePersistence.Read(db, key).PreviousRatioLimit; if (restoreRejected) { - var key = MaintenancePersistence.Key("client", "hash", "native-limits"); - MaintenancePersistence.Read(db, key).PreviousRatioLimit.Should().Be(ratio); + pending.Should().Be(1); + client.Setup(c => c.SetShareLimitsAsync("hash", 1, 60, 30, It.IsAny())).ReturnsAsync(true); await service.ReconcileProtectionLimitsAsync(torrent, trackers, default); - restoreCalls.Should().Be(2); MaintenancePersistence.Read(db, key).PreviousRatioLimit.Should().BeNull(); } - // A completed restore (or a user edit) releases ownership and cannot be replayed. - await service.ReconcileProtectionLimitsAsync(torrent, trackers, default); - client.Verify(c => c.SetShareLimitsAsync("hash", ratio, seedTime, inactiveTime, It.IsAny()), userChanged ? Times.Never() : Times.Exactly(restoreRejected ? 2 : 1)); + else pending.Should().BeNull(); } } diff --git a/tests/Torrentarr.Infrastructure.Tests/Services/QbitOverviewBuilderTests.cs b/tests/Torrentarr.Infrastructure.Tests/Services/QbitOverviewBuilderTests.cs index 469bccb0..800e6740 100644 --- a/tests/Torrentarr.Infrastructure.Tests/Services/QbitOverviewBuilderTests.cs +++ b/tests/Torrentarr.Infrastructure.Tests/Services/QbitOverviewBuilderTests.cs @@ -30,4 +30,25 @@ public async Task Overview_UsesEffectiveOwnerSubcategorySetting(bool? arrOverrid category["torrentCount"]!.Value().Should().Be(expected); category["totalSize"]!.Value().Should().Be(expected == 2 ? 30 : 10); } + + [Fact] + public async Task Overview_SummarizesBeforeCapAndDeduplicatesOverlappingCategories() + { + var config = new TorrentarrConfig(); + config.TorrentClients["one"] = new() { Host = "fixture", MatchSubcategories = true, ManagedCategories = ["movies", "movies/4k"] }; + var torrents = Enumerable.Range(0, 650).Select(i => new TorrentInfo + { Hash = $"hash{i}", Category = "movies/4k", Size = 10, State = i < 600 ? "stalledDL" : "pausedUP" }).ToList(); + var client = new Mock(); + client.Setup(c => c.GetTorrentsAsync(It.IsAny(), It.IsAny(), It.IsAny())).ReturnsAsync(torrents); + var registry = new Mock(); + registry.Setup(r => r.GetClient("one")).Returns(client.Object); + var result = JObject.FromObject(await QbitOverviewBuilder.BuildAsync(config, registry.Object, null)); + result["categories"]!.First()!["torrents"]!.Count().Should().Be(500); + result["categories"]!.First()!["torrentsTruncated"]!.Value().Should().BeTrue(); + var summary = result["summaries"]!.Single(); + summary["torrentCount"]!.Value().Should().Be(650); + summary["totalSize"]!.Value().Should().Be(6500); + summary["downloading"]!.Value().Should().Be(600); + summary["stopped"]!.Value().Should().Be(50); + } } diff --git a/tests/Torrentarr.Infrastructure.Tests/Services/SeedingServiceTests.cs b/tests/Torrentarr.Infrastructure.Tests/Services/SeedingServiceTests.cs index 30bb4c45..3c8d47df 100644 --- a/tests/Torrentarr.Infrastructure.Tests/Services/SeedingServiceTests.cs +++ b/tests/Torrentarr.Infrastructure.Tests/Services/SeedingServiceTests.cs @@ -19,7 +19,7 @@ namespace Torrentarr.Infrastructure.Tests.Services; /// public class SeedingServiceTests { - private static SeedingService CreateService(TorrentarrConfig? config = null, StalledUploadTracker? stalled = null) + private static SeedingService CreateService(TorrentarrConfig? config = null, StalledUploadTracker? stalled = null, TorrentInfo? observed = null) { var options = new DbContextOptionsBuilder() .UseInMemoryDatabase(Guid.NewGuid().ToString()) @@ -27,6 +27,8 @@ private static SeedingService CreateService(TorrentarrConfig? config = null, Sta var dbContext = new TorrentarrDbContext(options); var client = new Mock(); client.SetupGet(c => c.Capabilities).Returns(TorrentClientCapabilities.QBittorrent); + client.Setup(c => c.GetTorrentsAsync(It.IsAny(), It.IsAny(), It.IsAny())) + .ReturnsAsync(observed == null ? [] : [observed]); client.Setup(c => c.GetTorrentTrackersAsync(It.IsAny(), It.IsAny())) .ReturnsAsync([new() { Url = "https://tracker.example/announce", Status = 2 }]); var registry = new Mock(); @@ -35,6 +37,231 @@ private static SeedingService CreateService(TorrentarrConfig? config = null, Sta } + [Fact] + public async Task ProtectedTorrent_StillAppliesConfiguredTrackerRemoval() + { + var config = new TorrentarrConfig(); + config.TorrentClients["client"] = new() + { + Trackers = [new() { Uri = "tracker.example", RemoveIfExists = true, + HitAndRunMode = "and", MinSeedRatio = 99 }] + }; + using var db = new TorrentarrDbContext(new DbContextOptionsBuilder().UseInMemoryDatabase(Guid.NewGuid().ToString()).Options); + var torrent = new TorrentInfo { ClientInstanceId = "client", Hash = "hash", AddedOn = 1, Progress = 1, IsPrivate = true }; + var client = new Mock(); + client.SetupGet(c => c.Capabilities).Returns(TorrentClientCapabilities.QBittorrent); + client.Setup(c => c.GetTorrentsAsync(It.IsAny(), It.IsAny(), It.IsAny())).ReturnsAsync([torrent]); + client.Setup(c => c.GetTorrentTrackersAsync("hash", It.IsAny())).ReturnsAsync([new() { Url = "https://tracker.example/announce", Status = 2 }]); + client.Setup(c => c.RemoveTrackersAsync("hash", It.IsAny>(), It.IsAny())).ReturnsAsync(true); + var registry = new Mock(); registry.Setup(r => r.GetClient("client")).Returns(client.Object); + var service = new SeedingService(NullLogger.Instance, db, config, registry.Object); + await service.ApplyTrackerActionsForTorrentAsync(torrent); + client.Verify(c => c.RemoveTrackersAsync("hash", It.IsAny>(), It.IsAny()), Times.Once()); + } + + [Theory] + [InlineData(false, false)] + [InlineData(false, true)] + [InlineData(true, false)] + [InlineData(true, true)] + public async Task SuperSeeding_UsesFinalMatchingRuleAndSkipsUnchangedState(bool observed, bool desired) + { + var config = new TorrentarrConfig(); + config.TorrentClients["client"] = new() + { + Trackers = [new() { Uri = "tracker.example", SuperSeedMode = !desired }, + new() { Uri = "tracker.example", SuperSeedMode = desired }] + }; + using var db = new TorrentarrDbContext(new DbContextOptionsBuilder().UseInMemoryDatabase(Guid.NewGuid().ToString()).Options); + var client = new Mock(); + client.SetupGet(c => c.Capabilities).Returns(TorrentClientCapabilities.QBittorrent); + client.Setup(c => c.GetTorrentTrackersAsync("hash", It.IsAny())).ReturnsAsync([new() { Url = "https://tracker.example/announce", Status = 2 }]); + client.Setup(c => c.SetSuperSeedingAsync("hash", desired, It.IsAny())).ReturnsAsync(true); + var registry = new Mock(); registry.Setup(r => r.GetClient("client")).Returns(client.Object); + var service = new SeedingService(NullLogger.Instance, db, config, registry.Object); + var torrent = new TorrentInfo { Hash = "hash", ClientInstanceId = "client", SuperSeeding = observed }; + await service.ApplyTrackerActionsForTorrentAsync(torrent); + await service.ApplyTrackerActionsForTorrentAsync(torrent); + client.Verify(c => c.SetSuperSeedingAsync("hash", desired, It.IsAny()), observed == desired ? Times.Never() : Times.Once()); + client.Verify(c => c.SetSuperSeedingAsync("hash", !desired, It.IsAny()), Times.Never()); + } + + [Fact] + public async Task OrdinarySeedingLimits_AreIndependentOfHnrDeletionClearance() + { + var config = new TorrentarrConfig(); + config.TorrentClients["client"] = new() + { + CategorySeeding = new() + { + RemoveTorrent = 1, + MaxUploadRatio = 0.1, + HitAndRunMode = "and", + MinSeedRatio = 2, + MinSeedingTimeDays = 8 + } + }; + var service = CreateService(config); + var torrent = new TorrentInfo + { + ClientInstanceId = "client", + Hash = "hash", + State = "uploading", + AddedOn = 1, + Progress = 1, + Ratio = 0.5, + SeedingTime = 100, + IsPrivate = true + }; + (await service.SeedingLimitsMetAsync(torrent)).Should().BeTrue(); + (await service.ShouldRemoveTorrentAsync(torrent)).Should().BeFalse(); + } + + [Fact] + public async Task CustomFormatRejection_DoesNotRequireOrdinarySeedingLimits() + { + var config = new TorrentarrConfig(); + config.TorrentClients["client"] = new() { CategorySeeding = new() { RemoveTorrent = 2, MaxSeedingTime = 100000 } }; + var torrent = new TorrentInfo + { + ClientInstanceId = "client", + Hash = "hash", + State = "uploading", + AddedOn = 1, + Progress = 1, + IsPrivate = false, + RatioLimit = -1, + SeedingTimeLimit = -1 + }; + var service = CreateService(config, observed: torrent); + (await service.EvaluateRemovalAsync(torrent, TorrentRemovalIntent.RejectedByCustomFormat)).Allowed.Should().BeTrue(); + (await service.EvaluateRemovalAsync(torrent, TorrentRemovalIntent.SeedingLimit)).Blockers.Should().Contain(b => b.Code == "seeding.limit"); + config.TorrentClients["client"].CategorySeeding.HitAndRunMode = "and"; + config.TorrentClients["client"].CategorySeeding.MinSeedRatio = 2; + (await service.EvaluateRemovalAsync(torrent, TorrentRemovalIntent.RejectedByCustomFormat)).Allowed.Should().BeFalse(); + } + + [Theory] + [InlineData("and", 1, 1, 0.1, 80000, 1.0, 6460, false)] + [InlineData("and", 1, 1, 0.1, 90000, 1.0, 0, false)] + [InlineData("and", 1, 1, 1.0, 86460, 1.0, 0, true)] + [InlineData("or", 1, 1, 1.0, 100, 1.0, 86360, true)] + [InlineData("or", 1, 1, 0.1, 86460, 1.0, 0, true)] + [InlineData("and", 0, 1, 0.0, 86459, 1.0, 1, false)] + [InlineData("and", 1, 0, 0.1, 100, 1.0, 0, false)] + [InlineData("and", 0, 0, 0.0, 100, 1.0, 0, true)] + [InlineData("and", 1, 1, 0.1, 90000, 0.5, 0, false)] + [InlineData("and", 1, 1, 1.0, 100, 0.5, 0, true)] + [InlineData("and", 1, 1, 0.0, 0, 0.05, 0, true)] + public void LegacyDiagnostics_UseClientTimeAndPreserveClearance(string mode, double minimumRatio, + int minimumDays, double ratio, long seedSeconds, double progress, double remaining, bool eligible) + { + foreach (var trackerOverride in new[] { false, true }) + { + var config = new TorrentarrConfig(); + config.TorrentClients["client"] = new() + { + CategorySeeding = new() + { + HitAndRunMode = mode, + MinSeedRatio = minimumRatio, + MinSeedingTimeDays = minimumDays, + TrackerUpdateBuffer = 60 + }, + Trackers = trackerOverride ? [new() { Uri = "tracker.example" }] : [] + }; + var service = CreateService(config); + var torrent = new TorrentInfo + { + ClientInstanceId = "client", + IsPrivate = true, + Ratio = ratio, + SeedingTime = seedSeconds, + Progress = progress, + RatioLimit = -1, + SeedingTimeLimit = -1 + }; + var decision = service.GetProtectionDecisions(torrent, + [new() { Url = "https://tracker.example/announce", Status = 2 }]).Should().ContainSingle().Subject; + decision.State.Should().Be(eligible ? "eligible" : "protected"); + decision.ObservedSeedSeconds.Should().Be(seedSeconds); + decision.RemainingSeedSeconds.Should().Be(remaining); + decision.LocalEstimate.Should().BeFalse(); + decision.SeedAccountingDescription().Should().Contain("Client-reported").And.Contain("remaining time requirement"); + decision.Reason.Should().Contain("tracker update buffer=60s").And.Contain("mode=" + mode); + } + } + + [Theory] + [InlineData(true, false, "hnr.protected")] + [InlineData(false, false, "hnr.unknown")] + [InlineData(true, true, "hnr.protected.native-stop-risk")] + public async Task RemovalDiagnostic_OnlyHealthyProtectionWaitsAreExpected(bool healthy, bool nativeRisk, string code) + { + var config = new TorrentarrConfig(); + config.TorrentClients["client"] = new() { CategorySeeding = new() { HitAndRunMode = "and", MinSeedRatio = 1, MinSeedingTimeDays = 8 } }; + using var db = new TorrentarrDbContext(new DbContextOptionsBuilder().UseInMemoryDatabase(Guid.NewGuid().ToString()).Options); + var client = new Mock(); + client.Setup(c => c.GetTorrentTrackersAsync(It.IsAny(), It.IsAny())) + .ReturnsAsync([new() { Url = "https://tracker.example/announce", Status = healthy ? 2 : 4 }]); + var registry = new Mock(); + registry.Setup(r => r.GetClient("client")).Returns(client.Object); + var service = new SeedingService(NullLogger.Instance, db, config, registry.Object); + var decision = await service.EvaluateRemovalAsync(new() + { + ClientInstanceId = "client", + Hash = "hash", + AddedOn = 1, + IsPrivate = true, + Progress = 1, + SeedingTime = 480000, + Ratio = 0.048, + RatioLimit = nativeRisk ? 1 : -1, + SeedingTimeLimit = -1 + }, TorrentRemovalIntent.RejectedByAllowlist); + decision.Blockers.Should().ContainSingle().Which.Code.Should().Be(code); + decision.IsExpectedProtectionWait().Should().Be(code == "hnr.protected"); + decision.Reason.Should().Contain("Client-reported seed=480000s").And.Contain("remaining time requirement=211200s"); + } + + [Fact] + public async Task ModernDeadlineRisk_RemainsActionableAndUsesLocalObservations() + { + var config = new TorrentarrConfig(); + config.TorrentClients["client"] = new() + { + Trackers = [new() { Uri = "tracker.example", + Protection = new() { MinimumSeedHours = 48, FulfillmentHours = 1 } }] + }; + using var db = new TorrentarrDbContext(new DbContextOptionsBuilder().UseInMemoryDatabase(Guid.NewGuid().ToString()).Options); + var client = new Mock(); + List trackers = [new() { Url = "https://tracker.example/announce", Status = 2 }]; + client.Setup(c => c.GetTorrentTrackersAsync("hash", It.IsAny())).ReturnsAsync(trackers); + var registry = new Mock(); + registry.Setup(r => r.GetClient("client")).Returns(client.Object); + var service = new SeedingService(NullLogger.Instance, db, config, registry.Object); + var torrent = new TorrentInfo + { + Hash = "hash", + ClientInstanceId = "client", + AddedOn = DateTimeOffset.UtcNow.AddDays(-1).ToUnixTimeSeconds(), + IsPrivate = true, + Progress = 1, + SeedingTime = 90000, + RatioLimit = -1, + SeedingTimeLimit = -1 + }; + var protection = service.GetProtectionDecisions(torrent, trackers).Should().ContainSingle().Subject; + protection.LocalEstimate.Should().BeTrue(); + protection.ObservedSeedSeconds.Should().Be(0); + protection.RemainingSeedSeconds.Should().Be(48 * 3600); + protection.DeadlineRisk.Should().Be("unachievable"); + var decision = await service.EvaluateRemovalAsync(torrent, TorrentRemovalIntent.RejectedByAllowlist); + decision.IsExpectedProtectionWait().Should().BeFalse(); + decision.Blockers.Should().ContainSingle().Which.Code.Should().Be("hnr.protected.deadline-unachievable"); + decision.Reason.Should().Contain("Locally observed seed=0s").And.Contain("deadline risk=unachievable"); + } + [Fact] public void CategoryOverride_ReplacesOnlyExplicitValues() { diff --git a/tests/Torrentarr.Infrastructure.Tests/Services/TorrentCacheServiceTests.cs b/tests/Torrentarr.Infrastructure.Tests/Services/TorrentCacheServiceTests.cs index f273df73..a9fcd919 100644 --- a/tests/Torrentarr.Infrastructure.Tests/Services/TorrentCacheServiceTests.cs +++ b/tests/Torrentarr.Infrastructure.Tests/Services/TorrentCacheServiceTests.cs @@ -157,32 +157,23 @@ public void FileFilterStates_IgnoreInstanceAndHashCase() } [Fact] - public void AllowlistWarnings_AreThrottledPerInstanceAndHash() - { - var svc = CreateService(); - var now = DateTimeOffset.UtcNow; - var throttle = TimeSpan.FromMinutes(5); - - svc.ShouldLogAllowlistWarning("qBit-a", "shared-hash", now, throttle).Should().BeTrue(); - svc.ShouldLogAllowlistWarning("qBit-a", "shared-hash", now.AddMinutes(1), throttle).Should().BeFalse(); - svc.ShouldLogAllowlistWarning("qBit-b", "shared-hash", now.AddMinutes(1), throttle).Should().BeTrue(); - svc.ShouldLogAllowlistWarning("qBit-a", "shared-hash", now.AddMinutes(6), throttle).Should().BeTrue(); - } - - [Fact] - public void RemovalWarnings_ThrottleSameReasonButReportChangedReasons() - { - var svc = CreateService(); - var now = DateTimeOffset.UtcNow; - var window = TimeSpan.FromMinutes(5); - - svc.ShouldLogRemovalBlock("client-a", "hash", "HnR clearance", now, window).Should().BeTrue(); - svc.ShouldLogRemovalBlock("client-a", "hash", "HnR clearance", now.AddSeconds(5), window).Should().BeFalse(); - svc.ShouldLogRemovalBlock("client-a", "hash", "Missing path mapping", now.AddSeconds(6), window).Should().BeTrue(); - svc.ShouldLogRemovalBlock("client-b", "hash", "Missing path mapping", now.AddSeconds(7), window).Should().BeTrue(); - svc.ShouldLogRemovalBlock("client-a", "hash", "Missing path mapping", now.AddMinutes(6), window).Should().BeTrue(); + public void Diagnostics_ReportChangesPerClientTorrentAndStreamUntilReset() + { + var svc = CreateService(); + svc.DiagnosticChanged("client-a", "hash", "allowlist", "no files").Should().BeTrue(); + svc.DiagnosticChanged("CLIENT-A", "HASH", "allowlist", "no files").Should().BeFalse(); + svc.DiagnosticChanged("client-b", "hash", "allowlist", "no files").Should().BeTrue(); + svc.DiagnosticChanged("client-a", "other", "allowlist", "no files").Should().BeTrue(); + svc.DiagnosticChanged("client-a", "hash", "removal", "hnr.protected").Should().BeTrue(); + svc.DiagnosticChanged("client-a", "hash", "removal", "missing-mapping").Should().BeTrue(); + svc.DiagnosticChanged("client-a", "hash", "removal", "missing-mapping").Should().BeFalse(); + svc.DiagnosticChanged("client-a", "hash", "allowlist", null).Should().BeTrue(); + svc.DiagnosticChanged("client-a", "hash", "allowlist", "no files").Should().BeTrue(); + svc.DiagnosticChanged("client-a", "hash", "removal", "missing-mapping").Should().BeFalse(); svc.InvalidateTorrent("client-a", "hash"); - svc.ShouldLogRemovalBlock("client-a", "hash", "Missing path mapping", now.AddMinutes(6), window).Should().BeTrue(); + svc.DiagnosticChanged("client-a", "hash", "removal", "missing-mapping").Should().BeTrue(); + svc.Clear(); + svc.DiagnosticChanged("client-a", "hash", "removal", "missing-mapping").Should().BeTrue(); } [Fact] diff --git a/tests/Torrentarr.Infrastructure.Tests/Services/TorrentProcessorImportAllowlistTests.cs b/tests/Torrentarr.Infrastructure.Tests/Services/TorrentProcessorImportAllowlistTests.cs index 08b1ba49..92faabf5 100644 --- a/tests/Torrentarr.Infrastructure.Tests/Services/TorrentProcessorImportAllowlistTests.cs +++ b/tests/Torrentarr.Infrastructure.Tests/Services/TorrentProcessorImportAllowlistTests.cs @@ -89,7 +89,7 @@ public async Task CompletedTorrent_AutoDelete_RemovesOnlyOwnedDisallowedFiles(bo new RecoveryIncident { AddedOn = torrent.AddedOn, Stage = "checked", Attempts = 1 }); var result = await ApplyAsync(CreateProcessor(seeding.Object, deletion: deletion.Object), torrent, Config(autoDelete: true), client, completed: true); - Ready(result).Should().Be(!repaired); + Ready(result).Should().BeTrue("cleanup restrictions must not block allowed imports"); File.Exists(allowed).Should().BeTrue(); File.Exists(disallowed).Should().Be(repaired); File.Exists(sibling).Should().BeTrue(); @@ -174,7 +174,7 @@ public async Task CompletedTorrent_MetadataSidecars_DoNotBlockImportWithoutAutoD } [Fact] - public async Task CompletedTorrent_DisallowedNonMetadataExtras_StillBlockImport() + public async Task CompletedTorrent_DisallowedExtras_DoNotBlockConfiguredMedia() { var root = Directory.CreateTempSubdirectory("torrentarr-disallowed-import-"); try @@ -186,7 +186,7 @@ public async Task CompletedTorrent_DisallowedNonMetadataExtras_StillBlockImport( TorrentFile(0, "Movie/movie.mkv"), TorrentFile(1, "Movie/extra.dat")]); Ready(await ApplyAsync(CreateProcessor(), Torrent(content.FullName), Config(autoDelete: false), client, completed: true)) - .Should().BeFalse(); + .Should().BeTrue(); } finally { @@ -288,25 +288,32 @@ public async Task AllFilesExcluded_WhenHnrBlocksDeletion_RemainsPending() seeding.VerifyAll(); } - [Fact] - public async Task PartialExclusion_WhenHnrBlocksCleanup_DoesNotDeleteExcludedFile() + [Theory] + [InlineData(".txt")] + [InlineData(".png")] + [InlineData(".exe")] + [InlineData(".rar")] + [InlineData(".mkv")] + public async Task PartialExclusion_WhenHnrBlocksCleanup_DoesNotBlockImport(string extension) { var root = Directory.CreateTempSubdirectory("torrentarr-hnr-cleanup-"); try { var content = Directory.CreateDirectory(Path.Combine(root.FullName, "Movie")); await File.WriteAllTextAsync(Path.Combine(content.FullName, "movie.mkv"), "video"); - var excludedPath = Path.Combine(content.FullName, "sample.txt"); + var excludedPath = Path.Combine(content.FullName, "sample" + extension); await File.WriteAllTextAsync(excludedPath, "sample"); var torrent = Torrent(content.FullName); var seeding = new Mock(); seeding.Setup(s => s.HnrAllowsDeleteAsync(torrent, "excluded file cleanup by import allowlist", It.IsAny())) .ReturnsAsync(false); - var client = new FileClient([TorrentFile(0, "Movie/movie.mkv"), TorrentFile(1, "Movie/sample.txt")]); + var client = new FileClient([TorrentFile(0, "Movie/movie.mkv"), TorrentFile(1, "Movie/sample" + extension)]); - var result = await ApplyAsync(CreateProcessor(seeding.Object), torrent, Config(autoDelete: true), client, completed: true); + var cfg = Config(autoDelete: true); + cfg.Torrent.FileNameExclusionRegex = ["sample"]; + var result = await ApplyAsync(CreateProcessor(seeding.Object), torrent, cfg, client, completed: true); - Ready(result).Should().BeTrue("valid media can be imported without deleting H&R-protected metadata sidecars"); + Ready(result).Should().BeTrue("valid media can be imported without deleting H&R-protected excluded files"); File.Exists(excludedPath).Should().BeTrue(); seeding.VerifyAll(); } @@ -357,25 +364,40 @@ public async Task CompletedTorrent_WhenContentRootIsMissingButSavePathExists_Rem } [Fact] - public async Task AllowlistWarning_IsDeduplicatedForFiveMinutesThenExpires() + public async Task AllowlistWarning_ReportsChangesAndRecurrenceAfterResolution() { + var logger = new Mock>(); + var processor = CreateProcessor(logger: logger.Object); var torrent = Torrent("/downloads/Movie"); - var seeding = new Mock(); - seeding.Setup(s => s.HnrAllowsDeleteAsync(torrent, It.IsAny(), It.IsAny())) - .ReturnsAsync(false); - var cache = new TorrentCacheService(NullLogger.Instance); - var firstProcessor = CreateProcessor(seeding.Object, cache); - var secondProcessor = CreateProcessor(seeding.Object, cache); - var client = new FileClient([TorrentFile(0, "Movie/sample.txt")]); - var started = DateTimeOffset.UtcNow; - - await ApplyAsync(firstProcessor, torrent, Config(autoDelete: false), client, completed: true); - cache.ShouldLogAllowlistWarning("qBit", torrent.Hash, started.AddMinutes(1), TimeSpan.FromMinutes(5)) - .Should().BeFalse(); + var client = new FileClient([TorrentFile(0, "Movie/bad.exe")]); + await ApplyAsync(processor, torrent, Config(false), client, true); + await ApplyAsync(processor, torrent, Config(false), client, true); + await ApplyAsync(processor, torrent, Config(false), new FileClient([TorrentFile(0, "Movie/other.exe")]), true); + await ApplyAsync(processor, torrent, Config(false), new FileClient([TorrentFile(0, "Movie/movie.mkv")]), true); + await ApplyAsync(processor, torrent, Config(false), client, true); + var logs = logger.Invocations.Where(i => i.Method.Name == "Log" + && i.Arguments[2]?.ToString()?.StartsWith("Import blocked by file allowlist") == true).ToList(); + logs.Count(i => (LogLevel)i.Arguments[0]! == LogLevel.Warning).Should().Be(3); + logs.Count(i => (LogLevel)i.Arguments[0]! == LogLevel.Debug).Should().Be(1); + client.PriorityCalls.Should().Be(3); + } - await ApplyAsync(secondProcessor, torrent, Config(autoDelete: false), client, completed: true); - cache.ShouldLogAllowlistWarning("qBit", torrent.Hash, started.AddMinutes(6), TimeSpan.FromMinutes(5)) - .Should().BeTrue(); + [Theory] + [InlineData("Movie/release.rar", true)] + [InlineData("Movie/release.r00", true)] + [InlineData("Movie/release.R123", true)] + [InlineData("Movie/release.exe", false)] + public async Task RejectedContent_ExplainsArchivesWithoutAllowingImport(string name, bool archive) + { + var logger = new Mock>(); + var client = new FileClient([TorrentFile(0, name), TorrentFile(1, "Movie/sample.mkv")]); + Ready(await ApplyAsync(CreateProcessor(logger: logger.Object), Torrent("/downloads/Movie"), Config(false), client, true)).Should().BeFalse(); + var message = logger.Invocations.Single(i => i.Method.Name == "Log" + && i.Arguments[2]?.ToString()?.StartsWith("Import blocked by file allowlist") == true).Arguments[2]!.ToString()!; + message.Should().Contain(name).And.Contain("extension"); + message.Contains("archive contents cannot be imported").Should().Be(archive); + message.Should().NotContain("archive-only"); + client.PriorityCalls.Should().Be(1); } [Fact] @@ -486,7 +508,7 @@ public async Task AutoDelete_ContentRootSymlink_RemainsBlockedAndDoesNotDeleteTa } [Fact] - public async Task AllowlistWarning_ShowsBoundedExamplesAndKeepsThrottle() + public async Task AllowlistWarning_ShowsBoundedExamplesAndSuppressesRetries() { var logger = new Mock>(); var seeding = new Mock(); @@ -503,16 +525,51 @@ public async Task AllowlistWarning_ShowsBoundedExamplesAndKeepsThrottle() warning.Should().Contain("8 total, 3 more").And.Contain("sample0.txt").And.Contain("sample4.txt").And.NotContain("sample5.txt"); } + [Theory] + [InlineData("hnr.protected", LogLevel.Information)] + [InlineData("hnr.unknown", LogLevel.Warning)] + [InlineData("hnr.protected.native-stop-risk", LogLevel.Warning)] + [InlineData("storage.mapping", LogLevel.Warning)] + public async Task RemovalDiagnostics_ReportIssuesIndependentlyOfLogSuppression(string code, LogLevel firstLevel) + { + var logger = new Mock>(); + var issues = new Mock(); + var config = new TorrentarrConfig(); + var processor = CreateProcessor(logger: logger.Object, issues: issues.Object, config: config); + var torrent = Torrent("/downloads/Movie"); + var method = typeof(TorrentProcessor).GetMethod("LogRemovalBlocked", BindingFlags.NonPublic | BindingFlags.Instance)!; + async Task Block(string reason, string blocker = "") + { + var decision = new RemovalDecision([new(blocker.Length == 0 ? code : blocker, reason, "Review settings")]); + await (Task)method.Invoke(processor, [torrent, TorrentRemovalIntent.RejectedByAllowlist, decision.Reason, null, decision])!; + } + await Block("ratio=0.048; seed=480000s"); + await Block("ratio=0.049; seed=480300s"); + config.Apply(new TorrentarrConfig(), 1); + await Block("ratio=0.050; seed=480600s"); + var logs = logger.Invocations.Where(i => i.Method.Name == "Log").ToList(); + logs.Select(i => (LogLevel)i.Arguments[0]!).Should().Equal(firstLevel, LogLevel.Debug, firstLevel); + issues.Verify(i => i.ReportAsync(It.IsAny(), It.IsAny()), code == "hnr.protected" ? Times.Never() : Times.Exactly(3)); + issues.Verify(i => i.ResolveAsync(It.IsAny(), It.IsAny()), code == "hnr.protected" ? Times.Exactly(3) : Times.Never()); + await Block("new blocker", "client.disconnected"); + ((LogLevel)logger.Invocations.Last(i => i.Method.Name == "Log").Arguments[0]!).Should().Be(LogLevel.Warning); + var clear = typeof(TorrentProcessor).GetMethod("ClearRemovalBlockAsync", BindingFlags.NonPublic | BindingFlags.Instance)!; + await (Task)clear.Invoke(processor, [torrent, TorrentRemovalIntent.RejectedByAllowlist])!; + await Block("new blocker", "client.disconnected"); + ((LogLevel)logger.Invocations.Last(i => i.Method.Name == "Log").Arguments[0]!).Should().Be(LogLevel.Warning); + } + private TorrentProcessor CreateProcessor( ISeedingService? seeding = null, - ITorrentCacheService? cache = null, ISafeDeletionService? deletion = null, ILogger? logger = null) => new( + ITorrentCacheService? cache = null, ISafeDeletionService? deletion = null, ILogger? logger = null, + IOperationalIssueReporter? issues = null, TorrentarrConfig? config = null) => new( logger ?? NullLogger.Instance, new QBittorrentConnectionManager(NullLogger.Instance), _db, - new TorrentarrConfig(), + config ?? new TorrentarrConfig(), cache ?? new TorrentCacheService(NullLogger.Instance), new DatabaseRestartCoordinator(), - seedingService: seeding, safeDeletion: deletion); + seedingService: seeding, safeDeletion: deletion, issues: issues); private static ArrInstanceConfig Config(bool autoDelete) => new() { diff --git a/tests/Torrentarr.Infrastructure.Tests/Services/TorrentProcessorTests.cs b/tests/Torrentarr.Infrastructure.Tests/Services/TorrentProcessorTests.cs index 83d1980f..50078813 100644 --- a/tests/Torrentarr.Infrastructure.Tests/Services/TorrentProcessorTests.cs +++ b/tests/Torrentarr.Infrastructure.Tests/Services/TorrentProcessorTests.cs @@ -62,6 +62,17 @@ private TorrentProcessor CreateProcessor(TorrentarrConfig? config = null, Micros // ── Constructor ──────────────────────────────────────────────────────────── + [Fact] + public async Task RoutinePause_DoesNotDependOnHnrService() + { + var torrent = new TorrentInfo { ClientInstanceId = "qBit", Hash = "hash", AddedOn = 1, Progress = 1, State = "uploading", IsPrivate = true }; + var client = new Mock(); + client.Setup(c => c.GetTorrentsAsync(It.IsAny(), It.IsAny(), It.IsAny())).ReturnsAsync([torrent]); + var processor = CreateProcessor(); + var method = typeof(TorrentProcessor).GetMethod("MayPauseAsync", BindingFlags.NonPublic | BindingFlags.Instance)!; + (await (Task)method.Invoke(processor, [client.Object, torrent, CancellationToken.None])!).Should().BeTrue(); + } + [Fact] public async Task ImportDecision_IsVisibleAtInformation_WhenTorrentIsMissing() { @@ -95,13 +106,29 @@ public void Constructor_SpecialCategoriesComputedFromConfig() // ── ProcessTorrentsAsync – no clients ───────────────────────────────────── [Fact] - public async Task ProcessTorrentsAsync_NoQBitClients_DoesNotThrow() + public async Task ProcessTorrentsAsync_NoQBitClients_ReportsUnavailable() { var svc = CreateProcessor(); var act = async () => await svc.ProcessTorrentsAsync("radarr-hd"); - await act.Should().NotThrowAsync(); + await act.Should().ThrowAsync(); + } + + [Fact] + public async Task PartialClientOutage_ProcessesHealthyInventoryWithoutReportingOverallSuccess() + { + var config = new TorrentarrConfig(); config.Settings.Tagless = true; + config.QBitInstances["qBit-good"] = new() { Host = "fixture", ManagedCategories = ["autobrr"] }; + config.QBitInstances["qBit-missing"] = new() { Host = "fixture", ManagedCategories = ["autobrr"] }; + var torrent = new TorrentInfo { Hash = "healthy", ClientInstanceId = "qBit-good", Category = "autobrr", Tags = "qBitrr-ignored" }; + var manager = new QBittorrentConnectionManager(NullLogger.Instance); + RegisterTestClient(manager, "qBit-good", new StubQBittorrentClient(torrent)); + var processor = new TorrentProcessor(NullLogger.Instance, manager, _db, config, + new TorrentCacheService(NullLogger.Instance), new()); + await FluentActions.Awaiting(() => processor.ProcessTorrentsAsync("autobrr")) + .Should().ThrowAsync().WithMessage("*qBit-missing*"); + (await _db.TorrentLibrary.SingleAsync()).Hash.Should().Be("healthy"); } [Fact] @@ -112,8 +139,7 @@ public async Task ProcessTorrentsAsync_SpecialCategoryName_NoQBitClients_DoesNot var svc = CreateProcessor(config); - // No client registered → returns at the client-null guard before the - // special-category skip, so this must not throw. + // Special categories are handled by the host, regardless of client availability. var act = async () => await svc.ProcessTorrentsAsync("failed"); await act.Should().NotThrowAsync(); @@ -158,7 +184,7 @@ public async Task ProcessSingleTorrentAsync_RecheckOnMissingOwningClient_DoesNot } [Fact] - public async Task ProcessTorrentsAsync_PreCancelledToken_DoesNotThrow() + public async Task ProcessTorrentsAsync_PreCancelledToken_PreservesCancellation() { var svc = CreateProcessor(); using var cts = new CancellationTokenSource(); @@ -167,7 +193,7 @@ public async Task ProcessTorrentsAsync_PreCancelledToken_DoesNotThrow() // No client → exits before any awaited work involving the token. var act = async () => await svc.ProcessTorrentsAsync("radarr-hd", cts.Token); - await act.Should().NotThrowAsync(); + await act.Should().ThrowAsync(); } // ── ProcessSpecialCategoriesAsync ───────────────────────────────────────── @@ -263,6 +289,50 @@ public async Task ImportEligibility_RequiresArrOwner(string category, bool hasIm issues.Verify(i => i.ResolveAsync(It.Is(r => r.Code == "import.ownership" && r.SubjectId == torrent.Hash), It.IsAny()), Times.AtLeastOnce()); } + [Theory] + [InlineData(false)] + [InlineData(true)] + public async Task RemovalWarningsAfterRestart_ResolveOnlyAfterSuccessfulProcessing(bool processingFails) + { + var config = new TorrentarrConfig(); + config.Settings.Tagless = true; + config.QBitInstances["qBit"] = new() { ManagedCategories = ["autobrr"] }; + var torrent = new TorrentInfo + { + Hash = "ignored", + ClientInstanceId = "qBit", + Category = "autobrr", + Tags = "qBitrr-ignored", + AddedOn = 1 + }; + _db.OperationalIssues.Add(new() + { + Code = "torrent.removal.RejectedByAllowlist", + ClientInstance = "qBit", + ArrInstance = "PreviousOwner", + SubjectType = "torrent", + SubjectId = torrent.Hash, + Severity = "warning" + }); + await _db.SaveChangesAsync(); + var manager = new QBittorrentConnectionManager(NullLogger.Instance); + RegisterTestClient(manager, "qBit", new StubQBittorrentClient(torrent)); + var issues = new Mock(); + var seeding = new Mock(); + if (processingFails) + seeding.Setup(s => s.ApplySeedingLimitsAsync(torrent, It.IsAny())).ThrowsAsync(new IOException("inspection failed")); + var processor = new TorrentProcessor(NullLogger.Instance, manager, _db, config, + new TorrentCacheService(NullLogger.Instance), new(), seedingService: processingFails ? seeding.Object : null, + issues: issues.Object); + if (processingFails) + await FluentActions.Awaiting(() => processor.ProcessTorrentAsync(torrent.Hash)).Should().ThrowAsync(); + else + await processor.ProcessTorrentAsync(torrent.Hash); + issues.Verify(i => i.ResolveAsync(It.Is(r => r.Code == "torrent.removal.RejectedByAllowlist" + && r.ArrInstance == "PreviousOwner" && r.SubjectId == torrent.Hash), It.IsAny()), + processingFails ? Times.Never() : Times.Once()); + } + [Fact] public async Task ObservingIgnoredUnownedTorrent_ResolvesLegacyOwnershipIssue() { @@ -693,11 +763,14 @@ public async Task ProcessSingleTorrentAsync_CompleteOnFirstObservation_IsInspect } [Theory] - [InlineData(false)] - [InlineData(true)] - public async Task HealthyRepairedTorrent_ImportsWithoutWaitingForDestructiveClearance(bool removalLimitMet) + [InlineData(false, false)] + [InlineData(true, false)] + [InlineData(false, true)] + [InlineData(true, true)] + public async Task CompletedTorrent_ImportsWithoutWaitingForDestructiveClearance(bool removalLimitMet, bool repaired) { - var path = Path.GetTempFileName(); + var path = Path.Combine(Path.GetTempPath(), "import-" + Guid.NewGuid().ToString("N") + ".mkv"); + await File.WriteAllTextAsync(path, "video"); try { var torrent = new TorrentInfo @@ -717,17 +790,19 @@ public async Task HealthyRepairedTorrent_ImportsWithoutWaitingForDestructiveClea var manager = new QBittorrentConnectionManager(NullLogger.Instance); RegisterTestClient(manager, "qBit", new StubQBittorrentClient(torrent)); var imports = new Mock(); - imports.Setup(i => i.IsCustomFormatUnmetAsync(torrent.Hash, torrent.Category, It.IsAny())).ReturnsAsync(true); + imports.Setup(i => i.IsCustomFormatUnmetAsync(torrent.Hash, torrent.Category, It.IsAny())).ReturnsAsync(repaired); imports.Setup(i => i.TriggerImportAsync(torrent.Hash, path, torrent.Category, It.IsAny())).ReturnsAsync(new ImportResult { Success = true }); var seeding = new Mock(); - seeding.Setup(s => s.ShouldRemoveTorrentAsync(torrent, It.IsAny())).ReturnsAsync(removalLimitMet); + seeding.Setup(s => s.SeedingLimitsMetAsync(torrent, It.IsAny())).ReturnsAsync(removalLimitMet); var deletion = new Mock(MockBehavior.Strict); var cache = new TorrentCacheService(NullLogger.Instance); cache.MarkFileFiltered("qBit", torrent.Hash); - MaintenancePersistence.Write(_db, MaintenancePersistence.Key("qBit", torrent.Hash, "recovery:ErrorRecovery"), + if (repaired) MaintenancePersistence.Write(_db, MaintenancePersistence.Key("qBit", torrent.Hash, "recovery:ErrorRecovery"), new RecoveryIncident { AddedOn = torrent.AddedOn, Attempts = 1, Stage = "checked", CompletedAt = DateTimeOffset.UtcNow, LastObserved = DateTimeOffset.UtcNow }); + var validation = new Mock(); + validation.Setup(v => v.ValidateFileAsync(path, It.IsAny())).ReturnsAsync(new MediaValidationResult { IsValid = true }); var processor = new TorrentProcessor(NullLogger.Instance, manager, _db, config, cache, new DatabaseRestartCoordinator(), - imports.Object, seeding.Object, mediaValidation: Mock.Of(), safeDeletion: deletion.Object); + imports.Object, seeding.Object, mediaValidation: validation.Object, safeDeletion: deletion.Object); await processor.ProcessTorrentAsync(torrent.Hash); imports.Verify(i => i.TriggerImportAsync(torrent.Hash, path, torrent.Category, It.IsAny()), Times.Once); imports.Verify(i => i.BlocklistAndReSearchAsync(It.IsAny(), It.IsAny(), It.IsAny()), Times.Never); diff --git a/tests/Torrentarr.Infrastructure.Tests/Services/TransferHistoryTests.cs b/tests/Torrentarr.Infrastructure.Tests/Services/TransferHistoryTests.cs new file mode 100644 index 00000000..90226401 --- /dev/null +++ b/tests/Torrentarr.Infrastructure.Tests/Services/TransferHistoryTests.cs @@ -0,0 +1,115 @@ +using FluentAssertions; +using Microsoft.Data.Sqlite; +using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Logging.Abstractions; +using Moq; +using Torrentarr.Core.Configuration; +using Torrentarr.Core.Interfaces; +using Torrentarr.Core.Models; +using Torrentarr.Infrastructure.Database; +using Torrentarr.Infrastructure.Database.Models; +using Torrentarr.Infrastructure.Services; +using Xunit; + +namespace Torrentarr.Infrastructure.Tests.Services; + +public sealed class TransferHistoryTests +{ + private static readonly DateTimeOffset Now = DateTimeOffset.Parse("2026-11-04T12:00:00Z"); + + [Fact] + public async Task Query_BucketsRatesAndPreservesPartialAndMissingCoverageAcrossReopen() + { + using var connection = new SqliteConnection("Data Source=:memory:"); + connection.Open(); + var options = new DbContextOptionsBuilder().UseSqlite(connection).Options; + var minute = Now.ToUnixTimeSeconds() / 60; + await using (var db = new TorrentarrDbContext(options)) + { + db.Database.EnsureCreated(); + db.TransferSamples.AddRange( + new TransferSample { ClientInstance = "one", Minute = minute - 5, DownloadRate = 100, UploadRate = 10 }, + new TransferSample { ClientInstance = "one", Minute = minute - 4, DownloadRate = 300, UploadRate = 30 }, + new TransferSample { ClientInstance = "one", Minute = minute, DownloadRate = 900 }); + await db.SaveChangesAsync(); + } + await using var reopened = new TorrentarrDbContext(options); + var result = await TransferHistoryApi.QueryAsync(reopened, ["one", "offline"], "24h", Now); + result.Series[0].Points.Should().HaveCount(288); + var last = result.Series[0].Points.Last(); + last.DownloadRate.Should().Be(200); + last.UploadRate.Should().Be(20); + last.Samples.Should().Be(2); + last.ExpectedSamples.Should().Be(5); + result.Series[0].Points.First().DownloadRate.Should().BeNull(); + result.Series[1].Points.Should().OnlyContain(p => p.DownloadRate == null && p.Samples == 0); + } + + [Theory] + [InlineData("1h", 60, 1)] + [InlineData("24h", 288, 5)] + [InlineData("7d", 336, 30)] + public async Task Query_ReturnsBoundedAlignedRanges(string range, int count, int bucket) + { + await using var db = new TorrentarrDbContext(new DbContextOptionsBuilder() + .UseInMemoryDatabase(Guid.NewGuid().ToString()).Options); + var result = await TransferHistoryApi.QueryAsync(db, ["one"], range, Now); + result.BucketMinutes.Should().Be(bucket); + result.Series.Single().Points.Should().HaveCount(count); + result.Series.Single().Points.Last().Timestamp.Should().BeBefore(Now); + } + + [Fact] + public async Task Collector_SkipsUnavailableAndDisabledClientsAndPrunesWithoutDuplicateMinutes() + { + using var connection = new SqliteConnection("Data Source=:memory:"); + connection.Open(); + var services = new ServiceCollection(); + services.AddDbContext(o => o.UseSqlite(connection)); + using var provider = services.BuildServiceProvider(); + using (var scope = provider.CreateScope()) + { + var db = scope.ServiceProvider.GetRequiredService(); + db.Database.EnsureCreated(); + db.TransferSamples.Add(new() { ClientInstance = "one", Minute = Now.ToUnixTimeSeconds() / 60 - 10081 }); + await db.SaveChangesAsync(); + } + var config = new TorrentarrConfig(); + foreach (var name in new[] { "one", "offline", "unsupported", "disabled" }) + config.TorrentClients[name] = new() { Host = "fixture", Disabled = name == "disabled" }; + var registry = new Mock(); + var one = new Mock(); + one.Setup(c => c.GetTransferInfoAsync(It.IsAny())) + .ReturnsAsync(new TorrentTransferRecord { DownloadSpeed = 123, UploadSpeed = 45 }); + var offline = new Mock(); + offline.Setup(c => c.GetTransferInfoAsync(It.IsAny())).ThrowsAsync(new IOException("fixture offline")); + var unsupported = new Mock(); + registry.Setup(r => r.GetClient("one")).Returns(one.Object); + registry.Setup(r => r.GetClient("offline")).Returns(offline.Object); + registry.Setup(r => r.GetClient("unsupported")).Returns(unsupported.Object); + var collector = new TransferHistoryCollector(provider.GetRequiredService(), config, registry.Object, + NullLogger.Instance); + await collector.CollectAsync(Now, default); + await collector.CollectAsync(Now.AddSeconds(20), default); + using var check = provider.CreateScope(); + var saved = await check.ServiceProvider.GetRequiredService().TransferSamples.ToListAsync(); + saved.Should().ContainSingle().Which.DownloadRate.Should().Be(123); + registry.Verify(r => r.GetClient("disabled"), Times.Never); + } + + [Fact] + public void Migration_CreatesHistoryTableOnExistingDatabaseAndIsRepeatable() + { + using var db = new TorrentarrDbContext(new DbContextOptionsBuilder() + .UseSqlite("Data Source=:memory:").Options); + db.Database.OpenConnection(); + db.Database.EnsureCreated(); + db.Database.ExecuteSqlRaw("DROP TABLE torrentarr_transfer_samples"); + ManualSqliteMigrations.Apply(db); + db.TransferSamples.Add(new() { ClientInstance = "one", Minute = 123 }); + db.SaveChanges(); + ManualSqliteMigrations.Apply(db); + db.TransferSamples.Single().Minute.Should().Be(123); + } +} diff --git a/tests/Torrentarr.Infrastructure.Tests/Services/WorkerLifecycleTests.cs b/tests/Torrentarr.Infrastructure.Tests/Services/WorkerLifecycleTests.cs index dfe64879..809b696d 100644 --- a/tests/Torrentarr.Infrastructure.Tests/Services/WorkerLifecycleTests.cs +++ b/tests/Torrentarr.Infrastructure.Tests/Services/WorkerLifecycleTests.cs @@ -208,7 +208,7 @@ public async Task ImmediateChildExit_CannotDisposeProcessDuringStatusRead() { await supervisor.RestartProcessAsync("Radarr"); await supervisor.GetProcessStatusAsync(); - supervisor.MonitorWorkers(); + await supervisor.MonitorWorkersAsync(); } } finally { await orchestrator.StopAsync(); } @@ -291,6 +291,36 @@ await File.WriteAllTextAsync(status, System.Text.Json.JsonSerializer.Serialize(n finally { await orchestrator.StopAsync(); File.Delete(status); } } + [Fact] + public async Task RequestedRestart_UsesGracefulShutdownBeforeTerminatingWorker() + { + if (OperatingSystem.IsWindows()) return; + var config = Config(); config.Settings.AutoRestartProcesses = false; + var evidence = Path.GetTempFileName(); var status = Path.GetTempFileName(); + using var supervisor = new WorkerProcessSupervisor(config, NullLogger.Instance, + Mock.Of(), _ => new ProcessStartInfo("/bin/sh") + { + ArgumentList = { "-c", "read command; printf '%s' \"$command\" > \"$1\"", "worker", evidence }, + UseShellExecute = false, + RedirectStandardInput = true + }); + var orchestrator = (IProcessOrchestrator)supervisor; + try + { + await orchestrator.StartAsync(); + var pid = (await supervisor.GetProcessStatusAsync())["Radarr"].ProcessId!.Value; + var files = (Dictionary)typeof(WorkerProcessSupervisor) + .GetField("_statusFiles", System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.NonPublic)!.GetValue(supervisor)!; + files["Radarr"] = status; + await File.WriteAllTextAsync(status, System.Text.Json.JsonSerializer.Serialize(new WorkerHeartbeat + { Instance = "Radarr", Pid = pid, Heartbeat = DateTimeOffset.UtcNow, RestartRequested = true }, WorkerHeartbeat.JsonOptions)); + await supervisor.MonitorWorkersAsync(); + (await File.ReadAllTextAsync(evidence)).Should().Be("shutdown"); + (await supervisor.GetProcessStatusAsync())["Radarr"].IsAlive.Should().BeFalse(); + } + finally { await orchestrator.StopAsync(); File.Delete(evidence); File.Delete(status); } + } + private static TorrentarrConfig Config() { var config = new TorrentarrConfig(); diff --git a/tests/scripts/test_check_openapi_drift.py b/tests/scripts/test_check_openapi_drift.py index 1c345e62..c48ecd39 100644 --- a/tests/scripts/test_check_openapi_drift.py +++ b/tests/scripts/test_check_openapi_drift.py @@ -17,6 +17,16 @@ def document(paths, components=None): class OpenApiDriftTests(unittest.TestCase): + def test_statistics_generation_documents_coverage_and_detects_drift(self): + contract = json.loads((SCRIPT.parents[1] / "docs/assets/openapi.json").read_text()) + self.assertEqual(module.statistics_drift(contract), []) + schema = contract['paths']['/web/statistics/transfers']['get']['responses']['200']['content']['application/json']['schema'] + bucket = schema['properties']['series']['items']['properties']['points']['items'] + self.assertTrue(bucket['properties']['downloadRate']['nullable']) + self.assertIn('expectedSamples', bucket['properties']) + del contract['paths']['/web/statistics/transfers'] + self.assertTrue(module.statistics_drift(contract)) + def test_checked_in_contract_documents_native_cleanup_and_inspection_failure(self): contract = json.loads((SCRIPT.parents[1] / "docs/assets/openapi.json").read_text()) self.assertEqual(module.maintenance_drift(contract), []) diff --git a/webui/index.html b/webui/index.html index 2cc13661..c5a0a42a 100644 --- a/webui/index.html +++ b/webui/index.html @@ -2,6 +2,7 @@ + @@ -12,7 +13,7 @@ name="description" content="A Radarr and Sonarr companion app for managing torrents" /> - + Torrentarr diff --git a/webui/public/favicon-16x16.png b/webui/public/favicon-16x16.png index 00c828e79d245d65bc3376bfc2d7738ae70e7961..8cbdfb5453273eba711f5bd0be61b8ad3aeee6d4 100644 GIT binary patch delta 982 zcmV;{11bEc2Zy9jTL#nENSvHG%!-eUwC|~;!eAEpT?F5F1smRqTd>b z2Mtw-0eI(QLh0B{^}tB5KK>a1^aXTXZ`TZSxlug*QIzJUfK<)`@v4BJb3nZYAnp$E z@mD~+zkqri7!K)G+p5z$-+a5isd+$9b#qWn|D5UYu78{cioOPls(^TBfudbN&^%aZ z>-*8y>22*>I$O+AYXAJhxH*C&F=wuqwkDqz(xUW0R9jerkhT$+w;2c`K(G!JQP9nU zpCsoaO_9h2k3v_T@$AXnmB(IatD6(k{eMzU9}P;mtDW2~MwCZYyEue z(1~5q<;B(PSYE4itzOeC*}FiW`o#wKbBE#HTHu?pXhtn?Okh&&xoR=Dte)3dU8^}b*Az=b#d-h zea<;_D9KiA4|L)rLVb|^v>tyQYvdJ9GY?GKM zS%Zp>5rz<)iZWdS_`nh+8WUa8EW<>D!9)QcGR+WCG;@=Hs1R5U0~XkH4`R1&Ze{D* zUfa9g_3nDTyWahMA>a3(mtO<`fH6h@00<%c$JNOOhs*f}$A5&|WHIGp;z?LsiYjb6 z^Lrj;Idbf9??nIrpU+41^z@kMHbw{`Ga-oY(^hzwnHkgBg{d=qEHtSo3g$GTc10#y zIMeM`3FnUu9{RP8t}fQ+^AQMROd^C#Ijeu?$RnT4j2{1JX|SiSym?cFb9Gf6SnL3x z&I2D^`E^v>7=N-UjO(whX4T5*<3fBNA@Oves7;Td;agzrbmujON*++ zn37g?LqT?%O*2r!kj0ZKvwSP)+0iFarN2G1nF64AQAey!so=Yubpeyn>c7MH<*7MpZz|?`%{5X+3y6133 zQ^Np}Wgr~L{ZQyI8x4zlaq5$y_6;LM>H;<@pFl)E07hSLFR6O8Y3puWy6$aCh=<#w z!u06fG?denTrDk#Axgnk0IISl6sPUsgj%7g9h!OoEcodxS? zNpTTDBv}?zDXoJFX%J-sv~0eSGgxQ7%{%tRUiWM=b15PsO;d5r;!Jdqr0woP%6u{u zxG61!V?fL;hI46wd%%{2%Y0k|r=is4$h-+$!{illuS%zC5OE4mz z%$bIqDc3QsnVFr%#C$aEH>LCj`!5g;#kF24XXF=X{2nC4N=Shz1OQmfq#MqSO|kyT zz^dWtIf4Kjj1(@>xLrBuW%f<4*?)Jvb*Wj>*fEnpLRTbU|Yut|5{e3lTM4_ zVd9%qL2M&a9Fr!jjS14OwBlIzn!x2l2{}9d*`b3i2>XEGdAAE8ebntlHN$m1ANQ6$ zT0R>qEEA)7NLM6DuqW8sDP_=J7ouYdiaI4?>Jx4yoF z?%tE*?gWm&g@>d7i9rN|B1Z5M1S~Ze3n++%4Kzu*7+e?8s346d`q zVzIF6;siC7yfdCqZce3A6=_W?&~@FPF|CwoS@o7>*EqI)1TC@q)mN7_T~A#0V-pgI zM7c{e-_Z1F$z-Y^o=_l_N6p1(s!jW82_34sgzq3*5;BU%vj$*Z2Kj z0*J^b%kt9*P=BTCMs6~dg1D@Jqb1_SzSjEUK-!8h8vtD?nc>(di@@=i=n+#zZ-D|3 z2^eD#3Wl}@!~Qw1EL(Qzx&^4JTF5!ygaE(Rb;HY8M6^>!%Y89D%rZ3+xt{^jX&}c0 zZf5{lcVMCdxI5Ia#gI>9EV`v8d@J}u;YcJ5xp}#2UVm=>wE0iZ-tnUX;_-Ml#@KE_ zG}wca`t`}4{PX6ZFxS8c|5nDT@P{nTy7$q8!%Y*P*%>SCb;9U1JU1^7B9Vv)1OiXY z{Qb0b-zy*zAMMRkbEVo5xJPWEV`|VO5R>e<&F6`!OdJm^}5Nhu-;40kSM_ z5kwDq@S^yx14VAbW;Sx^YC1eW54ceUfPk=8F znCZd2M{Qv0!xnJNd9?TO0ubgl5WF4!jn)4E;kE*a4L~9Q$OQ;@Ga!EpOm>Jp@ntHz z5D6Hp{b@fS8Vke*1H#UqB=?@wdfq#Lg@3w~1cUzh0I)IZKz&1FT{4v#Ae6e=VSMK< zcF(`&(T;~-0phdUv%X^e1`xarNX+jTp9X;V#($%0{Pnuor0 zcQ6=)fKR%`-_X!7TuG${$O#p0v5qJ});judVi^eY@oQ>Myf#~NML4QCX-Y&_CI?{uq(b5zYdR=ywYmNce^xgru1;9-e z&~$;D742hd6??SyTxZZXFC->8`6Y{WhnMIM2i+E6*gUGCZx#>fpDg_Ydm>TU zIiCw^b6xJ8qB)YT8+oQ_L4S@&av-z4R<0QbHwy+5!rR&M0kMq`z0V+AKD;wn)qe78 zraMk^_(a1JzcOs`s&4VJ3oXjVb~p!5h~YYqT@m2AA@2w`43kS8mkH1;A%SfKvonMn zBlN%o;6Vuo64J@+XYZILnn!PRk7Nbh#rTpzU1!;v7_8eo-fq2^qkkCoySn8x@1{a!V{1*DF6a1+81F;P*g=SE&D6) zCexG&=OT*Rve6vA34a8dS8DD-QLGl6o@4@7A<;*G^j?UpmaEsoxeHB!i!E_5Tmfm< zh5AcPkP(>era*Vi(4jn$N)6RCjU^NnWL2q^Y}@+OaUE#Xqul}NZrTh!HYe5u0$=R3 zJZn2*PXIhXggr@6i$HOY%ixLw*+~ydFZNi44UI5Y#u2k+Vmt z*T!tFFKAQLpF6IA3m2QzR3`HnF$qr9dv!R*F>Ab%65=d#;p6=0zI<*CHQA`Z1jFRdjEd^=~K?`rdVu* z2h1-k*`*ui49jvPZw!(Ik)p!7714_aS`@8TNqTSt5_0_IY@(eRJTd3#_nt5RxU5)8 zj|3w1xn3_!-Jd%)oMgD;+_-*M_VO!@i1pum;x|b}eJv5Y+zW}IsI2TgpCt8&x5^_t zFfa-H;eW8%rB~OAXO$CgC~ERP4=%^$!~b5tdXyI>>wwq7d6C&RL|zAx^seMZ3+w-~ zAQfYhls0LOb($tWBMhNo!@D znfr2b>}Vu%$FqqO3zg&*xY63G%<$mXIFQ{^Uw<;mfHxcH3K@tZWW%++))Oe?d_N0^CT*O9euAkbD8} zbblc4MbIowuFiE3@}(f9JIy{bORhQ1BQdSVc$yFKVjW z=!0#yFTTwm2)rK*hPJFwux9P~OjoZ|0w@f?QbQu*^2#oD7hO+RCRBC0oKVXZRn0Cq zIWF6AS)RFf`@0*i`>&a?Ab%JU{u2lW0e=yXk6Lz;_aFBQpHBkDBtpcb5JI>vN6WGb zjZ7w(PV10R74U9X-@xO?;;o4pdp~~n#CLI|?-LMRnEoQ_*KY=*=_K%E@xHk?M$c~j zf-wmYkqIG+T$gvXEUPpN*RF-@TBcf-<5TJKs1S_;4Gj&>_r)OJcgQ&> z06+-gn>W8*J#NC}d%&nqx=4WK#%&hkEQ!M3up zl9zw`z39m%D^gF#Kgkpx3uQiF@w@OxYs6OL@0P;2lRSj;zf-W5B7fxeHDP{3s6d`2yWLfi9qw9tt=hm@_T*PUwzfQ^`|&-3V=umvD4{PQ-3%dsRv;2LorX!#)kWAqK95w|JxYwS4i@}) zdy6=|luI5~6bjh^zdY^oS+m-jnwms%s@3B0_~P>N^7Rg23Vol+J%73H@#<*XlSf{x zpMOza?)mRPvA^8WY|cd&6@vwCC8_6!hO`fpIj!I8_qhCp0bj5%NHmKU3Se%^?6HKJ zdR_`WI(cwo|H0sb0|x`Mec_;AA{;IAFW+$y@H-IBIoY~(YhdE!DTj;7%1gawuQ>Fl zTizLa@3yNuo}V|px>WwyTi^~bN~qxR3V(vfD;_zN+S1#f{%q=bmD7vH6wDDKff5iA zO3Hv~lL4EEY12kl%jG0Z(Sjxy9vGk8qs41)Z|(|Cqm@NLm!fIc&Aw#%+XMji9XRwr zO-aH^<1_lm>!cwJ0B_?`2M0CsI?%jjW6c+H@ITK4B5r2vU zL2KAVJYj6?iD%#1`f2aJHv@$c$Sx5+*)u~jbZvT8(NTn6h)VAc4o-%+ z@*mt_;7s6dH-tpOXp0m|1wqM}3d2ES$a?AVmf^15P4{~*UbIqwt7qW$veU;tHUz)| zVB{Re9a-?7R(4&{B@517y=~hZ02u&eG;~30wLhd3a>q|i;|xjF^?z~5E3kP#h>>U` z03WgR8e-T0mjDhhhwLfwVImP6ZsZ)2Q}I6MAhB4#wI&|-DYoav{e2^^>RDsR_6E)> z2!<9(jsv~lF#W}aQ*`aTwSgL3T%XSy7Bp20=M~kl@+ruUIsy=I zMt!!$07?OB!QoEECx4hlpeC^>gUYD}0EZ6Ma=Sa>@9ggT+t(HeSw*3Xh3x3Aw$`K9 zD3)z88?MxVO6BmF`OEkAEF^?*0KBt1p3+SV;ZQ&lbVC;uMS-d7kR><(U^Aw1+Xkl; zuq;T#DR4N^z_=6e5>%A=Y6$`V-K_1x(atW5212Ef0&@*-aDPtl+?rM6ivxwn4)?C$ zE{|a8)M2K>ndG|lR9BdmDIV+YLo$_xps1>&sv0b2gV}Nj;4rDvEjf&Hn*j!eY@0ty zo36tkg1`is6&Lz{`|J98KbgIQ)mkI!>hr4ZefR0LpKGs8Ku(-rnhq3ZCYr!G$%+4=h`rrfq{8H+Y|c?MrumK^TsjhU(`>Y7njAWNQ9 zCY`iwN6=LjmSsh{Xi;Fez1?q8tHRWE=!TvWR7LC5G=GgGbbyQQIsp8n>VLg&lr_q}#<|>a3N=lGuIWbvRnyt1l}p zcX2K<%71K4v$;E$A3sIy*er{zN=uDqiuA;VwSKdL0K6=nCJhb5fm^QL_H_quKiQvh zh|9gdD<0|W>33we8`Lred){a;vN`Q1rfEXgb>wo&c7ZC|CMBD8x;11UZ{s(#`U2r}91l2j^*(ab2#rjqXp8#cVQ&(yS? zibY6XcX)s}4-+Et17E#DFHAkUcz;S>kv9Y;3vkQe1O`QZ$kI;?lWXU_aA*_&#D)fE z+kfuCRn!-*a0{T*s!F%SZQ&M3ggb>n>5IMrvToDPl?V3hU6jq`t8`uG*<60tJ@?%4 zwEzIHji*$@B5_4vUPrlL=i`?=Y_AwBAv+q_B^&jJc4o@z+Ld7RSl;*M@vQH;&xT4b zsaw3+7q@Z*uFYu$>g@1=Lp1?JuE`OQil}*oSRT8N6XTI^Fh;Lg^HIphqz^T;1> zUefx{6Yg$6-n)MLLM>48lu}$h-stNetV;DRFARDnw_3iN96fKiiitnIE_q83l)-4LKOuFm&r=yofqfsKoVzr!e5K>q2Yon501fMJ7Vp=FVvf{x*ZL}^PlZIc&(-?)PL)iZB;RE1;9HxI)ra_!g`1#2D^1x+5B2>?tOGsRVQ$fr9#uW=sEQpI<+=pqdbJ=dxjBQa zs>mzmi=Rw(zi{H~|9NKD!H(XLH0_-3|hK5 z*-iBY!l+nm8_dWb8D+I6#X4`(pc3@Fz*~}i+4eN-aYI{m$= zNJU&)@KI4~ebQLHS41l)iVIMwR%}a9QCgI$7!fIgNR>caD zauPzVuRCk)%$Yf7&;0)1W6!tugu!>PRaaLRx}1+|OBkaQx-s0gtdlLv>Sa4lp5r)S z*L4%F=Qnz;x1V#r()0LEj?Tsf^XBdQw)bY=v}1jJeJ&@u!gajsY}*=}&<&Q*4X`XL zXtv{k<2c~DF1Vf#p67w@`{4OLaM?Hq!TAAB{B;uix#t!yuKIrw(A3nF#}V)3T-@Y1 zPA}WGL4to>_&@*(5JCVE0V2N3m@@y5OP+b>dlS&q)Kn@6pYVOrUjlXo&s|H;)ATQ+ z%+X`a^)>LR#ime0g6AA;#1on=QP>4MNEF5xXqxuAqD608`sAYrzcT^xc)UAfY&im5 z?)w5d!BaPJbmvL2!%am|W1xz>H;~u?L`Q&lD@Zs(d9DaZ3`2n!H)wyw7YyFtuZ(a! z%*}#_a7cqlB%()R(c9;h&3*1$3utI)7@{c3n+P;K8(!BFUSGar^T1GDf0cMGfLIAW zRKS>r_c8`_yz9~1H=F>fHy%)2NJx`*)GK?R-=2G#CBnT^@DPnfAsUTBG?ud>#&G7X zx8CZ0BLOWf4QC3Wyp2Fb+3>mg&P{`0S3iY`e*lWd*|`r_f0ZG+6tni~JP^*cM0E4k zx>#NLfkEemqA@KA9}9%2s`@@g!auup+O)*i5)hBa&t{CRN1(hkylW-&6U}v%QPKtY zcs)D+;XTJdZT$5h@Z8}*Z~_ny0}2EbabOq+#%oHK{jjmTyv*y+XXTaMKs6rl1pMTM z!zJN_QZyt552~Wd{iZ13kEc(cZvICCnwy&kA>zjf)He+;ti~fl^uJk*ycO_e^+T+* zTx0+~xaCYh@e3fj6iA$$jC&ztKe^93CKN|o9afca>BDzx^WQzh2W~7YE0b9a^q2oS zmw;YaZ=a*@#vhJO!3QlI(%y;yO)V`g75`QMQFWxLDgPq`I-7F=w(S778taDYs|5&e zE*mp{dFHnpZaNDD%m)$^J3ym2u7FO*!tvPqQN??J{k&px(i@}tg2&$4fu~uUVZwO< zyrw}U9ENB(JnxbzlkfSr0-Bnd<_p5#q~U$DxoK!yc`GCDA$I;BUJP#X{im=ZRK2O65g>bKD2qo4vp+r+1MxkCu#Lt3c>eR z1M%~|0{Q?DEd>;B0pLJDelCFM4~V&d}Op0peEyVKf<5ox=>%{Wy>u(Wo4Nj{-ynJ;Jv^ z!Wo1ZdSm^-M+ZSTubUiVWebJ1*=LOy@oWbH@p$|aUx+u-@Q~QQtJwZ~Apk#>jbGXr zgpCg$0OIK5Tb;iFAZ`PsyMXWpK%sVmwt;Xf5N-q^DuKiy04a|52cnTcVl)tW0Q3?9 zjZ~=D9YD=jwM0+8zH-R##|H4BkbK~M={eTPB_$=kmKNX(aeo?KK4D9YbuN(q5Sz5D zG5CSU{{Toy_+t__*gp--Ukn2N0VHEv{w^bQ2M{g-H~beibF<#n$d}&e*`fUb#MKI; zYM_>D*sKFD@WpULA7@vU*=_W2`LxzFbzrPVub%-xxgtB&*VmtJ+s>#oyk^u^hEVSX zT6N$iT6^^v;QlNS9vss!*uMzEdltCZ0Kz+^{X5?T?%fQbL1(gQTbHq!AN^Z+xv^P$ z53^Zc%m<;oB?KA4Nx(-2eX+<$!3QOWqN&%*ZvqdPwlgCQuNwx8@;___@czigR|JUy z=sps}WmoY__&IRpUSR%4AW@Lzoq8Z|5}W>cCBP@}bvJg?7yD=>9H27LU{>u~wn#jb z;~lNR=%63Sp;b}X1+t`PM8tMh?XFAcMjzcU!Lf9Es`3hFsymEb^jaOgad`;<-`aH& z3L6AF3&4{xKO^hAR$%-*Hofwg8o#WpOzB%(JU;j2VI_!=%S=l>qIwL$4m*a`CJ*JR$)Sj#Uz-GbRl$>-hmx-3pvn$WBDSK=9I3Jg4G5vnSy0 z)4<(3KzJ;39{rctjH=b00iJo|?IHPNC%-JZ^|;dInCtimLE{2kA5|OGaq%8StDmkj zXC2QAUY{{64NpN?LZ>SUBQYioFZ198yb;kDQ5z841Q@IXpF#AN;>nE(_Dre5-o%tnGxR{|`n+w&}v zm`H+sMnHoJeIPHojiYiED&A)(*g60f3g*Y@OA+-3Q11kQyVBPoB3lFk0(iMt5LFz< ziAs3S^C2XRHV~fEuGrT*T0LFU%TWlUK%|4TK4Q`XgeA1(>|;duto6TLkFRn$4$iXj z*^co4=^Fly>e~7xS$P1`Bl5G&@!GQYDN`+Q&SF{c;5Z&vT-W6iUY@jlu7YSUFMzv8 z((#=F48lc1BhX18SWR#ojD=DN^%P#iNa%);YBldCSxZdaY_LOEf5k6 z-jl&Hw*ygYItNlho}z^(_Q@7dUdqJM66QZKvgoh2FJ>hHlS%7vG2{G!V~$w9_&k{h zsq;ZX3viH5&)C!V`laDz*qf$lD4y>hktd}Hi$e|wo*UHSa@e}@hy@5ArL3FuLJ&R@ z7Echd=mj8XeiA(C9U>Ym$lL5MJYif{`LLv<@2jTm)dwPYVCf!JO5G{V39~U6UQ;7c zT8Qgm|4b-y@3b_$VVYnVWKpe$LUxmYC&oa0Ld#ZLi{a?v~7QpWxtd^#~$eknW^7^fDkdX2K*DO34*9I-PrwS5>5Y8L}yaxDW{+ zC8EniiO3nOtfqZH$l5H&(JFWE0T(4KfrCsjT`%X0FAsr>)VUp%eO9hqsXC4`CJCP` zIunMmUEzrTOvB3zZ}P)!B*esw!RFMo5{bJzhDj=ng3$+m?337sgi`>amJ!tfiF4hU zwGNK6nT}huxB39=-CqMP3UGy2%Ml0c*>?b%b)((-g!22{vQLlReaNtvnEG3%wJ}~gKar~ zU$p2}H<*zD(XzCFI;UGKm893xll=x|MBIMJ*7W>KfW*@5hgWt^B!vVXDUaS0Kc*}m z`Rev5U)I!K1d0N(g`!dH*{ATgbw}!N3k0Rk6DSaGXoSOuk8YR!nssNUZWz5X@N(+w zxX%PRD*r57{M05CxJAzK8~t3l>=8U^eL=Tn1F*84Uii+VHzMJMK(wigP_l_|Zp3Sx z))~I|FQ1Oszo+Ik*YT3pR}|>kr|*2*@gHnzXwFG`q`ie>F^^Hf9V=F>$kz>hZZ^E* zy8n81!6P4L1O#x3a=%>SZiU>Q?ND%w_1J)O#=TQ>W;RUpu7-a^e%mmH6dDRhKV?Fc z0>IRW3VmYtH9y44pu^hd;AJ0NwtsK+)(H)Q#= zeYwNN#Ul2Sit)v%iS}1-L)KR3nFo)&)KF9NQ#nXT%Q8iQZbb#FE+5wS%@y1BFZW$v zmAQ~?$-Iaa4=WoL8{iV?afz z3KNJ?`J@pa{5l~*j}oJUNb2h0=1JRSiO?1J&^3ETBf&4Z=U>BAwY#J1;21g>@n$Qp zuweF>qHe`&ch%m`Gw@09AauIt)_8YZc=FRrUN|RVS?`&q8Ec1c_4PLW+%>Pg{ONJm zM(>{c;Fb61F1b5I{H(wOnNpc8j^5jF^YAtMu0w_o5x)8p)(eV+Oze9{&&+fdt|{& z>p!q|^(zm5ySY$A=Lnb%_wLCxsGBrxBlL3X}L(azS6gfM8TIhT4T#vHt6}j$E){@}x=Y@|LWhLcX|+g`x+e zg}pZ+W6cDpqF@E&Izu?Y5QV{5(zKDnvVwOV@)}Ijf+3rHpUCs7Kn=}a7Hj&<@LQfV z40F0H)H=a$u|odDit=*fKYl}#DC=GjQbQF$RGjue(B#8tz~vk2hs|`uvB=#&()G`K z)a74B;m}kfm_$H*+QTP3QA>(XrIa9mZN%L*Xqa!T%s;x%67(y>u8@e>T(re^{}Bso5JNg zP6JSf5r|%bAYWc5n1zBITG@6`l~3r(Q_XiODn=Ly>leCZUZ?A3Z~2t%4Byt$w$TJ@ zYQ^f+t>5@9CCwQSv+~*&0n&34>zhG7=yrUAMhl*OIlskNo;soao%S9y7PhiA&y7LeY| zd-JA{LeBu<;8a$FTR37v1 z+m&2ijT39B1MO+q)m&~@uG-L$qv k(E7NntgOpf+kd+KA3cF6P4&F-oB#j-07*qoM6N<$f|`8!0RR91 literal 6808 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 z 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 Eg8gHIqW}N^ literal 60407 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 IM6N<$f#NOM#%prD(C@6qisWC4^u4 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 z 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

3}K&VRf!!Tv}7wrBR- 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 - - - - - - - - - - + + Torrentarr + + + + + + + + + + + + + + + + diff --git a/webui/public/manifest.json b/webui/public/manifest.json index d4111f08..cb7ab338 100644 --- a/webui/public/manifest.json +++ b/webui/public/manifest.json @@ -1,5 +1,5 @@ { - "background_color": "#101114", + "background_color": "#151719", "categories": ["utilities", "productivity"], "description": "A Radarr and Sonarr companion app for managing torrents", "display": "standalone", @@ -34,5 +34,5 @@ "screenshots": [], "short_name": "Torrentarr", "start_url": "/", - "theme_color": "#7aa2f7" + "theme_color": "#151719" } diff --git a/webui/src/AppShell.tsx b/webui/src/AppShell.tsx index 4fd5096d..54ea9c80 100644 --- a/webui/src/AppShell.tsx +++ b/webui/src/AppShell.tsx @@ -37,6 +37,7 @@ import ReadarrIcon from "./icons/readarr.svg"; import QbitIcon from "./icons/qbittorrent.svg"; import ConfigIcon from "./icons/gear.svg"; import logoUrl from "./icons/logov2-clean.svg"; +import { ShellDialog } from "./components/ShellDialog"; import { safeClick } from "./utils/safeClick"; import { NAVIGATE_TAB_EVENT, @@ -250,6 +251,8 @@ function AppShell({ const [meta, setMeta] = useState(initialMeta); const [metaLoading, setMetaLoading] = useState(false); const [showHelpMenu, setShowHelpMenu] = useState(false); + const [navigationOpen, setNavigationOpen] = useState(false); + const [displayOpen, setDisplayOpen] = useState(false); const [showChangelog, setShowChangelog] = useState(false); const [showAlreadyUpToDateModal, setShowAlreadyUpToDateModal] = useState(false); @@ -462,6 +465,7 @@ function AppShell({ "torrentarr:focusQbitInstance", detail.qbitInstance, ); + else sessionStorage.removeItem("torrentarr:focusQbitInstance"); } catch { /* Navigation still works without a persisted focus hint. */ } @@ -797,17 +801,173 @@ function AppShell({ } }, [push, refreshMeta]); + const resourceLinks = ( +

+ ); + const navigation = ( + + ); + return ( -
+
+ + {navigationOpen && ( + setNavigationOpen(false)} + > + {navigation} + {resourceLinks} + + )} + {displayOpen && ( + setDisplayOpen(false)} + > +
+ Theme +
+ + +
+
+
+ View density +
+ + +
+
+
+ )}
- -

Torrentarr

+ +

{tabs.find((tab) => tab.id === activeTab)?.label}

{displayVersion} @@ -843,40 +1003,15 @@ function AppShell({ Offline )} -
- - -
-
- - -
+
Live @@ -967,33 +1102,6 @@ function AppShell({
- Loading...
}>
{visitedTabs.has("issues") && ( diff --git a/webui/src/__tests__/components/ArrActivityCharts.test.tsx b/webui/src/__tests__/components/ArrActivityCharts.test.tsx new file mode 100644 index 00000000..baba7347 --- /dev/null +++ b/webui/src/__tests__/components/ArrActivityCharts.test.tsx @@ -0,0 +1,148 @@ +import { act, fireEvent, render, screen, within } from "@testing-library/react"; +import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"; +import type { ArrActivityHistory, ArrActivityPoint } from "../../api/types"; +import { + groupActivity, + activityPath, + activityColor, + workerHue, + activityValue, +} from "../../utils/arrActivityChart"; +import { ArrActivityCharts } from "../../components/ArrActivityCharts"; +import { getArrActivity } from "../../api/client"; +const settings = vi.hoisted(() => ({ liveArr: true })); +vi.mock("../../context/WebUIContext", () => ({ useWebUI: () => settings })); +vi.mock("../../api/client", async (original) => ({ + ...(await original()), + getArrActivity: vi.fn(), +})); +const point = ( + queueDepth: number | null, + queueSamples = 5, +): ArrActivityPoint => ({ + timestamp: "2026-11-04T11:55:00Z", + queueDepth, + queueSamples, + completionSamples: 5, + expectedSamples: 5, + searchesCompleted: 0, + importsConfirmed: 1, +}); +const history: ArrActivityHistory = { + range: "24h", + bucketMinutes: 5, + generatedAt: "2026-11-04T12:00:00Z", + series: [ + { instance: "Sonarr-one", type: "sonarr", points: [point(3)] }, + { instance: "Sonarr-two", type: "sonarr", points: [point(4)] }, + { instance: "Radarr-one", type: "radarr", points: [point(2)] }, + ], +}; +beforeEach(() => { + vi.mocked(getArrActivity).mockReset(); + settings.liveArr = true; +}); +afterEach(() => vi.useRealTimers()); +const settle = () => + act(async () => { + await Promise.resolve(); + }); +describe("Arr activity", () => { + it("aggregates workers by type while retaining missing coverage and observed zero", () => { + const rows = groupActivity(history.series, "type"); + const sonarr = rows.find((s) => s.type === "sonarr")!; + expect(sonarr.points[0]!.queueDepth).toBe(7); + expect(sonarr.points[0]!.searchesCompleted).toBe(0); + expect(sonarr.points[0]!.expectedSamples).toBe(10); + const partial = groupActivity( + [{ ...history.series[0]!, points: [point(null, 0)] }, history.series[1]!], + "type", + )[0]!; + expect(activityValue(partial.points[0], "queueDepth")).toBe("4 (partial)"); + expect(activityPath(partial.points, "queueDepth", 10)).toBe(""); + expect( + activityPath( + [point(0), point(null, 0), point(3)], + "queueDepth", + 10, + ).match(/M/g), + ).toHaveLength(2); + }); + it("keeps worker colors deterministic and uses distinct type colors", () => { + expect(workerHue("Sonarr-one")).toBe(workerHue("Sonarr-one")); + expect(activityColor(history.series[0]!, "worker")).not.toBe( + activityColor(history.series[1]!, "worker"), + ); + expect(activityColor(history.series[0]!, "type")).toBe("var(--arr-sonarr)"); + expect(activityColor(history.series[2]!, "type")).toBe("var(--arr-radarr)"); + }); + it("supports grouping, separate throughput metrics, legend toggles, and accessible data", async () => { + vi.mocked(getArrActivity).mockResolvedValue(history); + render(); + await settle(); + const section = screen.getByRole("region", { + name: "Arr activity history", + }); + expect(getArrActivity).toHaveBeenCalledWith("24h"); + expect( + within(section).getByRole("button", { name: "Sonarr", exact: true }), + ).toHaveAttribute("aria-pressed", "true"); + fireEvent.change(screen.getByLabelText("Group activity by"), { + target: { value: "worker" }, + }); + const toggle = within(section).getByRole("button", { + name: "Sonarr-one", + exact: true, + }); + fireEvent.click(toggle); + expect(toggle).toHaveAttribute("aria-pressed", "false"); + fireEvent.change(screen.getByLabelText("Completed activity metric"), { + target: { value: "importsConfirmed" }, + }); + expect( + screen.getByText("Torrent imports confirmed · per 5 min"), + ).toBeInTheDocument(); + expect( + screen.getByRole("slider", { name: "Inspect activity time" }), + ).toBeInTheDocument(); + const details = screen.getByText("View activity data").closest("details")!; + details.open = true; + fireEvent(details, new Event("toggle")); + expect(screen.getByRole("table")).toHaveTextContent("Sonarr-two"); + expect(screen.getByRole("table")).not.toHaveTextContent("Sonarr-one"); + }); + it("polls only while visible, active and live, with manual refresh support", async () => { + vi.useFakeTimers(); + vi.mocked(getArrActivity).mockResolvedValue(history); + const result = render(); + await settle(); + await act(async () => { + await vi.advanceTimersByTimeAsync(60000); + }); + expect(getArrActivity).toHaveBeenCalledTimes(2); + settings.liveArr = false; + result.rerender(); + await settle(); + const count = vi.mocked(getArrActivity).mock.calls.length; + await act(async () => { + await vi.advanceTimersByTimeAsync(120000); + }); + expect(getArrActivity).toHaveBeenCalledTimes(count); + window.dispatchEvent(new Event("torrentarr:statistics-refresh")); + await settle(); + expect(getArrActivity).toHaveBeenCalledTimes(count + 1); + result.rerender(); + window.dispatchEvent(new Event("torrentarr:statistics-refresh")); + await settle(); + expect(getArrActivity).toHaveBeenCalledTimes(count + 1); + }); + it("shows an error without fabricating a zero timeline", async () => { + vi.mocked(getArrActivity).mockRejectedValue(new Error("offline")); + render(); + await settle(); + expect(screen.getByRole("status")).toHaveTextContent( + "Arr activity unavailable", + ); + expect(screen.queryByRole("img")).not.toBeInTheDocument(); + }); +}); diff --git a/webui/src/__tests__/components/Statistics.test.tsx b/webui/src/__tests__/components/Statistics.test.tsx new file mode 100644 index 00000000..aee14b8b --- /dev/null +++ b/webui/src/__tests__/components/Statistics.test.tsx @@ -0,0 +1,44 @@ +import { describe, expect, it } from "vitest"; +import { render, screen } from "@testing-library/react"; +import { BreakdownBar } from "../../components/Statistics"; +import { transferPath } from "../../utils/transferChart"; +import type { TransferBucket } from "../../api/types"; + +const point = (downloadRate: number | null, samples = 5): TransferBucket => ({ + timestamp: "2026-11-04T12:00:00Z", + downloadRate, + uploadRate: 0, + samples, + expectedSamples: 5, +}); +describe("statistics", () => { + it("breaks lines for missing and incomplete buckets while preserving actual zero", () => { + const path = transferPath( + [ + point(0), + point(100), + point(null, 0), + point(30), + point(40, 2), + point(50), + ], + "downloadRate", + 100, + ); + expect(path.match(/M/g)).toHaveLength(3); + expect(path.match(/L/g)).toHaveLength(1); + expect(path).toContain("M20.00,130.00"); + }); + it("shows text equivalents for breakdowns including empty results", () => { + render( + , + ); + expect(screen.getByRole("group", { name: "Outcomes" })).toHaveTextContent( + "Failed 0", + ); + expect(screen.getByText("No data yet")).toBeInTheDocument(); + }); +}); diff --git a/webui/src/__tests__/components/TransferHistory.test.tsx b/webui/src/__tests__/components/TransferHistory.test.tsx new file mode 100644 index 00000000..2a08e8f6 --- /dev/null +++ b/webui/src/__tests__/components/TransferHistory.test.tsx @@ -0,0 +1,115 @@ +import { act, fireEvent, render, screen } from "@testing-library/react"; +import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"; +import { TransferHistory } from "../../components/TransferHistory"; +import { getTransferHistory } from "../../api/client"; +import type { TransferHistoryResponse } from "../../api/types"; + +const settings = vi.hoisted(() => ({ liveArr: true })); +vi.mock("../../context/WebUIContext", () => ({ useWebUI: () => settings })); +vi.mock("../../api/client", async (original) => ({ + ...(await original()), + getTransferHistory: vi.fn(), +})); +const history = (instance: string): TransferHistoryResponse => ({ + range: "24h", + bucketMinutes: 5, + generatedAt: "2026-11-04T12:00:00Z", + series: [ + { + instance, + points: [ + { + timestamp: "2026-11-04T11:50:00Z", + downloadRate: 1024, + uploadRate: 2048, + samples: 5, + expectedSamples: 5, + }, + { + timestamp: "2026-11-04T11:55:00Z", + downloadRate: null, + uploadRate: null, + samples: 0, + expectedSamples: 5, + }, + ], + }, + ], +}); +beforeEach(() => { + vi.useFakeTimers(); + vi.mocked(getTransferHistory).mockReset(); + settings.liveArr = true; +}); +afterEach(() => vi.useRealTimers()); +const settle = () => + act(async () => { + await Promise.resolve(); + }); + +describe("transfer history", () => { + it("polls at minute intervals only while active and live", async () => { + vi.mocked(getTransferHistory).mockResolvedValue(history("one")); + const view = render(); + await settle(); + expect(getTransferHistory).toHaveBeenCalledWith("one", "24h"); + await act(async () => { + await vi.advanceTimersByTimeAsync(59999); + }); + expect(getTransferHistory).toHaveBeenCalledTimes(1); + await act(async () => { + await vi.advanceTimersByTimeAsync(1); + }); + expect(getTransferHistory).toHaveBeenCalledTimes(2); + view.rerender(); + await act(async () => { + await vi.advanceTimersByTimeAsync(120000); + }); + expect(getTransferHistory).toHaveBeenCalledTimes(2); + settings.liveArr = false; + view.rerender(); + await settle(); + await act(async () => { + await vi.advanceTimersByTimeAsync(120000); + }); + expect(getTransferHistory).toHaveBeenCalledTimes(3); + window.dispatchEvent(new Event("torrentarr:statistics-refresh")); + await settle(); + expect(getTransferHistory).toHaveBeenCalledTimes(4); + }); + + it("changes range without mixing clients and rejects stale responses", async () => { + let finish!: (value: TransferHistoryResponse) => void; + vi.mocked(getTransferHistory) + .mockImplementationOnce( + () => + new Promise((resolve) => { + finish = resolve; + }), + ) + .mockResolvedValue(history("two")); + const view = render(); + view.rerender(); + await settle(); + finish(history("one")); + await settle(); + expect(screen.queryByText("one")).not.toBeInTheDocument(); + expect(screen.getByText("two")).toBeInTheDocument(); + fireEvent.click(screen.getByRole("button", { name: "7d", exact: true })); + await settle(); + expect(getTransferHistory).toHaveBeenLastCalledWith("two", "7d"); + }); + + it("exposes coverage and readable sampled data", async () => { + vi.mocked(getTransferHistory).mockResolvedValue(history("one")); + render(); + await settle(); + expect(screen.getByText(/5 \/ 10 minutes sampled/)).toBeInTheDocument(); + const details = screen.getByText("View sampled data").closest("details")!; + details.open = true; + fireEvent(details, new Event("toggle")); + await settle(); + expect(screen.getByRole("table")).toHaveTextContent("Unavailable"); + expect(screen.getByRole("table")).toHaveTextContent("1.0 KiB/s"); + }); +}); diff --git a/webui/src/__tests__/pages/AppShell.test.tsx b/webui/src/__tests__/pages/AppShell.test.tsx index 659b67b9..41298a65 100644 --- a/webui/src/__tests__/pages/AppShell.test.tsx +++ b/webui/src/__tests__/pages/AppShell.test.tsx @@ -1,6 +1,12 @@ import React from "react"; import { describe, it, expect, vi, beforeEach } from "vitest"; -import { render, screen, fireEvent, waitFor } from "@testing-library/react"; +import { + render, + screen, + fireEvent, + waitFor, + within, +} from "@testing-library/react"; import { AppShell } from "../../AppShell"; import { ToastProvider } from "../../context/ToastContext"; import { SearchProvider } from "../../context/SearchContext"; @@ -133,3 +139,93 @@ it("renders the authenticated shell when browser storage is blocked", async () = vi.restoreAllMocks(); } }); + +it("opens Display with persisted controls and returns focus on close", async () => { + view(); + const trigger = screen.getByRole("button", { name: "Display", exact: true }); + trigger.focus(); + fireEvent.click(trigger); + const dialog = screen.getByRole("dialog", { name: "Display" }); + fireEvent.click( + within(dialog).getByRole("button", { name: "Light", exact: true }), + ); + expect(document.documentElement).toHaveAttribute("data-theme", "light"); + fireEvent.click( + within(dialog).getByRole("button", { name: "Compact", exact: true }), + ); + expect(document.querySelector("[data-density]")).toHaveAttribute( + "data-density", + "compact", + ); + fireEvent.click( + within(dialog).getByRole("button", { name: "Close Display" }), + ); + expect(screen.queryByRole("dialog")).not.toBeInTheDocument(); + expect(trigger).toHaveFocus(); +}); + +it("closes the navigation drawer after switching pages and on Escape", async () => { + view(); + fireEvent.click(screen.getByRole("button", { name: "Menu", exact: true })); + const dialog = screen.getByRole("dialog", { name: "Navigation" }); + fireEvent.click( + within(dialog).getByRole("button", { name: "Config", exact: true }), + ); + expect(screen.queryByRole("dialog")).not.toBeInTheDocument(); + fireEvent.click(screen.getByRole("button", { name: "Menu", exact: true })); + fireEvent( + screen.getByRole("dialog", { name: "Navigation" }), + new Event("cancel", { bubbles: false, cancelable: true }), + ); + expect(screen.queryByRole("dialog")).not.toBeInTheDocument(); +}); + +it("dismisses Display when the native popover closes", () => { + view(); + fireEvent.click(screen.getByRole("button", { name: "Display", exact: true })); + const dialog = screen.getByRole("dialog", { name: "Display" }); + const event = new Event("toggle"); + Object.defineProperty(event, "newState", { value: "closed" }); + fireEvent(dialog, event); + expect( + screen.queryByRole("dialog", { name: "Display" }), + ).not.toBeInTheDocument(); +}); + +it("provides Wiki and GitHub footer links on desktop and in the navigation drawer", () => { + view(); + const resources = screen.getByLabelText("Project resources"); + const wiki = within(resources).getByRole("link", { name: /Wiki/ }); + expect(wiki).toHaveAttribute( + "href", + "https://feramance.github.io/Torrentarr/", + ); + expect(wiki).toHaveAttribute("target", "_blank"); + expect( + within(resources).getByRole("link", { name: /GitHub/ }), + ).toHaveAttribute("href", "https://github.com/Feramance/Torrentarr"); + fireEvent.click(screen.getByRole("button", { name: "Menu", exact: true })); + const drawer = screen.getByRole("dialog", { name: "Navigation" }); + expect( + within(drawer).getByRole("link", { name: /Wiki/ }), + ).toBeInTheDocument(); + expect(within(drawer).getByRole("link", { name: /GitHub/ })).toHaveAttribute( + "rel", + "noopener noreferrer", + ); +}); + +it("clears a stale client focus when Arr navigation requests all clients", async () => { + vi.mocked(api.getStatus).mockResolvedValue(offline); + view(); + await screen.findByRole("button", { name: "Torrent Clients", exact: true }); + sessionStorage.setItem("torrentarr:focusQbitInstance", "stale"); + window.dispatchEvent( + new CustomEvent("qbitrr:navigate-tab", { + detail: { tab: "qbittorrent", qbitCategory: "shared" }, + }), + ); + expect(sessionStorage.getItem("torrentarr:focusQbitInstance")).toBeNull(); + expect(sessionStorage.getItem("qbitrr:focusQbitCategory")).toBe("shared"); + sessionStorage.clear(); +}); diff --git a/webui/src/__tests__/pages/ProcessesView.test.tsx b/webui/src/__tests__/pages/ProcessesView.test.tsx index 4d5986d1..381bac2b 100644 --- a/webui/src/__tests__/pages/ProcessesView.test.tsx +++ b/webui/src/__tests__/pages/ProcessesView.test.tsx @@ -1,14 +1,42 @@ import userEvent from "@testing-library/user-event"; import React from "react"; -import { describe, it, expect, beforeAll, afterAll, afterEach } from "vitest"; -import { render, screen, waitFor } from "@testing-library/react"; +import { + describe, + it, + expect, + beforeAll, + afterAll, + afterEach, + vi, +} from "vitest"; +import { + render, + screen, + waitFor, + within, + fireEvent, +} from "@testing-library/react"; import { http, HttpResponse } from "msw"; import { setupServer } from "msw/node"; import type { ReactNode } from "react"; import { ProcessesView } from "../../pages/ProcessesView"; +import { WebUIProvider } from "../../context/WebUIContext"; +import { NAVIGATE_TAB_EVENT } from "../../utils/navigateTab"; import { ToastProvider } from "../../context/ToastContext"; -const server = setupServer(); +const server = setupServer( + http.get("/web/config", () => + HttpResponse.json({ WebUI: { LiveArr: false } }), + ), + http.get("/web/statistics/arr-activity", () => + HttpResponse.json({ + range: "24h", + bucketMinutes: 5, + generatedAt: "2026-11-04T12:00:00Z", + series: [], + }), + ), +); beforeAll(() => server.listen({ onUnhandledRequest: "bypass" })); afterEach(() => server.resetHandlers()); @@ -23,7 +51,11 @@ const emptyStatus = { const emptyCategories = { categories: [], ready: true }; function Wrapper({ children }: { children: ReactNode }) { - return {children}; + return ( + + {children} + + ); } function renderView(active = true) { @@ -404,7 +436,9 @@ it("refreshes loop status without showing a worker badge", async () => { expect( await screen.findByText("Running", {}, { timeout: 3000 }), ).toBeInTheDocument(); - expect(screen.queryByText(/^Worker(?: ·|$)/)).not.toBeInTheDocument(); + expect( + document.querySelector(".process-card__badge"), + ).not.toBeInTheDocument(); expect(screen.getByText(/1 in progress/)).toBeInTheDocument(); }); @@ -493,3 +527,183 @@ it("marks stopped worker activities as stopped despite a stale heartbeat", async ).toHaveLength(2); expect(screen.queryByText(/resumes/)).not.toBeInTheDocument(); }); + +it("deduplicates role metrics by instance while keeping shared-category instances separate", async () => { + server.use( + http.get("/web/processes", () => + HttpResponse.json({ + processes: [ + { + category: "shared", + name: "Radarr-one", + kind: "search", + alive: true, + queueCount: 4, + }, + { + category: "shared", + name: "Radarr-one", + kind: "torrent", + alive: true, + queueCount: 4, + }, + { + category: "shared", + name: "Radarr-two", + kind: "worker", + alive: true, + queueCount: 3, + }, + { + category: "space", + name: "FreeSpace", + kind: "system", + alive: true, + restartable: false, + freeSpacePaused: 2, + }, + ], + }), + ), + http.get("/web/status", () => HttpResponse.json(emptyStatus)), + http.get("/web/qbit/categories", () => HttpResponse.json(emptyCategories)), + ); + renderView(); + const metrics = screen.getByLabelText("Process statistics"); + await waitFor(() => + expect(within(metrics).getByText("7")).toBeInTheDocument(), + ); + expect(metrics).toHaveTextContent("Workers online2 / 2"); + expect(metrics).toHaveTextContent("Paused for space2"); +}); + +it.each(["worker", "torrent"])( + "makes the Arr %s torrent activity target navigate without nesting restart buttons", + async (kind) => { + const navigation = vi.fn(); + window.addEventListener(NAVIGATE_TAB_EVENT, navigation); + server.use( + http.get("/web/processes", () => + HttpResponse.json({ + processes: [ + { + name: "Sonarr-one", + category: "fallback", + kind, + alive: true, + pid: 10, + restartable: true, + }, + ], + }), + ), + http.get("/web/status", () => + HttpResponse.json({ + ready: true, + arrs: [{ name: "Sonarr-one", type: "sonarr", category: "tv" }], + qbitInstances: { one: { alive: false } }, + }), + ), + http.get("/web/qbit/categories", () => + HttpResponse.json({ + ready: true, + categories: [ + { + instance: "one", + category: "tv", + managedBy: "arr", + torrentCount: 2, + seedingCount: 0, + totalSize: 0, + }, + ], + }), + ), + ); + try { + renderView(); + const button = await screen.findByRole("button", { + name: "View torrents for Sonarr-one", + }); + expect(button.querySelector("button")).toBeNull(); + fireEvent.click(button); + expect(navigation).toHaveBeenCalledTimes(1); + expect((navigation.mock.calls[0]![0] as CustomEvent).detail).toEqual({ + tab: "qbittorrent", + qbitCategory: "tv", + qbitInstance: "one", + }); + if (kind === "worker") + expect( + screen.queryByRole("button", { name: /Search/ }), + ).not.toBeInTheDocument(); + } finally { + window.removeEventListener(NAVIGATE_TAB_EVENT, navigation); + } + }, +); + +it("uses all clients for a shared Arr category and keeps Restart independent", async () => { + const navigation = vi.fn(); + window.addEventListener(NAVIGATE_TAB_EVENT, navigation); + const restarted = vi.fn(); + server.use( + http.get("/web/processes", () => + HttpResponse.json({ + processes: [ + { + name: "Radarr-one", + category: "movies", + kind: "torrent", + alive: true, + pid: 10, + restartable: true, + }, + ], + }), + ), + http.get("/web/status", () => + HttpResponse.json({ + ready: true, + arrs: [{ name: "Radarr-one", type: "radarr", category: "movies" }], + qbitInstances: { one: { alive: true }, two: { alive: true } }, + }), + ), + http.get("/web/qbit/categories", () => + HttpResponse.json({ + ready: true, + categories: ["one", "two"].map((instance) => ({ + instance, + category: "movies", + managedBy: "arr", + torrentCount: 1, + seedingCount: 0, + totalSize: 0, + })), + }), + ), + http.post("/web/processes/movies/torrent/restart", () => { + restarted(); + return HttpResponse.json({ status: "ok" }); + }), + ); + try { + renderView(); + fireEvent.click( + await screen.findByRole("button", { + name: "View torrents for Radarr-one", + }), + ); + expect( + (navigation.mock.calls[0]![0] as CustomEvent).detail.qbitInstance, + ).toBeUndefined(); + navigation.mockClear(); + fireEvent.click( + screen.getByRole("button", { name: "Restart", exact: true }), + ); + await waitFor(() => expect(restarted).toHaveBeenCalledTimes(1)); + expect(navigation).not.toHaveBeenCalled(); + } finally { + window.removeEventListener(NAVIGATE_TAB_EVENT, navigation); + } +}); diff --git a/webui/src/__tests__/pages/QbitCategoriesView.test.tsx b/webui/src/__tests__/pages/QbitCategoriesView.test.tsx index 4cf84801..ca1a796e 100644 --- a/webui/src/__tests__/pages/QbitCategoriesView.test.tsx +++ b/webui/src/__tests__/pages/QbitCategoriesView.test.tsx @@ -20,6 +20,7 @@ vi.mock("../../api/client", () => ({ updateConfig: vi.fn(), getStatus: vi.fn(), getQbitOverview: vi.fn(), + getTransferHistory: vi.fn(async () => ({ series: [] })), })); const torrent = ( name = "Example", @@ -86,6 +87,7 @@ function view(active = true) { } beforeEach(() => { vi.resetAllMocks(); + sessionStorage.clear(); vi.mocked(api.getConfig).mockResolvedValue({ WebUI: { LiveArr: false } }); vi.mocked(api.getStatus).mockResolvedValue({ qbitInstances: { @@ -205,3 +207,51 @@ it("fetches the newly selected instance while an old overview is pending", async screen.queryByRole("button", { name: /movies.*Arr/ }), ).not.toBeInTheDocument(); }); + +it("clears an old search and opens shared categories across all clients", async () => { + vi.mocked(api.getStatus).mockResolvedValue({ + ready: true, + arrs: [], + qbitInstances: { + "qBit-home": { alive: true, host: "fixture", port: 1, version: null }, + "qBit-remote": { alive: false, host: "fixture", port: 2, version: null }, + }, + }); + vi.mocked(api.getQbitOverview).mockImplementation(async (instance) => ({ + ready: true, + instances: ["qBit-home", "qBit-remote"], + categories: [ + { ...category(), qbitInstance: "qBit-home" }, + { ...category(), qbitInstance: "qBit-remote" }, + ].filter((cat) => !instance || cat.qbitInstance === instance), + })); + const node = (active: boolean) => ( + + + + + + ); + const result = render(node(true)); + await screen.findAllByRole("button", { name: /movies.*Arr/ }); + fireEvent.change(screen.getByLabelText("Instance"), { + target: { value: "qBit-home" }, + }); + fireEvent.change(screen.getByLabelText("Search"), { + target: { value: "not-a-match" }, + }); + result.rerender(node(false)); + sessionStorage.setItem("qbitrr:focusQbitCategory", "movies"); + sessionStorage.removeItem("torrentarr:focusQbitInstance"); + result.rerender(node(true)); + await waitFor(() => expect(screen.getByLabelText("Search")).toHaveValue("")); + await waitFor(() => + expect(screen.getByLabelText("Instance")).toHaveValue("aggregate"), + ); + await waitFor(() => { + const buttons = screen.getAllByRole("button", { name: /movies.*Arr/ }); + expect(buttons).toHaveLength(2); + for (const button of buttons) + expect(button).toHaveAttribute("aria-expanded", "true"); + }); +}); diff --git a/webui/src/__tests__/setup.ts b/webui/src/__tests__/setup.ts index e9351c65..3731c22b 100644 --- a/webui/src/__tests__/setup.ts +++ b/webui/src/__tests__/setup.ts @@ -28,3 +28,21 @@ if (!globalThis.IntersectionObserver) unobserve() {} disconnect() {} } as unknown as typeof IntersectionObserver; + +if (!HTMLDialogElement.prototype.showModal) { + HTMLDialogElement.prototype.showModal = function () { + this.setAttribute("open", ""); + }; + HTMLDialogElement.prototype.close = function () { + this.removeAttribute("open"); + }; +} + +if (!HTMLElement.prototype.showPopover) { + HTMLElement.prototype.showPopover = function () { + this.setAttribute("open", ""); + }; + HTMLElement.prototype.hidePopover = function () { + this.removeAttribute("open"); + }; +} diff --git a/webui/src/api/client.ts b/webui/src/api/client.ts index 6c2642ab..89c0216c 100644 --- a/webui/src/api/client.ts +++ b/webui/src/api/client.ts @@ -1,5 +1,7 @@ import { clearUrlBaseCache, setUrlBaseFromMeta, webPath } from "./urlBase"; import type { + TransferHistoryResponse, + TransferRange, ArrListResponse, ConfigDocument, ConfigResponseWithWarning, @@ -1014,3 +1016,38 @@ export async function getIssueDetails( }> { return fetchJson(`/web/issues/${id}?page=${page}`); } + +export function getTransferHistory( + instance: string | undefined, + range: TransferRange, +) { + const params = new URLSearchParams({ range }); + if (instance) params.set("instance", instance); + return fetchJson( + `/web/statistics/transfers?${params}`, + ); +} + +export interface CatalogStatistics { + unit: string; + total: number; + available: number; + monitored: number; + missing: number; +} +export function getCatalogStatistics(kind: string, instance?: string) { + const params = new URLSearchParams({ kind }); + if (instance) params.set("instance", instance); + return fetchJson(`/web/statistics/catalog?${params}`); +} + +export function getArrActivity( + range: import("./types").TransferRange, + worker?: string, +) { + const params = new URLSearchParams({ range }); + if (worker) params.set("worker", worker); + return fetchJson( + `/web/statistics/arr-activity?${params}`, + ); +} diff --git a/webui/src/api/types.ts b/webui/src/api/types.ts index ec5a3101..6de4c0c6 100644 --- a/webui/src/api/types.ts +++ b/webui/src/api/types.ts @@ -157,7 +157,33 @@ export interface QbitOverviewCategory { torrentsTruncated?: boolean; } +export interface QbitActivitySummary { + instance: string; + torrentCount: number; + totalSize: number; + downloading: number; + seeding: number; + stopped: number; + other: number; +} + +export type TransferRange = "1h" | "24h" | "7d"; +export interface TransferBucket { + timestamp: string; + downloadRate: number | null; + uploadRate: number | null; + samples: number; + expectedSamples: number; +} +export interface TransferHistoryResponse { + range: TransferRange; + bucketMinutes: number; + generatedAt: string; + series: { instance: string; points: TransferBucket[] }[]; +} + export interface QbitOverviewResponse { + summaries?: QbitActivitySummary[]; instances: string[]; categories: QbitOverviewCategory[]; ready: boolean; @@ -801,3 +827,24 @@ export interface IssuesSummary { }[]; pending: PendingOperationalIssue[]; } + +export interface ArrActivityPoint { + timestamp: string; + queueDepth: number | null; + searchesCompleted: number | null; + importsConfirmed: number | null; + queueSamples: number; + completionSamples: number; + expectedSamples: number; +} +export interface ArrActivitySeries { + instance: string; + type: string; + points: ArrActivityPoint[]; +} +export interface ArrActivityHistory { + range: TransferRange; + bucketMinutes: number; + generatedAt: string; + series: ArrActivitySeries[]; +} diff --git a/webui/src/components/ArrActivityCharts.tsx b/webui/src/components/ArrActivityCharts.tsx new file mode 100644 index 00000000..a8dd8df1 --- /dev/null +++ b/webui/src/components/ArrActivityCharts.tsx @@ -0,0 +1,388 @@ +import { useEffect, useState } from "react"; +import { getArrActivity } from "../api/client"; +import type { + ArrActivityHistory, + ArrActivitySeries, + TransferRange, +} from "../api/types"; +import { useWebUI } from "../context/WebUIContext"; +import { + activityColor, + activityDash, + activityCoverage, + activityPath, + activityValue, + groupActivity, + type ActivityMetric, +} from "../utils/arrActivityChart"; + +function ActivityPlot({ + series, + metric, + title, + grouping, + index, + inspect, +}: { + series: ArrActivitySeries[]; + metric: ActivityMetric; + title: string; + grouping: "type" | "worker"; + index: number; + inspect: (index: number) => void; +}) { + const maximum = Math.max( + 1, + ...series.map((s) => + s.points.reduce((max, p) => Math.max(max, p[metric] ?? 0), 0), + ), + ); + const count = series[0]?.points.length ?? 0; + const available = series.some((s) => s.points.some((p) => p[metric] != null)); + return ( +
+
{title}
+ {!available ? ( +

+ {series.length + ? "Collecting activity history. Unavailable observations appear as gaps." + : "No series selected."} +

+ ) : ( + <> + { + const rect = event.currentTarget.getBoundingClientRect(); + inspect( + Math.round( + Math.max( + 0, + Math.min( + 1, + (((event.clientX - rect.left) / rect.width) * 800 - 24) / + 752, + ), + ) * + (count - 1), + ), + ); + }} + > + {[30, 86, 142].map((y) => ( + + ))} + + {maximum.toLocaleString(undefined, { maximumFractionDigits: 1 })} + + + {series[0]?.points[0] + ? new Date(series[0].points[0].timestamp).toLocaleString() + : ""} + + + {series[0]?.points.at(-1) + ? new Date(series[0].points.at(-1)!.timestamp).toLocaleString() + : ""} + + + {series.map((s) => ( + + + {s.points.map((p, i) => + p[metric] != null && + activityCoverage(p, metric) === p.expectedSamples && + (i === 0 || + i === s.points.length - 1 || + activityCoverage(s.points[i - 1]!, metric) !== + p.expectedSamples || + activityCoverage(s.points[i + 1]!, metric) !== + p.expectedSamples) ? ( + + ) : null, + )} + + ))} + +
+ {series.map((s) => ( +
+
+ + {s.instance} +
+
{activityValue(s.points[index], metric)}
+
+ ))} +
+ + )} +
+ ); +} + +export function ArrActivityCharts({ active }: { active: boolean }) { + const { liveArr } = useWebUI(); + const [range, setRange] = useState("24h"); + const [grouping, setGrouping] = useState<"type" | "worker">("type"); + const [metric, setMetric] = useState< + "searchesCompleted" | "importsConfirmed" + >("searchesCompleted"); + const [hidden, setHidden] = useState>(() => new Set()); + const [cursor, setCursor] = useState(null); + const [result, setResult] = useState<{ + range: TransferRange; + data: ArrActivityHistory; + } | null>(null); + const [failed, setFailed] = useState(null); + const [dataOpen, setDataOpen] = useState(false); + useEffect(() => { + if (!active) return; + let cancelled = false, + busy = false; + const load = async () => { + if (busy || document.hidden) return; + busy = true; + try { + const data = await getArrActivity(range); + if (!cancelled) { + setResult({ range, data }); + setFailed(null); + } + } catch { + if (!cancelled) setFailed(range); + } finally { + busy = false; + } + }; + void load(); + window.addEventListener("torrentarr:statistics-refresh", load); + const timer = liveArr + ? window.setInterval(() => void load(), 60000) + : undefined; + return () => { + cancelled = true; + window.clearInterval(timer); + window.removeEventListener("torrentarr:statistics-refresh", load); + }; + }, [range, active, liveArr]); + const data = result?.range === range ? result.data : null; + const series = groupActivity(data?.series ?? [], grouping); + const shown = series.filter((s) => !hidden.has(`${grouping}:${s.instance}`)); + const count = series[0]?.points.length ?? 0; + const index = Math.min(cursor ?? count - 1, Math.max(0, count - 1)); + const time = series[0]?.points[index]?.timestamp; + const throughput = + metric === "searchesCompleted" + ? "Searches completed" + : "Torrent imports confirmed"; + return ( +
+
+

Arr activity

+
+ +
+ {(["1h", "24h", "7d"] as const).map((value) => ( + + ))} +
+ +
+
+ {failed === range && ( +

+ Arr activity unavailable. + {data ? " Showing the last received history." : ""} +

+ )} + {!data && failed !== range && ( +

Loading Arr activity…

+ )} + {data && !series.length && ( +

+ Collecting activity history. No managed Arr workers yet. +

+ )} + {series.length > 0 && ( + <> +
+ {series.map((s) => { + const key = `${grouping}:${s.instance}`; + return ( + + ); + })} +
+
+ + +
+ +

+ 7 days retained. Queue values are interval averages; completions are + counted when confirmed. Partial worker coverage is labeled and + breaks the line. +

+
setDataOpen(e.currentTarget.open)} + > + View activity data + {dataOpen && ( +
+ + + + + + + + + + + + + + + {shown.flatMap((s) => + s.points.map((p) => ( + + + + + + + + + + )), + )} + +
Observed activity and coverage per interval
Time{grouping === "type" ? "Arr type" : "Worker"}Queue depthSearches completedImports confirmedQueue coverageCompletion coverage
{new Date(p.timestamp).toLocaleString()}{s.instance}{activityValue(p, "queueDepth")}{activityValue(p, "searchesCompleted")}{activityValue(p, "importsConfirmed")} + {p.queueSamples}/{p.expectedSamples} + + {p.completionSamples}/{p.expectedSamples} +
+
+ )} +
+ + )} +
+ ); +} diff --git a/webui/src/components/CatalogStatistics.tsx b/webui/src/components/CatalogStatistics.tsx new file mode 100644 index 00000000..ea601d47 --- /dev/null +++ b/webui/src/components/CatalogStatistics.tsx @@ -0,0 +1,90 @@ +import { useEffect, useState } from "react"; +import { + getCatalogStatistics, + type CatalogStatistics as Statistics, +} from "../api/client"; +import { useWebUI } from "../context/WebUIContext"; +import { BreakdownBar } from "./Statistics"; + +export function CatalogStatistics({ + kind, + instance, + active, +}: { + kind: string; + instance?: string; + active: boolean; +}) { + const { liveArr } = useWebUI(); + const key = `${kind}:${instance ?? "aggregate"}`; + const [result, setResult] = useState<{ + key: string; + data: Statistics; + } | null>(null); + const [failedKey, setFailedKey] = useState(""); + useEffect(() => { + if (!active) return; + let cancelled = false; + let busy = false; + const load = async () => { + if (busy || document.hidden) return; + busy = true; + try { + const data = await getCatalogStatistics(kind, instance); + if (!cancelled) { + setResult({ key, data }); + setFailedKey(""); + } + } catch { + if (!cancelled) setFailedKey(key); + } finally { + busy = false; + } + }; + void load(); + window.addEventListener("torrentarr:statistics-refresh", load); + const timer = liveArr + ? window.setInterval(() => void load(), 60000) + : undefined; + return () => { + cancelled = true; + window.removeEventListener("torrentarr:statistics-refresh", load); + window.clearInterval(timer); + }; + }, [kind, instance, active, liveArr, key]); + const data = result?.key === key ? result.data : null; + if (!data) + return ( +

+ {failedKey === key + ? "Catalog statistics unavailable." + : "Loading catalog statistics…"} +

+ ); + return ( +
+
+ + {data.available.toLocaleString()} / {data.monitored.toLocaleString()}{" "} + monitored {data.unit} available + + + {data.total.toLocaleString()} {data.unit} in catalog · full selected + library + +
+ + {failedKey === key && ( +

+ Refresh unavailable; showing the last received statistics. +

+ )} +
+ ); +} diff --git a/webui/src/components/ConfirmDialog.tsx b/webui/src/components/ConfirmDialog.tsx index fd8c0257..a83e1a93 100644 --- a/webui/src/components/ConfirmDialog.tsx +++ b/webui/src/components/ConfirmDialog.tsx @@ -33,7 +33,7 @@ export function ConfirmDialog({ >

{title}

-
diff --git a/webui/src/components/ShellDialog.tsx b/webui/src/components/ShellDialog.tsx new file mode 100644 index 00000000..6ecd2125 --- /dev/null +++ b/webui/src/components/ShellDialog.tsx @@ -0,0 +1,61 @@ +import { useLayoutEffect, useRef, type ReactNode } from "react"; + +/** Native modal drawers contain focus; display popovers allow light dismissal. */ +export function ShellDialog({ + title, + onClose, + children, + className = "", + popover = false, +}: { + title: string; + onClose: () => void; + children: ReactNode; + className?: string; + popover?: boolean; +}) { + const ref = useRef(null); + useLayoutEffect(() => { + const trigger = document.activeElement as HTMLElement | null; + const dialog = ref.current!; + if (popover) dialog.showPopover(); + else dialog.showModal(); + return () => { + if (popover) dialog.hidePopover(); + else dialog.close(); + trigger?.focus(); + }; + }, [popover]); + return ( + { + if (popover && (event.nativeEvent as ToggleEvent).newState === "closed") + onClose(); + }} + onCancel={(event) => { + event.preventDefault(); + onClose(); + }} + onClick={(event) => { + if (event.target === event.currentTarget) onClose(); + }} + > +
+ {title} + +
+ {children} +
+ ); +} diff --git a/webui/src/components/Statistics.tsx b/webui/src/components/Statistics.tsx new file mode 100644 index 00000000..fc9187d8 --- /dev/null +++ b/webui/src/components/Statistics.tsx @@ -0,0 +1,52 @@ +import type { ReactNode } from "react"; + +export function MetricStrip({ + items, + label, +}: { + label: string; + items: { label: string; value: ReactNode }[]; +}) { + return ( +
+ {items.map((item) => ( +
+
{item.label}
+
{item.value}
+
+ ))} +
+ ); +} + +export function BreakdownBar({ + label, + values, +}: { + label: string; + values: { label: string; value: number; tone: string }[]; +}) { + const total = values.reduce((sum, value) => sum + value.value, 0); + return ( +
+ +
+ {values.map((item) => ( + +