9.3 KiB
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>,/formatsreader.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) ereading_positions(una per file).
Frontend (bookish/static/js/)
main.js— Convertitore: drag&drop, form, SSE connection, downloadreader.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 SVGindex.html— Convertitore con drag&drop, griglia formati, progress barreader.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