Optimize startup: skip series loading on every SeriesApp init
- SeriesApp now passes skip_load=True to SerieList - Prevents redundant data file loading on every startup - Series loaded once during setup via sync_series_from_data_files() - Removed obsolete _init_list_sync() and _init_list() methods - Updated documentation in ARCHITECTURE.md and README.md
This commit is contained in:
56
README.md
56
README.md
@@ -4,22 +4,22 @@ A web-based anime download manager with REST API, WebSocket real-time updates, a
|
||||
|
||||
## Features
|
||||
|
||||
- Web interface for managing anime library
|
||||
- REST API for programmatic access
|
||||
- WebSocket real-time progress updates
|
||||
- Download queue with priority management
|
||||
- Automatic library scanning for missing episodes
|
||||
- **NFO metadata management with TMDB integration**
|
||||
- **Automatic poster/fanart/logo downloads**
|
||||
- JWT-based authentication
|
||||
- SQLite database for persistence
|
||||
- Web interface for managing anime library
|
||||
- REST API for programmatic access
|
||||
- WebSocket real-time progress updates
|
||||
- Download queue with priority management
|
||||
- Automatic library scanning for missing episodes
|
||||
- **NFO metadata management with TMDB integration**
|
||||
- **Automatic poster/fanart/logo downloads**
|
||||
- JWT-based authentication
|
||||
- SQLite database for persistence
|
||||
|
||||
## Quick Start
|
||||
|
||||
### Prerequisites
|
||||
|
||||
- Python 3.10+
|
||||
- Conda (recommended) or virtualenv
|
||||
- Python 3.10+
|
||||
- Conda (recommended) or virtualenv
|
||||
|
||||
### Installation
|
||||
|
||||
@@ -144,12 +144,34 @@ conda run -n AniWorld python -m pytest tests/integration/ -v
|
||||
|
||||
## Technology Stack
|
||||
|
||||
- **Web Framework**: FastAPI 0.104.1
|
||||
- **Database**: SQLite + SQLAlchemy 2.0
|
||||
- **Auth**: JWT (python-jose) + passlib
|
||||
- **Validation**: Pydantic 2.5
|
||||
- **Logging**: structlog
|
||||
- **Testing**: pytest + pytest-asyncio
|
||||
- **Web Framework**: FastAPI 0.104.1
|
||||
- **Database**: SQLite + SQLAlchemy 2.0
|
||||
- **Auth**: JWT (python-jose) + passlib
|
||||
- **Validation**: Pydantic 2.5
|
||||
- **Logging**: structlog
|
||||
- **Testing**: pytest + pytest-asyncio
|
||||
|
||||
## Application Lifecycle
|
||||
|
||||
### Initialization
|
||||
|
||||
On first startup, the application performs a one-time sync of series from data files to the database:
|
||||
|
||||
1. FastAPI lifespan starts
|
||||
2. Database is initialized
|
||||
3. `sync_series_from_data_files()` reads all data files from the anime directory
|
||||
4. Series metadata is synced to the database
|
||||
5. `SeriesApp` loads series from database (not from files)
|
||||
|
||||
On subsequent startups, `SeriesApp` initializes with an empty series list (`skip_load=True`). Series are loaded from the database by the service layer as needed, avoiding redundant file system scans.
|
||||
|
||||
### Adding New Series
|
||||
|
||||
When adding a new series:
|
||||
|
||||
1. Series is added to the database via `AnimeService`
|
||||
2. Data file is created in the anime directory
|
||||
3. In-memory `SerieList` is updated via `load_series_from_list()`
|
||||
|
||||
## License
|
||||
|
||||
|
||||
@@ -220,6 +220,10 @@ src/core/
|
||||
| `Serie` | Domain entity with `sanitized_folder` property for filesystem-safe names |
|
||||
| `SerieList` | Collection management with automatic folder creation using sanitized names |
|
||||
|
||||
**Initialization:**
|
||||
|
||||
`SeriesApp` is initialized with `skip_load=True` passed to `SerieList`, preventing automatic loading of series from data files on every instantiation. Series data is loaded once during application setup via `sync_series_from_data_files()` in the FastAPI lifespan, which reads data files and syncs them to the database. Subsequent operations load series from the database through the service layer.
|
||||
|
||||
Source: [src/core/](../src/core/)
|
||||
|
||||
### 2.4 Infrastructure Layer (`src/infrastructure/`)
|
||||
@@ -576,10 +580,10 @@ Source: [src/server/services/auth_service.py](../src/server/services/auth_servic
|
||||
|
||||
### 9.2 Password Requirements
|
||||
|
||||
- Minimum 8 characters
|
||||
- Mixed case (upper and lower)
|
||||
- At least one number
|
||||
- At least one special character
|
||||
- Minimum 8 characters
|
||||
- Mixed case (upper and lower)
|
||||
- At least one number
|
||||
- At least one special character
|
||||
|
||||
Source: [src/server/services/auth_service.py](../src/server/services/auth_service.py#L97-L125)
|
||||
|
||||
|
||||
@@ -119,149 +119,4 @@ For each task completed:
|
||||
|
||||
## TODO List:
|
||||
|
||||
All tasks completed! Recent issues have been resolved.
|
||||
|
||||
## Recently Fixed Issues:
|
||||
|
||||
### ✅ Fixed: NFO Folder Naming Without Year (2026-01-18)
|
||||
|
||||
**Issue:** After creating NFO files, the folder name is created as "<name>" but should be "<name> (<year>)" to properly distinguish series with the same name but different years (e.g., "Perfect Blue" vs "Perfect Blue (1997)").
|
||||
|
||||
**Root Cause:** The NFO endpoints were using `serie.folder` directly without ensuring it includes the year. The `Serie` class already had a `sanitized_folder` property that creates proper folder names with year format, but it wasn't being used consistently during NFO creation.
|
||||
|
||||
**Solution:**
|
||||
1. Added `Serie.ensure_folder_with_year()` method that checks if the folder name includes the year and updates it if needed
|
||||
2. Updated all NFO API endpoints (`create`, `update`, `check`, `view`, `download_media`, `batch_create`, `get_series_without_nfo`) to call `ensure_folder_with_year()` before any NFO operations
|
||||
3. This ensures there's a single code location responsible for folder name generation with year format
|
||||
4. The NFO service creates folders using the updated folder name
|
||||
|
||||
**Files Modified:**
|
||||
- [src/core/entities/series.py](../src/core/entities/series.py) - Added `ensure_folder_with_year()` method
|
||||
- [src/server/api/nfo.py](../src/server/api/nfo.py) - Updated all endpoints to use `ensure_folder_with_year()`
|
||||
|
||||
**Tests Added:**
|
||||
- [tests/unit/test_serie_folder_with_year.py](../tests/unit/test_serie_folder_with_year.py) - 5 comprehensive tests covering all scenarios
|
||||
|
||||
**Verification:**
|
||||
- All 5 unit tests pass
|
||||
- Folder names now consistently use "Name (Year)" format when year is available
|
||||
- Existing folders without year are automatically updated when NFO operations are performed
|
||||
- Single centralized method ensures consistency across the application
|
||||
|
||||
---
|
||||
|
||||
### ✅ Fixed: NFO JavaScript JSON Parsing Error (2026-01-18)
|
||||
|
||||
**Issue:** Browser console shows `Error creating NFO: Error: Failed to create NFO` from [nfo-manager.js](../src/server/web/static/js/index/nfo-manager.js) and [series-manager.js](../src/server/web/static/js/index/series-manager.js). The API calls were failing because the code was trying to access response properties directly without parsing the JSON.
|
||||
|
||||
**Root Cause:** The `AniWorld.ApiClient.request()` function returns a native `Response` object from the Fetch API, not the parsed JSON data. The code was attempting to access properties like `response.message` and `response.content` directly without first calling `response.json()` to parse the response body.
|
||||
|
||||
**Solution:** Updated all NFO-related API calls in the JavaScript modules to:
|
||||
|
||||
1. Check if response exists (`if (!response)`)
|
||||
2. Parse the JSON response (`const data = await response.json()`)
|
||||
3. Access properties on the parsed data object (`data.message`, `data.content`, etc.)
|
||||
|
||||
**Files Modified:**
|
||||
|
||||
- [src/server/web/static/js/index/nfo-manager.js](../src/server/web/static/js/index/nfo-manager.js) - Fixed `createNFO()`, `refreshNFO()`, `viewNFO()`, `getSeriesWithoutNFO()`
|
||||
- [src/server/web/static/js/index/nfo-config.js](../src/server/web/static/js/index/nfo-config.js) - Fixed `load()`, `testTMDBConnection()`
|
||||
|
||||
**Verification:**
|
||||
|
||||
- NFO creation now works correctly from the web UI
|
||||
- Error messages are properly displayed with details from the API
|
||||
- All NFO operations (create, refresh, view) function as expected
|
||||
|
||||
---
|
||||
|
||||
### ✅ Fixed: NFO 503 Error After Server Reload (2026-01-18)
|
||||
|
||||
**Issue:** POST http://127.0.0.1:8000/api/nfo/blue-exorcist/create returns 503 (Service Unavailable) with "NFO service not configured. TMDB API key required." even though TMDB API key is configured in config.json. This occurred after server reloads in development mode (--reload).
|
||||
|
||||
**Root Cause:** The `settings` singleton is recreated on each module reload by uvicorn in development mode. The TMDB API key loaded from config.json during startup was lost when modules were reloaded, causing `settings.tmdb_api_key` to be None again.
|
||||
|
||||
**Solution:** Modified `get_nfo_service()` dependency function to check `settings.tmdb_api_key` first, and if not found, fall back to loading from `config.json` directly. This ensures the TMDB API key is always available even after hot reloads in development.
|
||||
|
||||
**Files Modified:**
|
||||
|
||||
- [src/server/api/nfo.py](../src/server/api/nfo.py)
|
||||
|
||||
**Tests Added:**
|
||||
|
||||
- [tests/unit/test_nfo_dependency.py](../tests/unit/test_nfo_dependency.py) - Tests for config fallback behavior
|
||||
|
||||
**Verification:**
|
||||
|
||||
- All 4 unit tests pass
|
||||
- NFO endpoints work correctly even after server reloads
|
||||
- Falls back gracefully to config.json when settings are reset
|
||||
|
||||
---
|
||||
|
||||
### ✅ Fixed: NFO Creation 500 Error - Missing Folder (2026-01-18)
|
||||
|
||||
**Issue:** POST http://127.0.0.1:8000/api/nfo/blue-period/create returns 500 (Internal Server Error) with message "Series folder not found: /home/lukas/Volume/serien/Blue Period"
|
||||
|
||||
**Root Cause:** The NFO service was checking if the series folder existed before creating NFO files, and raising a FileNotFoundError if it didn't. This prevented users from creating NFO files for newly added series where no episodes had been downloaded yet.
|
||||
|
||||
**Solution:** Modified `create_tvshow_nfo()` in `nfo_service.py` to automatically create the series folder (with `mkdir(parents=True, exist_ok=True)`) if it doesn't exist, instead of raising an error.
|
||||
|
||||
**Files Modified:**
|
||||
|
||||
- [src/core/services/nfo_service.py](../src/core/services/nfo_service.py)
|
||||
|
||||
**Tests Added:**
|
||||
|
||||
- [tests/unit/test_nfo_service_folder_creation.py](../tests/unit/test_nfo_service_folder_creation.py) - Unit test verifying folder creation
|
||||
- [tests/integration/test_nfo_folder_creation.py](../tests/integration/test_nfo_folder_creation.py) - Integration test for end-to-end verification
|
||||
|
||||
**Verification:**
|
||||
|
||||
- Unit and integration tests pass
|
||||
- NFO files can now be created for series even when the folder doesn't exist
|
||||
- Folder is automatically created when needed
|
||||
|
||||
---
|
||||
|
||||
### ✅ Fixed: NFO Service 503 Error (2026-01-18)
|
||||
|
||||
**Issue:** Failed to load resource: the server responded with a status of 503 (Service Unavailable) when creating NFO files.
|
||||
|
||||
**Root Cause:** The TMDB API key configured in `data/config.json` was not being loaded into the application settings during startup. The NFO service dependency check was failing because `settings.tmdb_api_key` was None.
|
||||
|
||||
**Solution:** Updated `fastapi_app.py` startup code to sync NFO configuration (including TMDB API key) from `data/config.json` to `settings` object, similar to how `anime_directory` was already being synced.
|
||||
|
||||
**Files Modified:**
|
||||
|
||||
- [src/server/fastapi_app.py](../src/server/fastapi_app.py)
|
||||
|
||||
**Verification:** NFO endpoints now return 200 OK instead of 503, and NFO creation is functional.
|
||||
|
||||
---
|
||||
|
||||
### ✅ Fixed: NFO Database Query Error (2026-01-18)
|
||||
|
||||
**Issue:** WARNING: Could not fetch NFO data from database: '\_AsyncGeneratorContextManager' object has no attribute 'query'
|
||||
|
||||
**Root Cause:**
|
||||
|
||||
1. Synchronous code in `anime.py` was calling `get_db_session()` which returns an async context manager, but wasn't using `async with`.
|
||||
2. Async methods in `anime_service.py` were calling `get_db_session()` without using `async with`.
|
||||
3. Incorrect attribute name: used `folder_name` instead of `folder`.
|
||||
|
||||
**Solution:**
|
||||
|
||||
1. Changed `anime.py` line ~323 to use `get_sync_session()` instead of `get_db_session()` for synchronous database access.
|
||||
2. Updated three async methods in `anime_service.py` to properly use `async with get_db_session()`:
|
||||
- `update_nfo_status()`
|
||||
- `get_series_without_nfo()`
|
||||
- `get_nfo_statistics()`
|
||||
3. Fixed attribute name from `db_series.folder_name` to `db_series.folder` in `anime.py`.
|
||||
|
||||
**Files Modified:**
|
||||
|
||||
- [src/server/api/anime.py](../src/server/api/anime.py)
|
||||
- [src/server/services/anime_service.py](../src/server/services/anime_service.py)
|
||||
|
||||
**Verification:** Server now starts without warnings, and NFO metadata can be fetched from the database correctly.
|
||||
1. ✅ **COMPLETED**: SeriesApp no longer loads series from data files on every startup. Series are now loaded once during setup via `sync_series_from_data_files()` in the FastAPI lifespan. The `SeriesApp` initializes with `skip_load=True` passed to `SerieList`, preventing automatic file loading. Subsequent operations load series from the database through the service layer.
|
||||
|
||||
@@ -164,11 +164,12 @@ class SeriesApp:
|
||||
self.serie_scanner = SerieScanner(
|
||||
directory_to_search, self.loader
|
||||
)
|
||||
self.list = SerieList(self.directory_to_search)
|
||||
# Skip automatic loading from data files - series will be loaded
|
||||
# from database by the service layer during application setup
|
||||
self.list = SerieList(self.directory_to_search, skip_load=True)
|
||||
self.series_list: List[Any] = []
|
||||
# Synchronous init used during constructor to avoid awaiting
|
||||
# in __init__
|
||||
self._init_list_sync()
|
||||
# Initialize empty list - series loaded later via load_series_from_list()
|
||||
# No need to call _init_list_sync() anymore
|
||||
|
||||
# Initialize NFO service if TMDB API key is configured
|
||||
self.nfo_service: Optional[NFOService] = None
|
||||
@@ -242,26 +243,6 @@ class SeriesApp:
|
||||
len(self.series_list)
|
||||
)
|
||||
|
||||
def _init_list_sync(self) -> None:
|
||||
"""Synchronous initialization helper for constructor."""
|
||||
self.series_list = self.list.GetMissingEpisode()
|
||||
logger.debug(
|
||||
"Loaded %d series with missing episodes",
|
||||
len(self.series_list)
|
||||
)
|
||||
|
||||
async def _init_list(self) -> None:
|
||||
"""Initialize the series list with missing episodes (async)."""
|
||||
loop = asyncio.get_running_loop()
|
||||
self.series_list = await loop.run_in_executor(
|
||||
self.executor,
|
||||
self.list.GetMissingEpisode
|
||||
)
|
||||
logger.debug(
|
||||
"Loaded %d series with missing episodes",
|
||||
len(self.series_list)
|
||||
)
|
||||
|
||||
async def search(self, words: str) -> List[Dict[str, Any]]:
|
||||
"""
|
||||
Search for anime series (async).
|
||||
|
||||
Reference in New Issue
Block a user