feat: cron-based scheduler with auto-download after rescan
- Replace asyncio sleep loop with APScheduler AsyncIOScheduler + CronTrigger
- Add schedule_time (HH:MM), schedule_days (days of week), auto_download_after_rescan fields to SchedulerConfig
- Add _auto_download_missing() to queue missing episodes after rescan
- Reload config live via reload_config(SchedulerConfig) without restart
- Update GET/POST /api/scheduler/config to return {success, config, status} envelope
- Add day-of-week pill toggles to Settings -> Scheduler section in UI
- Update JS loadSchedulerConfig / saveSchedulerConfig for new API shape
- Add 29 unit tests for SchedulerConfig model, 18 unit tests for SchedulerService
- Rewrite 23 endpoint tests and 36 integration tests for APScheduler behaviour
- Coverage: 96% api/scheduler, 95% scheduler_service, 90% total (>= 80% threshold)
- Update docs: API.md, CONFIGURATION.md, features.md, CHANGELOG.md
This commit is contained in:
115
docs/API.md
115
docs/API.md
@@ -660,7 +660,10 @@ Return current application configuration.
|
||||
"data_dir": "data",
|
||||
"scheduler": {
|
||||
"enabled": true,
|
||||
"interval_minutes": 60
|
||||
"interval_minutes": 60,
|
||||
"schedule_time": "03:00",
|
||||
"schedule_days": ["mon", "tue", "wed", "thu", "fri", "sat", "sun"],
|
||||
"auto_download_after_rescan": false
|
||||
},
|
||||
"logging": {
|
||||
"level": "INFO",
|
||||
@@ -691,7 +694,9 @@ Apply an update to the configuration.
|
||||
{
|
||||
"scheduler": {
|
||||
"enabled": true,
|
||||
"interval_minutes": 30
|
||||
"interval_minutes": 60,
|
||||
"schedule_time": "06:30",
|
||||
"schedule_days": ["mon", "wed", "fri"]
|
||||
},
|
||||
"logging": {
|
||||
"level": "DEBUG"
|
||||
@@ -1177,47 +1182,21 @@ Source: [src/server/api/nfo.py](../src/server/api/nfo.py#L637-L684)
|
||||
|
||||
Prefix: `/api/scheduler`
|
||||
|
||||
Source: [src/server/api/scheduler.py](../src/server/api/scheduler.py#L1-L122)
|
||||
All GET/POST config responses share the same envelope:
|
||||
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
"config": { ... },
|
||||
"status": { ... }
|
||||
}
|
||||
```
|
||||
|
||||
Source: [src/server/api/scheduler.py](../src/server/api/scheduler.py)
|
||||
|
||||
### GET /api/scheduler/config
|
||||
|
||||
Get current scheduler configuration.
|
||||
|
||||
**Authentication:** Required
|
||||
|
||||
**Response (200 OK):**
|
||||
|
||||
```json
|
||||
{
|
||||
"enabled": true,
|
||||
"interval_minutes": 60
|
||||
}
|
||||
```
|
||||
|
||||
Source: [src/server/api/scheduler.py](../src/server/api/scheduler.py#L22-L42)
|
||||
|
||||
### POST /api/scheduler/config
|
||||
|
||||
Update scheduler configuration.
|
||||
|
||||
**Authentication:** Required
|
||||
|
||||
**Request Body:**
|
||||
|
||||
```json
|
||||
{
|
||||
"enabled": true,
|
||||
"interval_minutes": 30
|
||||
}
|
||||
```
|
||||
|
||||
**Response (200 OK):** Updated scheduler configuration
|
||||
|
||||
Source: [src/server/api/scheduler.py](../src/server/api/scheduler.py#L45-L75)
|
||||
|
||||
### POST /api/scheduler/trigger-rescan
|
||||
|
||||
Manually trigger a library rescan.
|
||||
Get current scheduler configuration and runtime status.
|
||||
|
||||
**Authentication:** Required
|
||||
|
||||
@@ -1226,11 +1205,65 @@ Manually trigger a library rescan.
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
"config": {
|
||||
"enabled": true,
|
||||
"interval_minutes": 60,
|
||||
"schedule_time": "03:00",
|
||||
"schedule_days": ["mon", "tue", "wed", "thu", "fri", "sat", "sun"],
|
||||
"auto_download_after_rescan": false
|
||||
},
|
||||
"status": {
|
||||
"is_running": true,
|
||||
"next_run": "2025-07-15T03:00:00+00:00",
|
||||
"last_run": null,
|
||||
"scan_in_progress": false
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### POST /api/scheduler/config
|
||||
|
||||
Update scheduler configuration and apply changes immediately.
|
||||
|
||||
**Authentication:** Required
|
||||
|
||||
**Request Body (all fields optional, uses model defaults):**
|
||||
|
||||
```json
|
||||
{
|
||||
"enabled": true,
|
||||
"schedule_time": "06:30",
|
||||
"schedule_days": ["mon", "wed", "fri"],
|
||||
"auto_download_after_rescan": true
|
||||
}
|
||||
```
|
||||
|
||||
**Response (200 OK):** Same envelope as GET, reflecting saved values.
|
||||
|
||||
**Validation errors (422):**
|
||||
|
||||
- `schedule_time` must match `HH:MM` (00:00–23:59)
|
||||
- `schedule_days` entries must be one of `mon tue wed thu fri sat sun`
|
||||
- `interval_minutes` must be ≥ 1
|
||||
|
||||
### POST /api/scheduler/trigger-rescan
|
||||
|
||||
Manually trigger a library rescan (and auto-download if configured).
|
||||
|
||||
**Authentication:** Required
|
||||
|
||||
**Response (200 OK):**
|
||||
|
||||
```json
|
||||
{
|
||||
"message": "Rescan started successfully"
|
||||
}
|
||||
```
|
||||
|
||||
Source: [src/server/api/scheduler.py](../src/server/api/scheduler.py#L78-L122)
|
||||
**Error responses:**
|
||||
|
||||
- `503` — SeriesApp not yet initialised
|
||||
- `500` — Rescan failed unexpectedly
|
||||
|
||||
---
|
||||
|
||||
|
||||
Reference in New Issue
Block a user