# 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/ │ 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/`, `/download/`, `/formats` - **`reader.py`** — Blueprint `/reader`, `/reader/`, `/reader/serve/`, `/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/` | SSE stream progresso | | GET | `/download/` | Scarica file convertito | | GET | `/formats?input=` | Formati disponibili | | GET | `/reader` | Pagina reader | | GET | `/reader/` | Reader per file | | GET | `/reader/serve/` | Serve file al reader | | GET | `/reader/formats` | Formati supportati | | GET | `/api/bookmarks/` | Segnalibri del libro | | POST | `/api/bookmarks` | Aggiunge segnalibro | | DELETE | `/api/bookmarks/` | Elimina segnalibro | | GET | `/api/position/` | Posizione di lettura | | PUT | `/api/position/` | 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