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

View File

@@ -118,268 +118,3 @@ For each task completed:
---
## 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>