This commit is contained in:
2026-02-22 10:16:24 +01:00
parent dee2601bda
commit 228964e928
4 changed files with 167 additions and 312 deletions

View File

@@ -14,12 +14,12 @@ NFO files are XML documents that contain metadata about TV shows and episodes. M
### Features ### Features
- **Automatic NFO Creation**: Generate NFO files during downloads - **Automatic NFO Creation**: Generate NFO files during downloads
- **TMDB Integration**: Fetch metadata from The Movie Database - **TMDB Integration**: Fetch metadata from The Movie Database
- **Image Downloads**: Poster, fanart, and logo images - **Image Downloads**: Poster, fanart, and logo images
- **Batch Operations**: Create/update NFO files for multiple anime - **Batch Operations**: Create/update NFO files for multiple anime
- **Web UI**: Manage NFO settings and operations - **Web UI**: Manage NFO settings and operations
- **API Access**: Programmatic NFO management - **API Access**: Programmatic NFO management
--- ---
@@ -254,7 +254,7 @@ NFO files are created in the anime directory:
**Query Parameters**: **Query Parameters**:
- `folder_path` (required): Absolute path to anime directory - `folder_path` (required): Absolute path to anime directory
**Response**: **Response**:
@@ -329,7 +329,7 @@ NFO files are created in the anime directory:
**Query Parameters**: **Query Parameters**:
- `file_path` (required): Absolute path to NFO file - `file_path` (required): Absolute path to NFO file
**Response**: **Response**:
@@ -347,7 +347,7 @@ NFO files are created in the anime directory:
**Query Parameters**: **Query Parameters**:
- `folder_path` (required): Absolute path to anime directory - `folder_path` (required): Absolute path to anime directory
**Response**: **Response**:
@@ -529,54 +529,54 @@ NFO files are created in the anime directory:
### 7.1 Configuration Recommendations ### 7.1 Configuration Recommendations
- **Image Size**: Use `w500` for optimal balance of quality and storage - **Image Size**: Use `w500` for optimal balance of quality and storage
- **Auto-create**: Enable for new downloads - **Auto-create**: Enable for new downloads
- **Update on scan**: Disable to avoid unnecessary TMDB API calls - **Update on scan**: Disable to avoid unnecessary TMDB API calls
- **Poster**: Always enable for show and episode thumbnails - **Poster**: Always enable for show and episode thumbnails
- **Logo/Fanart**: Enable only if your media server supports them - **Logo/Fanart**: Enable only if your media server supports them
### 7.2 Maintenance ### 7.2 Maintenance
- **Regular Updates**: Update NFO files quarterly to get latest metadata - **Regular Updates**: Update NFO files quarterly to get latest metadata
- **Backup**: Include NFO files in your backup strategy - **Backup**: Include NFO files in your backup strategy
- **Validation**: Periodically check missing NFOs using `/api/nfo/missing` - **Validation**: Periodically check missing NFOs using `/api/nfo/missing`
- **API Rate Limits**: Be mindful of TMDB API rate limits when batch processing - **API Rate Limits**: Be mindful of TMDB API rate limits when batch processing
### 7.3 Performance ### 7.3 Performance
- **Batch Operations**: Use batch endpoints for multiple anime - **Batch Operations**: Use batch endpoints for multiple anime
- **Off-Peak Processing**: Create NFOs during low-activity periods - **Off-Peak Processing**: Create NFOs during low-activity periods
- **Image Optimization**: Use smaller image sizes for large libraries - **Image Optimization**: Use smaller image sizes for large libraries
- **Selective Updates**: Only update NFOs when metadata changes - **Selective Updates**: Only update NFOs when metadata changes
### 7.4 Media Server Integration ### 7.4 Media Server Integration
#### Plex #### Plex
- Use "Personal Media Shows" agent - Use "Personal Media Shows" agent
- Enable "Local Media Assets" scanner - Enable "Local Media Assets" scanner
- Place NFO files in anime directories - Place NFO files in anime directories
- Refresh metadata after creating NFOs - Refresh metadata after creating NFOs
#### Jellyfin #### Jellyfin
- Use "NFO" metadata provider - Use "NFO" metadata provider
- Enable in Library settings - Enable in Library settings
- Order providers: NFO first, then online sources - Order providers: NFO first, then online sources
- Scan library after NFO creation - Scan library after NFO creation
#### Emby #### Emby
- Enable "NFO" metadata reader - Enable "NFO" metadata reader
- Configure in Library advanced settings - Configure in Library advanced settings
- Use "Prefer embedded metadata" option - Use "Prefer embedded metadata" option
- Refresh metadata after updates - Refresh metadata after updates
#### Kodi #### Kodi
- NFO files are automatically detected - NFO files are automatically detected
- No additional configuration needed - No additional configuration needed
- Update library to see changes - Update library to see changes
--- ---
@@ -629,10 +629,10 @@ curl -X POST "http://127.0.0.1:8000/api/scheduler/config" \
## 9. Related Documentation ## 9. Related Documentation
- [API.md](API.md) - Complete API reference - [API.md](API.md) - Complete API reference
- [CONFIGURATION.md](CONFIGURATION.md) - All configuration options - [CONFIGURATION.md](CONFIGURATION.md) - All configuration options
- [ARCHITECTURE.md](ARCHITECTURE.md) - System architecture - [ARCHITECTURE.md](ARCHITECTURE.md) - System architecture
- [DEVELOPMENT.md](DEVELOPMENT.md) - Development guide - [DEVELOPMENT.md](DEVELOPMENT.md) - Development guide
--- ---
@@ -640,9 +640,9 @@ curl -X POST "http://127.0.0.1:8000/api/scheduler/config" \
### Getting Help ### Getting Help
- Check logs in `logs/` directory for error details - Check logs in `logs/` directory for error details
- Review [TESTING.md](TESTING.md) for test coverage - Review [TESTING.md](TESTING.md) for test coverage
- Consult [DATABASE.md](DATABASE.md) for NFO status schema - Consult [DATABASE.md](DATABASE.md) for NFO status schema
### Common Issues ### Common Issues
@@ -650,6 +650,6 @@ See section 6 (Troubleshooting) for solutions to common problems.
### TMDB Resources ### TMDB Resources
- TMDB API Documentation: https://developers.themoviedb.org/3 - TMDB API Documentation: https://developers.themoviedb.org/3
- TMDB Support: https://www.themoviedb.org/talk - TMDB Support: https://www.themoviedb.org/talk
- TMDB API Status: https://status.themoviedb.org/ - TMDB API Status: https://status.themoviedb.org/

View File

@@ -118,268 +118,3 @@ For each task completed:
--- ---
## TODO List: ## TODO List:
### Task 1: Add APScheduler Dependency ✅
- [x] **1.1** Add `APScheduler>=3.10.4` to `requirements.txt`
- [x] **1.2** Verify installation: `conda run -n AniWorld pip install APScheduler>=3.10.4`
- [x] **1.3** Verify import works: `conda run -n AniWorld python -c "from apscheduler.schedulers.asyncio import AsyncIOScheduler; from apscheduler.triggers.cron import CronTrigger; print('OK')"`
---
### Task 2: Extend SchedulerConfig Model ✅
**File:** `src/server/models/config.py`
The existing `SchedulerConfig` has `enabled: bool` and `interval_minutes: int`. Extend it with cron-based fields.
- [x] **2.1** Add field `schedule_time: str = "03:00"` — 24h HH:MM format for the daily run time
- [x] **2.2** Add field `schedule_days: List[str]` with default `["mon","tue","wed","thu","fri","sat","sun"]` — lowercase 3-letter day abbreviations
- [x] **2.3** Add field `auto_download_after_rescan: bool = False` — whether to auto-queue and start downloads of all missing episodes after a scheduled rescan completes
- [x] **2.4** Add a `field_validator` for `schedule_time` that validates HH:MM format (00:0023:59). Raise `ValueError` with a clear message on invalid input
- [x] **2.5** Add a `field_validator` for `schedule_days` that validates each entry is one of `["mon","tue","wed","thu","fri","sat","sun"]`. Raise `ValueError` on invalid entries. Empty list is allowed (means scheduler won't run)
- [x] **2.6** Keep `interval_minutes` field for backward compatibility but it will no longer be used when `schedule_time` is set. Add a docstring noting this deprecation
- [x] **2.7** Update `data/config.json` default to include the new fields with their defaults
**Verification:**
- [x] **2.8** Run: `conda run -n AniWorld python -c "from src.server.models.config import SchedulerConfig; c = SchedulerConfig(); print(c.schedule_time, c.schedule_days, c.auto_download_after_rescan)"`
- [x] **2.9** Verify invalid time raises error: `SchedulerConfig(schedule_time='25:00')` should raise `ValidationError`
- [x] **2.10** Verify invalid day raises error: `SchedulerConfig(schedule_days=['monday'])` should raise `ValidationError`
---
### Task 3: Write Unit Tests for SchedulerConfig Model ✅
**File:** `tests/unit/test_scheduler_config_model.py` (new file)
- [x] **3.1** Test default values: `SchedulerConfig()` creates instance with `schedule_time="03:00"`, `schedule_days` = all 7 days, `auto_download_after_rescan=False`
- [x] **3.2** Test valid `schedule_time` values: `"00:00"`, `"03:00"`, `"12:30"`, `"23:59"`
- [x] **3.3** Test invalid `schedule_time` values: `"25:00"`, `"3pm"`, `""`, `"3:00pm"`, `"24:00"`, `"-1:00"` — all must raise `ValidationError`
- [x] **3.4** Test valid `schedule_days` values: `["mon"]`, `["mon","fri"]`, all 7 days, empty list `[]`
- [x] **3.5** Test invalid `schedule_days` values: `["monday"]`, `["xyz"]`, `["Mon"]` (case-sensitive), `[""]` — all must raise `ValidationError`
- [x] **3.6** Test `auto_download_after_rescan` accepts `True` and `False`
- [x] **3.7** Test backward compatibility: creating `SchedulerConfig(enabled=True, interval_minutes=30)` without new fields uses defaults for `schedule_time`, `schedule_days`, `auto_download_after_rescan`
- [x] **3.8** Test serialization roundtrip: `config.model_dump()` then `SchedulerConfig(**dumped)` produces identical config
**Verification:**
- [x] **3.9** Run: `conda run -n AniWorld python -m pytest tests/unit/test_scheduler_config_model.py -v` — all tests pass
---
### Task 4: Rewrite SchedulerService to Use APScheduler with CronTrigger ✅
**File:** `src/server/services/scheduler_service.py`
The existing service used a custom `asyncio.sleep()` loop in `_scheduler_loop()`. Replaced with APScheduler's `AsyncIOScheduler` and `CronTrigger`.
- [x] **4.1** Add imports: `from apscheduler.schedulers.asyncio import AsyncIOScheduler` and `from apscheduler.triggers.cron import CronTrigger`
- [x] **4.2** Add a private method `_build_cron_trigger(self) -> CronTrigger` that converts `self._config.schedule_time` (HH:MM) and `self._config.schedule_days` (list of day abbreviations) into a `CronTrigger(hour=H, minute=M, day_of_week='mon,wed,fri')`. If `schedule_days` is empty, return `None` (no trigger = don't schedule)
- [x] **4.3** Refactor `start()`: instead of creating an `asyncio.Task` with `_scheduler_loop`, create an `AsyncIOScheduler` instance, add a job using the `CronTrigger` from `_build_cron_trigger()`, and call `scheduler.start()`. Store the scheduler as `self._scheduler`. If `_build_cron_trigger()` returns `None`, log a warning and don't add a job. Set `self._is_running = True`
- [x] **4.4** Refactor `stop()`: call `self._scheduler.shutdown(wait=False)` instead of cancelling an asyncio task. Set `self._is_running = False`
- [x] **4.5** Add method `reload_config(self, config: SchedulerConfig) -> None`: update `self._config`, then if scheduler is running, reschedule the job using `self._scheduler.reschedule_job()` with the new cron trigger. If trigger is `None`, remove the job. If scheduler is not running and config is enabled, call `start()`
- [x] **4.6** Remove or deprecate `_scheduler_loop()` method entirely
- [x] **4.7** Update `get_status()` to return `next_run_time` obtained from `self._scheduler.get_job('scheduled_rescan').next_run_time` (handle case where job doesn't exist). Also include `schedule_time`, `schedule_days`, and `auto_download_after_rescan` in status dict
- [x] **4.8** The job function should be `_perform_rescan()` (existing method) — keep it as the scheduled task. It is already async
- [x] **4.9** Add structured logging: log when scheduler starts, stops, reschedules, and when cron trigger is built (include the cron expression in the log)
- [x] **4.10** Ensure the file stays under 500 lines. If it exceeds, extract helper functions
**Verification:**
- [x] **4.11** Start the app: `conda run -n AniWorld python -m uvicorn src.server.fastapi_app:app --host 127.0.0.1 --port 8000`. Check logs for scheduler startup messages
- [x] **4.12** Verify no import errors or startup crashes
---
### Task 5: Implement Auto-Download After Rescan Logic ✅
**File:** `src/server/services/scheduler_service.py`
- [x] **5.1** At the end of `_perform_rescan()`, after the scan completes successfully, check `self._config.auto_download_after_rescan`
- [x] **5.2** If enabled: call `self._anime_service.get_all_anime()` to get all series. For each series that has missing episodes (non-empty `missing_episodes` list), call `self._download_service.add_to_queue()` for each missing episode.
- [x] **5.3** After all episodes are queued, call `self._download_service.start_download()` (or equivalent method from the existing download service) to begin processing the queue
- [x] **5.4** Broadcast a WebSocket event `auto_download_started` with `{"queued_count": N}` where N is the number of episodes queued. Use the existing `_broadcast()` helper method
- [x] **5.5** Wrap the auto-download logic in try/except. On failure, broadcast `auto_download_error` with `{"error": str(e)}` and log the error. Do NOT let auto-download failures crash the scheduler
- [x] **5.6** If `auto_download_after_rescan` is `False`, skip the auto-download logic entirely (just log that auto-download is disabled)
- [x] **5.7** Add structured logging for: auto-download started, number of episodes queued, auto-download completed, auto-download failed
**Verification:**
- [x] **5.8** Read `src/server/services/download_service.py` to confirm the exact method names and signatures used for queueing and starting downloads. Use those exact method names
---
### Task 6: Update Scheduler API Endpoints ✅
**File:** `src/server/api/scheduler.py`
- [x] **6.1** Update **GET `/api/scheduler/config`**: Return response that includes the new config fields (`schedule_time`, `schedule_days`, `auto_download_after_rescan`) plus runtime status (`is_running`, `next_run`, `last_run`). The response shape should be `{"success": true, "config": {...all config fields...}, "status": {...runtime fields...}}`
- [x] **6.2** Update **POST `/api/scheduler/config`**: Accept the new fields in the request body. Validate using the updated `SchedulerConfig` model. After saving config via `config_service`, call `scheduler_service.reload_config(new_config)` to apply changes immediately without restart. Return the same enriched response format as GET
- [x] **6.3** Ensure backward compatibility: if a POST request only sends `{"enabled": true}` without new fields, use existing/default values for the omitted fields
- [x] **6.4** Add proper error responses: 422 for validation errors (invalid time/days), 500 for scheduler failures
- [x] **6.5** The existing **POST `/api/scheduler/trigger-rescan`** endpoint should remain unchanged — it triggers an immediate rescan (which will also auto-download if configured)
**Verification:**
- [x] **6.6** Start the app and test with curl:
- `curl -H "Authorization: Bearer <token>" http://127.0.0.1:8000/api/scheduler/config` — should return new fields
- `curl -X POST -H "Authorization: Bearer <token>" -H "Content-Type: application/json" -d '{"enabled":true,"schedule_time":"02:00","schedule_days":["mon","wed","fri"],"auto_download_after_rescan":true}' http://127.0.0.1:8000/api/scheduler/config` — should save and return updated config
---
### Task 7: Update Frontend HTML — Scheduler Settings Section ✅
**File:** `src/server/web/templates/index.html`
- [x] **7.1** Keep the existing "Enable Scheduler" checkbox (`scheduler-enabled`)
- [x] **7.2** Replace or supplement the `scheduler-interval` (minutes) input with a **time picker input** (`type="time"`, id `scheduler-time`). Set default value to `03:00`. Add a label: "Run at" or equivalent with `data-text` attribute for i18n
- [x] **7.3** Add **7 day-of-week toggle checkboxes** (MonSun) with ids `scheduler-day-mon` through `scheduler-day-sun`. All checked by default. Display them in a horizontal row. Each checkbox label should have a `data-text` attribute for i18n. Use short labels: "Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun"
- [x] **7.4** Keep the existing "Auto-download after rescan" checkbox (it already exists in the HTML as `auto-download-checkbox` around line 292). Ensure it maps to `auto_download_after_rescan`
- [x] **7.5** Hide or remove the old `scheduler-interval` minutes input. If keeping for backward compat, move it to a collapsible "Advanced" section
- [x] **7.6** Add a help text below the day checkboxes: "Scheduler runs at the selected time on checked days. Uncheck all to disable scheduling." — with `data-text` for i18n
- [x] **7.7** Add a display area showing "Next scheduled run: <datetime>" that gets populated by JS (id `scheduler-next-run`)
- [x] **7.8** Ensure all new elements work in both dark and light themes (use existing CSS classes)
**Verification:**
- [ ] **7.9** Start the app, open the settings modal, verify all new UI elements render correctly in both dark and light mode
- [ ] **7.10** Verify the day-of-week checkboxes are displayed horizontally and are all checked by default
---
### Task 8: Update Frontend CSS — Day-of-Week Toggle Styling
**File:** `src/server/web/static/css/styles.css`
> ⚠️ **NOT YET DONE** — The HTML elements exist but CSS classes `.scheduler-days-container`, `.scheduler-day-toggle`, `.scheduler-day-label` are not defined in `styles.css`.
- [ ] **8.1** Add styles for `.scheduler-days-container`: horizontal flexbox layout, gap between items, wrapping on small screens
- [ ] **8.2** Add styles for `.scheduler-day-toggle` checkboxes: styled as pill/button toggles. Unchecked = outline/muted, Checked = accent/filled. Use existing theme CSS variables for colors
- [ ] **8.3** Add styles for `.scheduler-day-label`: centered text, pointer cursor, appropriate padding
- [ ] **8.4** Ensure dark mode support using existing dark mode CSS variables/selectors (check existing dark mode patterns in the file)
- [ ] **8.5** Ensure responsive layout: on mobile, days should wrap to 2 rows if needed
- [ ] **8.6** Style the `#scheduler-next-run` display: muted text, smaller font size, italic
**Verification:**
- [ ] **8.7** Test in browser: verify day toggles look like toggle buttons, dark/light mode both work, responsive on narrow viewport
---
### Task 9: Update Frontend JavaScript — Scheduler Config Load/Save ✅
**File:** `src/server/web/static/js/app.js`
- [x] **9.1** Update `loadSchedulerConfig()` — reads `config.schedule_time`, `config.schedule_days`, `config.auto_download_after_rescan`, and `status.next_run`
- [x] **9.2** Update `saveSchedulerConfig()` — sends `schedule_time`, `schedule_days`, `auto_download_after_rescan`, and `enabled` to POST `/api/scheduler/config`
- [x] **9.3** Update `toggleSchedulerTimeInput()` — toggles visibility of day checkboxes and next-run display when scheduler is enabled/disabled
- [x] **9.4** Handle WebSocket events `auto_download_started` and `auto_download_error` with toast notifications
**Verification:**
- [ ] **9.5** Start the app, open settings, change time to `02:00`, uncheck "Sat" and "Sun", enable auto-download, save. Reload the page and verify values persist
- [ ] **9.6** Check browser console for any JS errors during load/save
- [ ] **9.7** Verify the "Next scheduled run" display updates after saving
---
### Task 10: Update Config Service for Backward Compatibility ✅
**File:** `src/server/services/config_service.py`
- [x] **10.1** Ensure `load_config()` handles old `config.json` files that don't have `schedule_time`, `schedule_days`, or `auto_download_after_rescan` — Pydantic defaults handle this automatically
- [x] **10.2** Ensure `save_config()` writes the new fields to disk
- [x] **10.3** Pydantic model defaults handle migration automatically; no explicit migration step required
**Verification:**
- [ ] **10.4** Delete `schedule_time`, `schedule_days`, and `auto_download_after_rescan` from `data/config.json`, restart the app, verify defaults are used and the app doesn't crash
- [ ] **10.5** Verify the config is saved with the new fields after any settings change
---
### Task 11: Update FastAPI Lifespan for APScheduler ✅
**File:** `src/server/fastapi_app.py`
- [x] **11.1** Verify that the `lifespan` function calls `scheduler_service.start()` on startup — initializes the APScheduler. Confirmed working
- [x] **11.2** Verify that `scheduler_service.stop()` is called on shutdown — calls `scheduler.shutdown()`. Confirmed working
- [x] **11.3** Ensure no duplicate scheduler instances are created (singleton pattern in `get_scheduler_service()` prevents this)
**Verification:**
- [ ] **11.4** Start and stop the app, verify no errors in logs about scheduler startup/shutdown
- [ ] **11.5** Verify logs show "Scheduler started with cron trigger: ..." or similar
---
### Task 12: Write Unit Tests for SchedulerService (APScheduler + Auto-Download) ✅
**File:** `tests/unit/test_scheduler_service.py`
- [x] **12.1** Test `_build_cron_trigger()` with various input combinations
- [x] **12.2** Test `start()` — mocks `AsyncIOScheduler`, verifies `add_job()` and `scheduler.start()` called
- [x] **12.3** Test `start()` with empty `schedule_days` — no job added, warning logged
- [x] **12.4** Test `stop()` — verifies `scheduler.shutdown(wait=False)` called
- [x] **12.5** Test `reload_config()` — verifies `reschedule_job()` called with new CronTrigger
- [x] **12.6** Test `reload_config()` with empty `schedule_days` — verifies job removed
- [x] **12.7** Test `_perform_rescan()` with `auto_download_after_rescan=True`
- [x] **12.8** Test `_perform_rescan()` with `auto_download_after_rescan=False`
- [x] **12.9** Test `_perform_rescan()` auto-download error handling
- [x] **12.10** Test `get_status()` returns correct fields
- [x] **12.11** Removed tests for old `_scheduler_loop()` method
- [x] **12.12** File stays under 500 lines
**Verification:**
- [ ] **12.13** Run: `conda run -n AniWorld python -m pytest tests/unit/test_scheduler_service.py -v --tb=short` — all tests pass
- [ ] **12.14** Run: `conda run -n AniWorld python -m pytest tests/unit/test_scheduler_config_model.py -v --tb=short` — all tests pass
- [ ] **12.15** Run coverage: `conda run -n AniWorld python -m pytest tests/unit/test_scheduler_service.py tests/unit/test_scheduler_config_model.py --cov=src/server/services/scheduler_service --cov=src/server/models/config --cov-report=term-missing` — target ≥80% on both modules
---
### Task 13: Write/Update API Endpoint Tests ✅
**File:** `tests/api/test_scheduler_endpoints.py`
- [x] **13.1** Test GET `/api/scheduler/config` returns response with new fields
- [x] **13.2** Test POST `/api/scheduler/config` with valid new fields saves correctly
- [x] **13.3** Test POST with invalid `schedule_time` (`"25:00"`) returns 422 validation error
- [x] **13.4** Test POST with invalid `schedule_days` (`["monday"]`) returns 422 validation error
- [x] **13.5** Test POST with only `{"enabled": true}` (backward compat) — defaults used
- [x] **13.6** Test POST with `schedule_days: []` — valid, scheduler has no scheduled jobs
- [x] **13.7** Test trigger-rescan endpoint still works correctly
**Verification:**
- [ ] **13.8** Run: `conda run -n AniWorld python -m pytest tests/api/test_scheduler_endpoints.py -v --tb=short` — all tests pass
---
### Task 14: Write/Update Integration Tests ✅
**File:** `tests/integration/test_scheduler_workflow.py`
- [x] **14.1** Test full workflow: Enable scheduler with `schedule_time="02:00"` and `schedule_days=["mon","fri"]`
- [x] **14.2** Test config change workflow: Change `schedule_time` via API → job rescheduled
- [x] **14.3** Test disable/re-enable workflow
- [x] **14.4** Test auto-download integration with `auto_download_after_rescan=True`
- [x] **14.5** Test auto-download disabled scenario
**Verification:**
- [ ] **14.6** Run: `conda run -n AniWorld python -m pytest tests/integration/test_scheduler_workflow.py -v --tb=short` — all tests pass
---
### Task 15: Run Full Test Suite and Verify Coverage
- [ ] **15.1** Run all tests: `conda run -n AniWorld python -m pytest tests/ -v --tb=short` — all tests pass, no regressions
- [ ] **15.2** Run coverage for scheduler-related code: `conda run -n AniWorld python -m pytest tests/ --cov=src/server/services/scheduler_service --cov=src/server/models/config --cov=src/server/api/scheduler --cov-report=term-missing` — all three modules ≥80%
- [ ] **15.3** Fix any failing tests from other test files that may have been affected by the config model changes
- [ ] **15.4** Run: `conda run -n AniWorld python -m pytest tests/ -v -x` — stop at first failure, fix, repeat until all green
---
### Task 16: Update Documentation ✅
- [x] **16.1** Updated `docs/features.md`: Cron-based scheduling, auto-download after rescan, and time + day-of-week configuration documented
- [x] **16.2** Updated `docs/CONFIGURATION.md`: `scheduler.schedule_time`, `scheduler.schedule_days`, `scheduler.auto_download_after_rescan` config fields documented with table and examples
- [x] **16.3** Updated `docs/API.md`: GET/POST `/api/scheduler/config` request/response schemas documented with new fields
- [x] **16.4** Updated `docs/instructions.md`: Tasks 116 marked as completed
- [x] **16.5** Updated `docs/CHANGELOG.md`: Cron scheduler feature entry added under `[Unreleased]`
---
### Task 17: Final Manual Verification
- [ ] **17.1** Start the app: `conda run -n AniWorld python -m uvicorn src.server.fastapi_app:app --host 127.0.0.1 --port 8000 --reload`
- [ ] **17.2** Log in with admin credentials (`admin` / `Hallo123!`)
- [ ] **17.3** Open settings modal → Scheduler section. Verify: time picker shows `03:00`, all 7 days are checked, auto-download is unchecked
- [ ] **17.4** Change time to `02:00`, uncheck Sat and Sun, enable auto-download, save. Verify toast notification confirms save
- [ ] **17.5** Reload the page, reopen settings. Verify saved values persist
- [ ] **17.6** Check terminal logs for: "Scheduler rescheduled with cron trigger: ..." message
- [ ] **17.7** Trigger manual rescan. If auto-download is enabled and there are missing episodes, verify episodes appear in download queue
- [ ] **17.8** Disable scheduler entirely, save. Verify logs show scheduler stopped
- [ ] **17.9** Re-enable scheduler, save. Verify logs show scheduler started with correct cron expression
- [ ] **17.10** Test dark mode and light mode — all scheduler UI elements render correctly
- [ ] **17.11** Test on narrow viewport (mobile) — day checkboxes wrap properly (note: CSS Task 8 must be done first)
---
### Task 18: Commit and Clean Up
- [ ] **18.1** Review all changed files for adherence to architecture principles (single responsibility, <500 lines, type hints, docstrings)
- [ ] **18.2** Run linter: `conda run -n AniWorld python -m flake8 src/server/services/scheduler_service.py src/server/models/config.py src/server/api/scheduler.py` — no critical issues
- [x] **18.3** Commit with message: `feat: cron-based scheduler with auto-download after rescan`
- [ ] **18.4** Mark this feature as complete in `docs/instructions.md` TODO list

37
tvshow.nfo.bad Normal file
View File

@@ -0,0 +1,37 @@
<?xml version="1.0" encoding="utf-8" standalone="yes"?>
<tvshow>
<plot />
<outline />
<lockdata>false</lockdata>
<dateadded>2026-02-14 14:40:13</dateadded>
<title>86 Eighty Six</title>
<rating>8.066</rating>
<collectionnumber>100565</collectionnumber>
<imdb_id>tt13718450</imdb_id>
<tmdbid>100565</tmdbid>
<tvdbid>378609</tvdbid>
<art>
<poster>/media/Serien/86 Eighty Six/poster.jpg</poster>
<fanart>/media/Serien/86 Eighty Six/fanart.jpg</fanart>
</art>
<id>378609</id>
<episodeguide>
<url cache="378609.xml">http://www.thetvdb.com/api/1D62F2F90030C444/series/378609/all/de.zip</url>
</episodeguide>
<season>-1</season>
<episode>-1</episode>
<showtitle>86: Eighty Six</showtitle>
<ratings>
<rating name="themoviedb" max="10" default="true">
<value>8.066</value>
<votes>211</votes>
</rating>
</ratings>
<uniqueid type="tmdb">100565</uniqueid>
<uniqueid type="imdb">tt13718450</uniqueid>
<uniqueid type="tvdb" default="true">378609</uniqueid>
<thumb aspect="poster">https://image.tmdb.org/t/p/original/aEG1ZtdyjDFDtlGqovvAHsAfsR6.jpg</thumb>
<fanart>
<thumb>https://image.tmdb.org/t/p/original/lSlL2CAPSDJ9gf2MZX0x2u2inKX.jpg</thumb>
</fanart>
</tvshow>

83
tvshow.nfo.good Normal file
View File

@@ -0,0 +1,83 @@
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<tvshow>
<title>Farming Life in Another World</title>
<originaltitle>異世界のんびり農家</originaltitle>
<showtitle>Farming Life in Another World</showtitle>
<year>2023</year>
<plot>Nachdem Hiraku einer schweren Krankheit erliegt, schenkt Gott ihm neues Leben. Hiraku wird seine Gesundheit sowie Jugend neu gewährt und er wird in eine Fantasiewelt seiner Wahl geschickt. Außerdem übergibt ihm Gott das allmächtige Ackergerät, um seine zweite Chance zu nutzen und sein Leben umzupflügen.</plot>
<runtime>24</runtime>
<premiered>2023-01-06</premiered>
<status>Returning Series</status>
<ratings>
<rating name="themoviedb" max="10" default="true">
<value>7.57</value>
<votes>114</votes>
</rating>
</ratings>
<tmdbid>196285</tmdbid>
<imdbid>tt19223420</imdbid>
<tvdbid>418367</tvdbid>
<id>418367</id>
<imdb_id>tt19223420</imdb_id>
<uniqueid type="tmdb">196285</uniqueid>
<uniqueid type="imdb">tt19223420</uniqueid>
<uniqueid type="tvdb" default="true">418367</uniqueid>
<genre>Animation</genre>
<genre>Komödie</genre>
<genre>Sci-Fi &amp; Fantasy</genre>
<studio>AT-X</studio>
<country>Japan</country>
<thumb aspect="poster">https://image.tmdb.org/t/p/original/mE4pE6NOV3AbvTUE3MkFMlfs12n.jpg</thumb>
<fanart>
<thumb>https://image.tmdb.org/t/p/original/6XJ0XJbL14YkThOe1iU5TJKU5l3.jpg</thumb>
</fanart>
<actor>
<name>Atsushi Abe</name>
<role>Machio Hiraku (voice)</role>
<thumb>https://image.tmdb.org/t/p/h632/b59PickRsrhT6kaUKN2dD8c7ip.jpg</thumb>
<tmdbid>1154449</tmdbid>
</actor>
<actor>
<name>Shino Shimoji</name>
<role>Rurushi Ru (voice)</role>
<thumb>https://image.tmdb.org/t/p/h632/nw2Zrgzcb0kLgUCHL3lIoy8qYiH.jpg</thumb>
<tmdbid>1780773</tmdbid>
</actor>
<actor>
<name>Aya Suzaki</name>
<role>Tia (voice)</role>
<thumb>https://image.tmdb.org/t/p/h632/eCbCtnzFqr4aTpe3arDeRGazP5K.jpg</thumb>
<tmdbid>1258548</tmdbid>
</actor>
<actor>
<name>Lynn</name>
<role>Ria (voice)</role>
<thumb>https://image.tmdb.org/t/p/h632/eJ2NqgzpnzNbT6Nt9EpDfzqNeZM.jpg</thumb>
<tmdbid>1691384</tmdbid>
</actor>
<actor>
<name>Yukiyo Fujii</name>
<role>Ann (voice)</role>
<thumb>https://image.tmdb.org/t/p/h632/tLG4K1iix3QNHFexf98mrZ25jT6.jpg</thumb>
<tmdbid>1287794</tmdbid>
</actor>
<actor>
<name>Machico</name>
<role>Sena (voice)</role>
<thumb>https://image.tmdb.org/t/p/h632/hocukD5IKqBDcrcXfcQJhgk4jJl.jpg</thumb>
<tmdbid>1842285</tmdbid>
</actor>
<actor>
<name>Natsumi Hioka</name>
<role>Lastismun (voice)</role>
<thumb>https://image.tmdb.org/t/p/h632/jCXosIVhe3QuTjbHFlemTSZJ53u.jpg</thumb>
<tmdbid>2043754</tmdbid>
</actor>
<actor>
<name>Miyu Tomita</name>
<role>Flora (voice)</role>
<thumb>https://image.tmdb.org/t/p/h632/rSR17l4HdchLkhpRuAZbGbXNmUS.jpg</thumb>
<tmdbid>1647636</tmdbid>
</actor>
<watched>false</watched>
</tvshow>