Files

9.3 KiB

Architettura di Bookish

Panoramica

Bookish è un'applicazione web Flask monolitica progettata per l'uso self-hostato. Si compone di due macro-funzionalità: un convertitore di formati ebook e un reader web per visualizzarli, con SQLite per persistenza di segnalibri e posizioni di lettura.

┌──────────┐     ┌──────────────────────────────────────────────┐
│ Browser  │────▶│                   Flask                      │
│          │     │  ┌──────────┐  ┌────────────────────────┐    │
│ HTML/CSS │     │  │ Routes   │  │    Services            │    │
│ JS/ES    │     │  │          │  │                        │    │
│          │     │  │ convert  │─▶│ converter.py           │───▶│ Pandoc
│ epub.js  │     │  │ reader   │  │ sse_manager.py         │    │ WeasyPrint
│ pdf.js   │     │  │ api      │  │ formatter.py           │    │ python-docx
└──────────┘     │  └──────────┘  │ database.py ──────────▶│───▶│ SQLite
                 │                └────────────────────────┘    │
                 │                         │            │       │
                 └──────────────────────────────────────────────┘
                                      uploads/     converted/
                                   (temporaneo)  (temporaneo)

Stack Tecnologico

Componente Tecnologia Ruolo
Backend Flask 3.x + Jinja2 Server web, routing, template engine
Database SQLite (via sqlite3) Segnalibri e posizioni di lettura
Conversione Pandoc 3.x (via pypandoc) Motore di conversione principale
PDF avanzato WeasyPrint PDF con layout precisi da HTML
DOCX avanzato python-docx Post-processing documenti Word
Frontend JavaScript vanilla Nessun framework pesante
Reader EPUB epub.js 0.3 Renderer EPUB lato client
Reader PDF pdf.js 3.x Renderer PDF lato client
Progresso SSE (Server-Sent Events) Barra progresso real-time
Storage Filesystem + SQLite uploads/, converted/, bookish.db

Flusso Conversione

Upload file  →  Validazione formato  →  Avvio thread conversione
                                            │
                     ┌──────────────────────┘
                     ▼
              SSE Manager crea task_id
                     │
              Frontend apre connessione SSE
                     │
              Converter dispatcher:
              ├─ Pandoc per formati standard
              ├─ WeasyPrint per PDF avanzati
              ├─ python-docx per DOCX
              └─ BeautifulSoup per tabelle/HTML
                     │
              Ogni fase → update SSE progress
                     │
                     ▼
         File salvato in converted/
         SSE invia download_url

Flusso Reader con Persistenza

Upload file → Salva in uploads/ → Calcola SHA256 (content_hash)
                                       │
                                Redirect a /reader/<file_id>
                                       │
                                Template reader.html (contiene BOOKISH_HASH)
                                       │
                                Rileva formato:
                                ├─ EPUB → epub.js
                                ├─ PDF → pdf.js
                                └─ Testo → HTML renderizzato
                                       │
                                Interazioni lato client:
                                - Temi, zoom, ricerca, indice
                                - Slider larghezza lettura
                                - Aiuto contestuale (?)
                                       │
                                Segnalibri e posizione:
                                ├─ Salvataggio immediato in localStorage
                                ├─ Sync asincrono su server SQLite
                                └─ Caricamento da server con fallback localStorage

Il content_hash (SHA256 del file) è l'identificatore univoco del libro. Viene calcolato:

  • All'upload (restituito nella response)
  • All'apertura del reader (dal file su disco)
  • Garantisce che lo stesso contenuto → stesso hash → stessi segnalibri

Architettura del Codice

Routes (bookish/routes/)

  • convert.py — Blueprint /upload, /progress/<task_id>, /download/<filename>, /formats
  • reader.py — Blueprint /reader, /reader/<filename>, /reader/serve/<filename>, /reader/formats. Calcola SHA256 del file per identificare univocamente il libro.
  • api.py — Blueprint /api/bookmarks/* (CRUD segnalibri), /api/position/* (salva/carica posizione lettura)

Services (bookish/services/)

  • converter.py — Dispatcher che sceglie Pandoc o pipeline custom in base alla coppia input/output. Aggiorna SSE progress ad ogni fase.
  • sse_manager.py — Gestisce task in memoria con {task_id: {progress, status, output_file}}. Thread-safe con lock.
  • formatter.py — WeasyPrint PDF da HTML con post-processing (tabelle, immagini via BeautifulSoup).
  • database.py — Gestione SQLite con WAL mode. Due tabelle: bookmarks (per file) e reading_positions (una per file).

Frontend (bookish/static/js/)

  • main.js — Convertitore: drag&drop, form, SSE connection, download
  • reader.js — Reader completo (800+ righe): epub.js/pdf.js/text viewer, sidebar con TOC/cerca/segnalibri, temi, zoom, slider larghezza, help modal, sync server per bookmarks e posizioni

Template (bookish/templates/)

  • base.html — Layout base con navbar e logo SVG
  • index.html — Convertitore con drag&drop, griglia formati, progress bar
  • reader.html — Reader con toolbar, sidebar, help modal, e script inline per BOOKISH_FILE, BOOKISH_EXT, BOOKISH_HASH

API REST

Metodo Path Descrizione
POST /upload Carica file e avvia conversione
GET /progress/<task_id> SSE stream progresso
GET /download/<filename> Scarica file convertito
GET /formats?input=<ext> Formati disponibili
GET /reader Pagina reader
GET /reader/<filename> Reader per file
GET /reader/serve/<filename> Serve file al reader
GET /reader/formats Formati supportati
GET /api/bookmarks/<hash> Segnalibri del libro
POST /api/bookmarks Aggiunge segnalibro
DELETE /api/bookmarks/<id> Elimina segnalibro
GET /api/position/<hash> Posizione di lettura
PUT /api/position/<hash> Salva posizione di lettura

Database SQLite (bookish.db)

Tabella bookmarks

Colonna Tipo Descrizione
id TEXT PK ID univoco del segnalibro
content_hash TEXT SHA256 del file libro
type TEXT epub, pdf, text
cfi TEXT CFI per EPUB
href TEXT Href del capitolo EPUB
page INTEGER Numero pagina PDF
total_pages INTEGER Pagine totali (PDF)
scroll_pct INTEGER % scorrimento (testo)
label TEXT Etichetta visualizzata
chapter_title TEXT Titolo capitolo corrente
created REAL Timestamp creazione

Tabella reading_positions

Colonna Tipo Descrizione
content_hash TEXT PK SHA256 del file libro
type TEXT epub, pdf, text
cfi TEXT CFI per EPUB
href TEXT Href del capitolo
page INTEGER Pagina PDF
scroll_pct INTEGER % scorrimento (testo)
chapter_title TEXT Titolo capitolo
updated REAL Timestamp ultimo aggiornamento

Decisioni Architetturali

Decisione Motivazione
Thread invece di Celery Uso singolo utente, niente Redis, minore complessità
SSE invece di WebSocket Sincrono semplice, nessuna dipendenza extra, supporto nativo
content_hash (SHA256) invece di UUID Identifica il contenuto, non l'istanza. Stesso file = stesso hash
SQLite per persistenza Incluso in Python, zero configurazione, file singolo
Server + localStorage come fallback Offline-resilient, UX istantanea, sync asincrono
Position save debounced (1.5s) Evita flooding di scritture SQLite ad ogni pagina
File temporanei con cleanup Nessun accumulo, auto-pulizia dopo 1h
JS vanilla Nessun framework, leggero, manutenibile
Pandoc via pypandoc-binary Include Pandoc binary, nessuna installazione systema richiesta

Limiti Attuali

  • Thread in memoria: I task di conversione muoiono se il processo Flask viene killato
  • No autenticazione: Uso personale, nessun multi-utente
  • No HTTPS di base: Da abilitare con reverse proxy (nginx/caddy)
  • No ottimizzazione mobile: Funziona ma non è prioritario
  • Gestione code block reader: Reader testo ha markdown renderer minimale