7.5 KiB
Typed Scheduler and qBittorrent Polling Implementation Plan
This plan implements the approved design in
docs/superpowers/specs/2026-07-14-scheduled-actions-design.md.
Goal: Move qBittorrent speed collection into a backend-owned typed scheduler while adding per-service controls, run history, stale-data status, and a manual run action.
Scope: qBittorrent speed polling only. The scheduler registry is extensible, but arbitrary widgets, SSH tasks, and other integrations remain out of scope.
Delivery slices
Slice 1 — Contracts and persistence
- Add validated qBittorrent config fields to
backend/src/media_library_viewer_api/integrations/qbittorrent.py:polling_enableddefaulttrue;poll_interval_secondsdefault15, range5..300;sample_retention_secondsdefault1800, range60..86400;sample_max_rowsdefault1200, range60..1200.
- Add migration/default normalization tests for existing qBittorrent records.
- Extend
QbittorrentSampleStoreto prune by retention timestamp and capped row count. - Add a generic scheduler storage concern with run records, indexes, pruning, and cascade deletion by service ID.
- Add typed backend models for scheduler status, run records, samples, and manual-run responses.
Acceptance: Existing service records validate without edits; qBittorrent samples remain isolated by service; scheduler records cannot contain secrets; deletion removes service-owned samples and runs.
Slice 2 — Typed scheduler core
- Create a scheduler action protocol and registry.
- Implement a single-worker, lifespan-managed scheduler coordinator with responsive stop behavior.
- Add qBittorrent speed sampling as the first registered action.
- Extract external polling from
QbittorrentWidgetSourceinto a reusable sampler/action helper. - Implement immediate startup execution with deterministic staggering.
- Implement fixed-delay, no-overlap execution and bounded exponential backoff.
- Reconcile enabled services/config changes on each cycle.
- Add safe structured logs and Prometheus metrics.
- Start/stop the scheduler in
main.pyalongside the existing mail queue and backup poller.
Acceptance: With no frontend open, enabled qBittorrent services append samples; one slow service cannot create overlapping runs or a backlog; shutdown joins the worker; a successful manual or scheduled run resets backoff.
Slice 3 — Read-only APIs
- Add
backend/src/media_library_viewer_api/routers/scheduler.py. - Add status, paginated runs, manual-run, and read-only samples endpoints.
- Keep service configuration writes on the existing service-instance API.
- Change the qBittorrent speed widget adapter to read samples only.
- Add stale-data calculation and safe error truncation.
- Add API tests for disabled/missing services, stale data, pagination, manual runs, backoff, and authentication.
Acceptance: Opening or refreshing a speed widget never contacts qBittorrent and never appends a sample; API responses expose timestamps and status but no credentials.
Slice 4 — Frontend controls and history
- Add scheduler TypeScript types, API functions, and React Query hooks.
- Add qBittorrent schedule controls to the existing schema-driven service editor.
- Add status/backoff/stale-data presentation and a
Run nowaction. - Add user-selectable chart windows.
- Add a paginated run-history table with safe error details.
- Keep UI refreshes separate from sampler cadence.
- Add frontend tests for validation, disabled state, stale warning, manual-run reset, chart-window selection, and run-history rendering.
Acceptance: Operators can configure, inspect, and manually trigger qBittorrent polling from the service surface without opening the dashboard; the chart remains useful during outages and identifies stale data.
Slice 5 — Documentation and operational verification
- Update
docs/REQUIREMENTS.mdwith scheduler requirements and the one-worker constraint. - Update deployment/runbook documentation with scheduler startup, shutdown, and replica guidance.
- Add migration/recovery notes for sample and run-history retention.
- Run backend tests, frontend tests, lint, and build.
- Verify a headless collection scenario against a mocked qBittorrent service.
- Verify project-map artifacts after files are added.
Suggested file map
Backend
| File | Change |
|---|---|
backend/src/media_library_viewer_api/integrations/qbittorrent.py |
Schedule config schema and defaults |
backend/src/media_library_viewer_api/services/qbittorrent_store.py |
Duration/cap pruning and sample queries |
backend/src/media_library_viewer_api/services/service_data.py |
Register scheduler run concern |
backend/src/media_library_viewer_api/services/scheduler.py |
Worker lifecycle, reconciliation, timing, backoff |
backend/src/media_library_viewer_api/services/scheduler_actions.py |
Typed registry and qBittorrent action |
backend/src/media_library_viewer_api/services/scheduler_store.py |
Run-record persistence and pruning |
backend/src/media_library_viewer_api/models/scheduler.py |
Response/request models |
backend/src/media_library_viewer_api/routers/scheduler.py |
Status, history, samples, manual-run API |
backend/src/media_library_viewer_api/widgets/sources.py |
Make qBittorrent speed reads side-effect free |
backend/src/media_library_viewer_api/main.py |
Start/stop scheduler |
backend/tests/test_scheduler.py |
Scheduler lifecycle/timing/backoff tests |
backend/tests/test_scheduler_api.py |
Endpoint and auth tests |
backend/tests/test_service_data.py |
Migration/cascade coverage |
backend/tests/test_widgets.py |
Read-only qBittorrent widget coverage |
Frontend
| File | Change |
|---|---|
frontend/src/types/scheduler.ts |
Scheduler status/run/sample types |
frontend/src/api/scheduler.ts |
Typed endpoint wrappers |
frontend/src/hooks/useScheduler.ts |
Queries and manual-run mutation |
frontend/src/pages/ServicesPage.tsx |
qBittorrent schedule controls/status surface |
frontend/src/pages/ServicePage.tsx or qBittorrent service tab |
Status, chart window, history surface |
frontend/src/widgets/QbittorrentSpeedWidget.tsx |
Read-only sample window and stale warning |
frontend/src/integrations/registry.ts |
Schedule metadata/config exposure if needed |
frontend/src/types/index.ts |
Shared exports |
Risks and mitigations
- Duplicate polling: widget adapter becomes read-only; only scheduler action calls qBittorrent.
- Multiple backend workers: document and log the one-worker constraint; do not silently duplicate work.
- Unbounded storage: prune by both duration and row cap; test pruning under rapid polling.
- Credential leakage: reuse existing secret resolution and sanitize run errors/log fields.
- Scheduler shutdown races: use a stop event, per-action lock, and bounded joins; test lifespan shutdown.
- Config changes during a run: let the current run finish, then reconcile on the next cycle.
- Stale but useful data: return samples plus explicit stale status rather than blanking the chart.
Verification commands
cd backend && PYTHONPATH=src pytest
cd frontend && npm test
cd frontend && npm run lint
cd frontend && npm run build
Do not begin implementation until the final module names, retry cap/jitter, and chart-window response shape are confirmed during the implementation pass.