525 lines
17 KiB
Markdown
525 lines
17 KiB
Markdown
# Architecture Documentation
|
|
|
|
## Document Purpose
|
|
|
|
This document describes the system architecture of the Aniworld anime download manager.
|
|
|
|
---
|
|
|
|
## 1. System Overview
|
|
|
|
Aniworld is a web-based anime download manager built with Python, FastAPI, and SQLite. It provides a REST API and WebSocket interface for managing anime libraries, downloading episodes, and tracking progress.
|
|
|
|
### High-Level Architecture
|
|
|
|
```
|
|
+------------------+ +------------------+ +------------------+
|
|
| Web Browser | | CLI Client | | External |
|
|
| (Frontend) | | (Main.py) | | Providers |
|
|
+--------+---------+ +--------+---------+ +--------+---------+
|
|
| | |
|
|
| HTTP/WebSocket | Direct | HTTP
|
|
| | |
|
|
+--------v---------+ +--------v---------+ +--------v---------+
|
|
| | | | | |
|
|
| FastAPI <-----> Core Layer <-----> Provider |
|
|
| Server Layer | | (SeriesApp) | | Adapters |
|
|
| | | | | |
|
|
+--------+---------+ +--------+---------+ +------------------+
|
|
| |
|
|
| |
|
|
+--------v---------+ +--------v---------+
|
|
| | | |
|
|
| SQLite DB | | File System |
|
|
| (aniworld.db) | | (data/*.json) |
|
|
| | | |
|
|
+------------------+ +------------------+
|
|
```
|
|
|
|
Source: [src/server/fastapi_app.py](../src/server/fastapi_app.py#L1-L252)
|
|
|
|
---
|
|
|
|
## 2. Architectural Layers
|
|
|
|
### 2.1 CLI Layer (`src/cli/`)
|
|
|
|
Legacy command-line interface for direct interaction with the core layer.
|
|
|
|
| Component | File | Purpose |
|
|
| --------- | ----------------------------- | --------------- |
|
|
| Main | [Main.py](../src/cli/Main.py) | CLI entry point |
|
|
|
|
### 2.2 Server Layer (`src/server/`)
|
|
|
|
FastAPI-based REST API and WebSocket server.
|
|
|
|
```
|
|
src/server/
|
|
+-- fastapi_app.py # Application entry point, lifespan management
|
|
+-- api/ # API route handlers
|
|
| +-- anime.py # /api/anime/* endpoints
|
|
| +-- auth.py # /api/auth/* endpoints
|
|
| +-- config.py # /api/config/* endpoints
|
|
| +-- download.py # /api/queue/* endpoints
|
|
| +-- scheduler.py # /api/scheduler/* endpoints
|
|
| +-- websocket.py # /ws/* WebSocket handlers
|
|
| +-- health.py # /health/* endpoints
|
|
+-- controllers/ # Page controllers for HTML rendering
|
|
| +-- page_controller.py # UI page routes
|
|
| +-- health_controller.py# Health check route
|
|
| +-- error_controller.py # Error pages (404, 500)
|
|
+-- services/ # Business logic
|
|
| +-- anime_service.py # Anime operations
|
|
| +-- auth_service.py # Authentication
|
|
| +-- config_service.py # Configuration management
|
|
| +-- download_service.py # Download queue management
|
|
| +-- progress_service.py # Progress tracking
|
|
| +-- websocket_service.py# WebSocket broadcasting
|
|
| +-- queue_repository.py # Database persistence
|
|
+-- models/ # Pydantic models
|
|
| +-- auth.py # Auth request/response models
|
|
| +-- config.py # Configuration models
|
|
| +-- download.py # Download queue models
|
|
| +-- websocket.py # WebSocket message models
|
|
+-- middleware/ # Request processing
|
|
| +-- auth.py # JWT validation, rate limiting
|
|
| +-- error_handler.py # Exception handlers
|
|
| +-- setup_redirect.py # Setup flow redirect
|
|
+-- database/ # SQLAlchemy ORM
|
|
| +-- connection.py # Database connection
|
|
| +-- models.py # ORM models
|
|
| +-- service.py # Database service
|
|
+-- web/ # Static files and templates
|
|
+-- static/ # CSS, JS, images
|
|
+-- templates/ # Jinja2 templates
|
|
```
|
|
|
|
Source: [src/server/](../src/server/)
|
|
|
|
### 2.3 Core Layer (`src/core/`)
|
|
|
|
Domain logic for anime series management.
|
|
|
|
```
|
|
src/core/
|
|
+-- SeriesApp.py # Main application facade
|
|
+-- SerieScanner.py # Directory scanning
|
|
+-- entities/ # Domain entities
|
|
| +-- series.py # Serie class
|
|
| +-- SerieList.py # SerieList collection
|
|
+-- providers/ # External provider adapters
|
|
| +-- base_provider.py # Loader interface
|
|
| +-- provider_factory.py # Provider registry
|
|
+-- interfaces/ # Abstract interfaces
|
|
| +-- callbacks.py # Progress callback system
|
|
+-- exceptions/ # Domain exceptions
|
|
+-- Exceptions.py # Custom exceptions
|
|
```
|
|
|
|
Source: [src/core/](../src/core/)
|
|
|
|
### 2.4 Infrastructure Layer (`src/infrastructure/`)
|
|
|
|
Cross-cutting concerns.
|
|
|
|
```
|
|
src/infrastructure/
|
|
+-- logging/ # Structured logging setup
|
|
+-- security/ # Security utilities
|
|
```
|
|
|
|
### 2.5 Configuration Layer (`src/config/`)
|
|
|
|
Application settings management.
|
|
|
|
| Component | File | Purpose |
|
|
| --------- | ---------------------------------------- | ------------------------------- |
|
|
| Settings | [settings.py](../src/config/settings.py) | Environment-based configuration |
|
|
|
|
Source: [src/config/settings.py](../src/config/settings.py#L1-L96)
|
|
|
|
---
|
|
|
|
## 11. Graceful Shutdown
|
|
|
|
The application implements a comprehensive graceful shutdown mechanism that ensures data integrity and proper cleanup when the server is stopped via Ctrl+C (SIGINT) or SIGTERM.
|
|
|
|
### 11.1 Shutdown Sequence
|
|
|
|
```
|
|
1. SIGINT/SIGTERM received
|
|
+-- Uvicorn catches signal
|
|
+-- Stops accepting new requests
|
|
|
|
2. FastAPI lifespan shutdown triggered
|
|
+-- 30 second total timeout
|
|
|
|
3. WebSocket shutdown (5s timeout)
|
|
+-- Broadcast {"type": "server_shutdown"} to all clients
|
|
+-- Close each connection with code 1001 (Going Away)
|
|
+-- Clear connection tracking data
|
|
|
|
4. Download service stop (10s timeout)
|
|
+-- Set shutdown flag
|
|
+-- Persist active download as "pending" in database
|
|
+-- Cancel active download task
|
|
+-- Shutdown ThreadPoolExecutor with wait
|
|
|
|
5. Progress service cleanup
|
|
+-- Clear event subscribers
|
|
+-- Clear active progress tracking
|
|
|
|
6. Database cleanup (10s timeout)
|
|
+-- SQLite: Run PRAGMA wal_checkpoint(TRUNCATE)
|
|
+-- Dispose async engine
|
|
+-- Dispose sync engine
|
|
|
|
7. Process exits cleanly
|
|
```
|
|
|
|
Source: [src/server/fastapi_app.py](../src/server/fastapi_app.py#L142-L210)
|
|
|
|
### 11.2 Key Components
|
|
|
|
| Component | File | Shutdown Method |
|
|
| ------------------- | ------------------------------------------------------------------- | ------------------------------ |
|
|
| WebSocket Service | [websocket_service.py](../src/server/services/websocket_service.py) | `shutdown(timeout=5.0)` |
|
|
| Download Service | [download_service.py](../src/server/services/download_service.py) | `stop(timeout=10.0)` |
|
|
| Database Connection | [connection.py](../src/server/database/connection.py) | `close_db()` |
|
|
| Uvicorn Config | [run_server.py](../run_server.py) | `timeout_graceful_shutdown=30` |
|
|
| Stop Script | [stop_server.sh](../stop_server.sh) | SIGTERM with fallback |
|
|
|
|
### 11.3 Data Integrity Guarantees
|
|
|
|
1. **Active downloads preserved**: In-progress downloads are saved as "pending" and can resume on restart.
|
|
|
|
2. **Database WAL flushed**: SQLite WAL checkpoint ensures all writes are in the main database file.
|
|
|
|
3. **WebSocket clients notified**: Clients receive shutdown message before connection closes.
|
|
|
|
4. **Thread pool cleanup**: Background threads complete or are gracefully cancelled.
|
|
|
|
### 11.4 Manual Stop
|
|
|
|
```bash
|
|
# Graceful stop via script (sends SIGTERM, waits up to 30s)
|
|
./stop_server.sh
|
|
|
|
# Or press Ctrl+C in terminal running the server
|
|
```
|
|
|
|
Source: [stop_server.sh](../stop_server.sh#L1-L80)
|
|
|
|
---
|
|
|
|
## 3. Component Interactions
|
|
|
|
### 3.1 Request Flow (REST API)
|
|
|
|
```
|
|
1. Client sends HTTP request
|
|
2. AuthMiddleware validates JWT token (if required)
|
|
3. Rate limiter checks request frequency
|
|
4. FastAPI router dispatches to endpoint handler
|
|
5. Endpoint calls service layer
|
|
6. Service layer uses core layer or database
|
|
7. Response returned as JSON
|
|
```
|
|
|
|
Source: [src/server/middleware/auth.py](../src/server/middleware/auth.py#L1-L209)
|
|
|
|
### 3.2 Download Flow
|
|
|
|
```
|
|
1. POST /api/queue/add
|
|
+-- DownloadService.add_to_queue()
|
|
+-- QueueRepository.save_item() -> SQLite
|
|
|
|
2. POST /api/queue/start
|
|
+-- DownloadService.start_queue_processing()
|
|
+-- Process pending items sequentially
|
|
+-- ProgressService emits events
|
|
+-- WebSocketService broadcasts to clients
|
|
|
|
3. During download:
|
|
+-- ProgressService.emit("progress_updated")
|
|
+-- WebSocketService.broadcast_to_room()
|
|
+-- Client receives WebSocket message
|
|
```
|
|
|
|
Source: [src/server/services/download_service.py](../src/server/services/download_service.py#L1-L150)
|
|
|
|
### 3.3 WebSocket Event Flow
|
|
|
|
```
|
|
1. Client connects to /ws/connect
|
|
2. Server sends "connected" message
|
|
3. Client joins room: {"action": "join", "data": {"room": "downloads"}}
|
|
4. ProgressService emits events
|
|
5. WebSocketService broadcasts to room subscribers
|
|
6. Client receives real-time updates
|
|
```
|
|
|
|
Source: [src/server/api/websocket.py](../src/server/api/websocket.py#L1-L260)
|
|
|
|
---
|
|
|
|
## 4. Design Patterns
|
|
|
|
### 4.1 Repository Pattern
|
|
|
|
Database access is abstracted through repository classes.
|
|
|
|
```python
|
|
# QueueRepository provides CRUD for download items
|
|
class QueueRepository:
|
|
async def save_item(self, item: DownloadItem) -> None: ...
|
|
async def get_all_items(self) -> List[DownloadItem]: ...
|
|
async def delete_item(self, item_id: str) -> bool: ...
|
|
```
|
|
|
|
Source: [src/server/services/queue_repository.py](../src/server/services/queue_repository.py)
|
|
|
|
### 4.2 Dependency Injection
|
|
|
|
FastAPI's `Depends()` provides constructor injection.
|
|
|
|
```python
|
|
@router.get("/status")
|
|
async def get_status(
|
|
download_service: DownloadService = Depends(get_download_service),
|
|
):
|
|
...
|
|
```
|
|
|
|
Source: [src/server/utils/dependencies.py](../src/server/utils/dependencies.py)
|
|
|
|
### 4.3 Event-Driven Architecture
|
|
|
|
Progress updates use an event subscription model.
|
|
|
|
```python
|
|
# ProgressService publishes events
|
|
progress_service.emit("progress_updated", event)
|
|
|
|
# WebSocketService subscribes
|
|
progress_service.subscribe("progress_updated", ws_handler)
|
|
```
|
|
|
|
Source: [src/server/fastapi_app.py](../src/server/fastapi_app.py#L98-L108)
|
|
|
|
### 4.4 Singleton Pattern
|
|
|
|
Services use module-level singletons for shared state.
|
|
|
|
```python
|
|
# In download_service.py
|
|
_download_service_instance: Optional[DownloadService] = None
|
|
|
|
def get_download_service() -> DownloadService:
|
|
global _download_service_instance
|
|
if _download_service_instance is None:
|
|
_download_service_instance = DownloadService(...)
|
|
return _download_service_instance
|
|
```
|
|
|
|
Source: [src/server/services/download_service.py](../src/server/services/download_service.py)
|
|
|
|
---
|
|
|
|
## 5. Data Flow
|
|
|
|
### 5.1 Series Identifier Convention
|
|
|
|
The system uses two identifier fields:
|
|
|
|
| Field | Type | Purpose | Example |
|
|
| -------- | -------- | -------------------------------------- | -------------------------- |
|
|
| `key` | Primary | Provider-assigned, URL-safe identifier | `"attack-on-titan"` |
|
|
| `folder` | Metadata | Filesystem folder name (display only) | `"Attack on Titan (2013)"` |
|
|
|
|
All API operations use `key`. The `folder` is for filesystem operations only.
|
|
|
|
Source: [src/server/database/models.py](../src/server/database/models.py#L26-L50)
|
|
|
|
### 5.2 Database Schema
|
|
|
|
```
|
|
+----------------+ +----------------+ +--------------------+
|
|
| anime_series | | episodes | | download_queue_item|
|
|
+----------------+ +----------------+ +--------------------+
|
|
| id (PK) |<--+ | id (PK) | +-->| id (PK) |
|
|
| key (unique) | | | series_id (FK) |---+ | series_id (FK) |
|
|
| name | +---| season | | status |
|
|
| site | | episode_number | | priority |
|
|
| folder | | title | | progress_percent |
|
|
| created_at | | is_downloaded | | added_at |
|
|
| updated_at | | file_path | | started_at |
|
|
+----------------+ +----------------+ +--------------------+
|
|
```
|
|
|
|
Source: [src/server/database/models.py](../src/server/database/models.py#L1-L200)
|
|
|
|
### 5.3 Configuration Storage
|
|
|
|
Configuration is stored in `data/config.json`:
|
|
|
|
```json
|
|
{
|
|
"name": "Aniworld",
|
|
"data_dir": "data",
|
|
"scheduler": { "enabled": true, "interval_minutes": 60 },
|
|
"logging": { "level": "INFO" },
|
|
"backup": { "enabled": false, "path": "data/backups" },
|
|
"other": {
|
|
"master_password_hash": "$pbkdf2-sha256$...",
|
|
"anime_directory": "/path/to/anime"
|
|
}
|
|
}
|
|
```
|
|
|
|
Source: [data/config.json](../data/config.json)
|
|
|
|
---
|
|
|
|
## 6. Technology Stack
|
|
|
|
| Layer | Technology | Version | Purpose |
|
|
| ------------- | ------------------- | ------- | ---------------------- |
|
|
| Web Framework | FastAPI | 0.104.1 | REST API, WebSocket |
|
|
| ASGI Server | Uvicorn | 0.24.0 | HTTP server |
|
|
| Database | SQLite + SQLAlchemy | 2.0.35 | Persistence |
|
|
| Auth | python-jose | 3.3.0 | JWT tokens |
|
|
| Password | passlib | 1.7.4 | bcrypt hashing |
|
|
| Validation | Pydantic | 2.5.0 | Data models |
|
|
| Templates | Jinja2 | 3.1.2 | HTML rendering |
|
|
| Logging | structlog | 24.1.0 | Structured logging |
|
|
| Testing | pytest | 7.4.3 | Unit/integration tests |
|
|
|
|
Source: [requirements.txt](../requirements.txt)
|
|
|
|
---
|
|
|
|
## 7. Scalability Considerations
|
|
|
|
### Current Limitations
|
|
|
|
1. **Single-process deployment**: In-memory rate limiting and session state are not shared across processes.
|
|
|
|
2. **SQLite database**: Not suitable for high concurrency. Consider PostgreSQL for production.
|
|
|
|
3. **Sequential downloads**: Only one download processes at a time by design.
|
|
|
|
### Recommended Improvements for Scale
|
|
|
|
| Concern | Current | Recommended |
|
|
| -------------- | --------------- | ----------------- |
|
|
| Rate limiting | In-memory dict | Redis |
|
|
| Session store | In-memory | Redis or database |
|
|
| Database | SQLite | PostgreSQL |
|
|
| Task queue | In-memory deque | Celery + Redis |
|
|
| Load balancing | None | Nginx/HAProxy |
|
|
|
|
---
|
|
|
|
## 8. Integration Points
|
|
|
|
### 8.1 External Providers
|
|
|
|
The system integrates with anime streaming providers via the Loader interface.
|
|
|
|
```python
|
|
class Loader(ABC):
|
|
@abstractmethod
|
|
def search(self, query: str) -> List[Serie]: ...
|
|
|
|
@abstractmethod
|
|
def get_episodes(self, serie: Serie) -> Dict[int, List[int]]: ...
|
|
```
|
|
|
|
Source: [src/core/providers/base_provider.py](../src/core/providers/base_provider.py)
|
|
|
|
### 8.2 Filesystem Integration
|
|
|
|
The scanner reads anime directories to detect downloaded episodes.
|
|
|
|
```python
|
|
SerieScanner(
|
|
basePath="/path/to/anime", # Anime library directory
|
|
loader=provider, # Provider for metadata
|
|
db_session=session # Optional database
|
|
)
|
|
```
|
|
|
|
Source: [src/core/SerieScanner.py](../src/core/SerieScanner.py#L59-L96)
|
|
|
|
---
|
|
|
|
## 9. Security Architecture
|
|
|
|
### 9.1 Authentication Flow
|
|
|
|
```
|
|
1. User sets master password via POST /api/auth/setup
|
|
2. Password hashed with pbkdf2_sha256 (via passlib)
|
|
3. Hash stored in config.json
|
|
4. Login validates password, returns JWT token
|
|
5. JWT contains: session_id, user, created_at, expires_at
|
|
6. Subsequent requests include: Authorization: Bearer <token>
|
|
```
|
|
|
|
Source: [src/server/services/auth_service.py](../src/server/services/auth_service.py#L1-L150)
|
|
|
|
### 9.2 Password Requirements
|
|
|
|
- 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)
|
|
|
|
### 9.3 Rate Limiting
|
|
|
|
| Endpoint | Limit | Window |
|
|
| ----------------- | ----------- | ---------- |
|
|
| `/api/auth/login` | 5 requests | 60 seconds |
|
|
| `/api/auth/setup` | 5 requests | 60 seconds |
|
|
| All origins | 60 requests | 60 seconds |
|
|
|
|
Source: [src/server/middleware/auth.py](../src/server/middleware/auth.py#L54-L68)
|
|
|
|
---
|
|
|
|
## 10. Deployment Modes
|
|
|
|
### 10.1 Development
|
|
|
|
```bash
|
|
# Run with hot reload
|
|
python -m uvicorn src.server.fastapi_app:app --reload
|
|
```
|
|
|
|
### 10.2 Production
|
|
|
|
```bash
|
|
# Via conda environment
|
|
conda run -n AniWorld python -m uvicorn src.server.fastapi_app:app \
|
|
--host 127.0.0.1 --port 8000
|
|
```
|
|
|
|
### 10.3 Configuration
|
|
|
|
Environment variables (via `.env` or shell):
|
|
|
|
| Variable | Default | Description |
|
|
| ----------------- | ------------------------------ | ---------------------- |
|
|
| `JWT_SECRET_KEY` | Random | Secret for JWT signing |
|
|
| `DATABASE_URL` | `sqlite:///./data/aniworld.db` | Database connection |
|
|
| `ANIME_DIRECTORY` | (empty) | Path to anime library |
|
|
| `LOG_LEVEL` | `INFO` | Logging level |
|
|
| `CORS_ORIGINS` | `localhost:3000,8000` | Allowed CORS origins |
|
|
|
|
Source: [src/config/settings.py](../src/config/settings.py#L1-L96)
|