Aniworld/docs/ARCHITECTURE.md

18 KiB

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


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 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
+-- utils/                  # Utility modules
|   +-- filesystem.py       # Folder sanitization, path safety
|   +-- validators.py       # Input validation utilities
|   +-- dependencies.py     # FastAPI dependency injection
+-- web/                    # Static files and templates
    +-- static/             # CSS, JS, images
    +-- templates/          # Jinja2 templates

Source: 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, targeted single-series scan
+-- entities/               # Domain entities
|   +-- series.py           # Serie class with sanitized_folder property
|   +-- SerieList.py        # SerieList collection with sanitized folder support
+-- 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

Key Components:

Component Purpose
SeriesApp Main application facade for anime operations
SerieScanner Scans directories for anime; scan_single_series() for targeted scans
Serie Domain entity with sanitized_folder property for filesystem-safe names
SerieList Collection management with automatic folder creation using sanitized names

Source: 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 Environment-based configuration

Source: src/config/settings.py


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

11.2 Key Components

Component File Shutdown Method
WebSocket Service websocket_service.py shutdown(timeout=5.0)
Download Service download_service.py stop(timeout=10.0)
Database Connection connection.py close_db()
Uvicorn Config run_server.py timeout_graceful_shutdown=30
Stop Script 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

# 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


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

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

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


4. Design Patterns

4.1 Repository Pattern

Database access is abstracted through repository classes.

# 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

4.2 Dependency Injection

FastAPI's Depends() provides constructor injection.

@router.get("/status")
async def get_status(
    download_service: DownloadService = Depends(get_download_service),
):
    ...

Source: src/server/utils/dependencies.py

4.3 Event-Driven Architecture

Progress updates use an event subscription model.

# ProgressService publishes events
progress_service.emit("progress_updated", event)

# WebSocketService subscribes
progress_service.subscribe("progress_updated", ws_handler)

Source: src/server/fastapi_app.py

4.4 Singleton Pattern

Services use module-level singletons for shared state.

# 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


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

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

5.3 Configuration Storage

Configuration is stored in data/config.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


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


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.

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.

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

8.2 Filesystem Integration

The scanner reads anime directories to detect downloaded episodes.

SerieScanner(
    basePath="/path/to/anime",  # Anime library directory
    loader=provider,             # Provider for metadata
    db_session=session           # Optional database
)

Source: src/core/SerieScanner.py


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

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

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


10. Deployment Modes

10.1 Development

# Run with hot reload
python -m uvicorn src.server.fastapi_app:app --reload

10.2 Production

# 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