Files
Aniworld/docs/ARCHITECTURE.md

26 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)  |     |   (anime/*/)     |
|  - Series data   |     |   - Video files  |
|  - Episodes      |     |   - NFO files    |
|  - Queue state   |     |   - Media files  |
+------------------+     +------------------+

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
|   +-- nfo.py              # /api/nfo/* 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
|   +-- nfo_service.py      # NFO metadata management
+-- 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.2.1 Frontend Architecture (src/server/web/static/)

The frontend uses a modular architecture with no build step required. CSS and JavaScript files are organized by responsibility.

CSS Structure

src/server/web/static/css/
+-- styles.css              # Entry point with @import statements
+-- base/
|   +-- variables.css       # CSS custom properties (colors, fonts, spacing)
|   +-- reset.css           # CSS reset and normalize styles
|   +-- typography.css      # Font styles, headings, text utilities
+-- components/
|   +-- buttons.css         # All button styles
|   +-- cards.css           # Card and panel components
|   +-- forms.css           # Form inputs, labels, validation styles
|   +-- modals.css          # Modal and overlay styles
|   +-- navigation.css      # Header, nav, sidebar styles
|   +-- progress.css        # Progress bars, loading indicators
|   +-- notifications.css   # Toast, alerts, messages
|   +-- tables.css          # Table and list styles
|   +-- status.css          # Status badges and indicators
+-- pages/
|   +-- login.css           # Login page specific styles
|   +-- index.css           # Index/library page specific styles
|   +-- queue.css           # Queue page specific styles
+-- utilities/
    +-- animations.css      # Keyframes and animation classes
    +-- responsive.css      # Media queries and breakpoints
    +-- helpers.css         # Utility classes (hidden, flex, spacing)

JavaScript Structure

JavaScript uses the IIFE pattern with a shared AniWorld namespace for browser compatibility without build tools.

src/server/web/static/js/
+-- shared/                 # Shared utilities used by all pages
|   +-- constants.js        # API endpoints, localStorage keys, defaults
|   +-- auth.js             # Token management (getToken, setToken, checkAuth)
|   +-- api-client.js       # Fetch wrapper with auto-auth headers
|   +-- theme.js            # Dark/light theme toggle
|   +-- ui-utils.js         # Toast notifications, format helpers
|   +-- websocket-client.js # Socket.IO wrapper
+-- index/                  # Index page modules
|   +-- series-manager.js   # Series list rendering and filtering
|   +-- selection-manager.js# Multi-select and bulk download
|   +-- search.js           # Series search functionality
|   +-- scan-manager.js     # Library rescan operations
|   +-- scheduler-config.js # Scheduler configuration
|   +-- logging-config.js   # Logging configuration
|   +-- advanced-config.js  # Advanced settings
|   +-- main-config.js      # Main configuration and backup
|   +-- config-manager.js   # Config modal orchestrator
|   +-- socket-handler.js   # WebSocket event handlers
|   +-- app-init.js         # Application initialization
+-- queue/                  # Queue page modules
    +-- queue-api.js        # Queue API interactions
    +-- queue-renderer.js   # Queue list rendering
    +-- progress-handler.js # Download progress updates
    +-- queue-socket-handler.js # WebSocket events for queue
    +-- queue-init.js       # Queue page initialization

Module Pattern

All JavaScript modules follow the IIFE pattern with namespace:

var AniWorld = window.AniWorld || {};

AniWorld.ModuleName = (function () {
    "use strict";

    // Private variables and functions

    // Public API
    return {
        init: init,
        publicMethod: publicMethod,
    };
})();

Source: src/server/web/static/

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

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/

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 (Service Layer as Repository)

Architecture Decision: The Service Layer serves as the Repository layer for database access.

Database access is abstracted through service classes in src/server/database/service.py that provide CRUD operations and act as the repository layer. This eliminates the need for a separate repository layer while maintaining clean separation of concerns.

Service Layer Classes (acting as repositories):

  • AnimeSeriesService - CRUD operations for anime series
  • EpisodeService - CRUD operations for episodes
  • DownloadQueueService - CRUD operations for download queue
  • UserSessionService - CRUD operations for user sessions
  • SystemSettingsService - CRUD operations for system settings

Key Principles:

  1. No Direct Database Queries: Controllers and business logic services MUST use service layer methods
  2. Service Layer Encapsulation: All SQLAlchemy queries are encapsulated in service methods
  3. Consistent Interface: Services provide consistent async methods for all database operations
  4. Single Responsibility: Each service manages one entity type

Example Usage:

# CORRECT: Use service layer
from src.server.database.service import AnimeSeriesService

async with get_db_session() as db:
    series = await AnimeSeriesService.get_by_key(db, "attack-on-titan")
    await AnimeSeriesService.update(db, series.id, has_nfo=True)

# INCORRECT: Direct database query
result = await db.execute(select(AnimeSeries).filter(...))  # ❌ Never do this

Special Case - Queue Repository Adapter:

The QueueRepository in src/server/services/queue_repository.py is an adapter that wraps DownloadQueueService to provide domain model conversion between Pydantic models and SQLAlchemy models:

# QueueRepository provides CRUD with model conversion
class QueueRepository:
    async def save_item(self, item: DownloadItem) -> None: ...  # Converts Pydantic → SQLAlchemy
    async def get_all_items(self) -> List[DownloadItem]: ...    # Converts SQLAlchemy → Pydantic
    async def delete_item(self, item_id: str) -> bool: ...

Source: src/server/database/service.py, 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

4.5 Error Handling Pattern

Architecture Decision: Dual error handling approach based on exception source.

The application uses two complementary error handling mechanisms:

  1. FastAPI HTTPException - For simple validation and HTTP-level errors
  2. Custom Exception Hierarchy - For business logic and service-level errors with rich context

Exception Hierarchy

# Base exception with HTTP status mapping
AniWorldAPIException(message, status_code, error_code, details)
├── AuthenticationError (401)
├── AuthorizationError (403)
├── ValidationError (422)
├── NotFoundError (404)
├── ConflictError (409)
├── BadRequestError (400)
├── RateLimitError (429)
└── ServerError (500)
    ├── DownloadError
    ├── ConfigurationError
    ├── ProviderError
    └── DatabaseError

When to Use Each

Use HTTPException for:

  • Simple parameter validation (missing fields, wrong type)
  • Direct HTTP-level errors (401, 403, 404 without business context)
  • Quick endpoint-specific failures

Use Custom Exceptions for:

  • Service-layer business logic errors (AnimeServiceError, ConfigServiceError)
  • Errors needing rich context (details dict, error codes)
  • Errors that should be logged with specific categorization
  • Cross-cutting concerns (authentication, authorization, rate limiting)

Example:

# Simple validation - Use HTTPException
if not series_key:
    raise HTTPException(status_code=400, detail="series_key required")

# Business logic error - Use custom exception
try:
    await anime_service.add_series(series_key)
except AnimeServiceError as e:
    raise ServerError(
        message=f"Failed to add series: {e}",
        error_code="ANIME_ADD_FAILED",
        details={"series_key": series_key}
    )

Global Exception Handlers

All custom exceptions are automatically handled by global middleware that:

  • Converts exceptions to structured JSON responses
  • Logs errors with appropriate severity
  • Includes request ID for tracking
  • Provides consistent error format

Source: src/server/exceptions/__init__.py, src/server/middleware/error_handler.py

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,
        "schedule_time": "03:00",
        "schedule_days": ["mon", "tue", "wed", "thu", "fri", "sat", "sun"],
        "auto_download_after_rescan": false
    },
    "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