Contenuto

Pinakes 0.7.86 è online. Contiene due cose che si vedono — una funzione nuova per le biblioteche che cercano libri, e l’emeroteca che finalmente risponde quando la cerchi dal catalogo — e una serie di correzioni che non si vedono affatto, ma che riguardano il momento peggiore possibile: quello in cui si ripristina un backup.

I libri che la biblioteca cerca

Una biblioteca non possiede soltanto: desidera. C’è il titolo esaurito, quello che manca alla collana, quello che un lettore ha chiesto e che varrebbe la pena avere. Fino a ieri Pinakes non aveva un posto per queste schede, e chi le voleva tenere da qualche parte finiva per crearle come libri con zero copie — con l’effetto di pubblicare nel catalogo pubblico dei volumi che la biblioteca non ha.

Ora c’è. Si cataloga la scheda normalmente, con ISBN e scraping come sempre, e prima delle copie si spunta «Desiderata: cerchiamo questo libro». Il numero di copie va a zero e resta bloccato, e la scheda smette di comparire dove comparirebbe un libro posseduto: niente catalogo sfogliabile, niente caroselli, niente feed RSS, niente sitemap, niente API mobile, niente protocolli di interoperabilità. Ma resta trovabile cercandola per nome, perché una richiesta che nessuno può leggere non serve a niente.

La parte che mi interessava davvero è l’altra metà: le donazioni. Ogni desiderata porta un pulsante «Ce l’ho, posso donarlo», e lo stesso modulo accetta anche proposte libere da chi ha un libro da offrire senza che la biblioteca lo stesse cercando. Le proposte arrivano in un pannello dove si valutano una per una, e quando il libro arriva fisicamente c’è «Libro arrivato: registra una copia», che crea la copia con il suo numero di inventario, chiude la richiesta e toglie il contrassegno — tutto in una transazione sola, e ripetere l’operazione non duplica niente.

Disinstallando il plugin il contrassegno viene azzerato su tutte le schede, comprese quelle cestinate. È voluto: finché la colonna esiste il programma nasconde dal catalogo qualunque scheda contrassegnata, e una richiesta rimasta tale senza più il plugin sarebbe un libro invisibile, senza più la casella nel modulo e senza niente, in amministrazione, che spieghi perché.

L’articolo che non si trovava

Questa arriva da un utente, Hans Uwe, che aveva catalogato degli articoli di rivista nell’emeroteca e poi li cercava dal catalogo pubblico. Non li trovava. La ricerca leggeva soltanto l’indice dei libri, e gli articoli vivono in tabelle proprie: quello che la versione precedente aveva aggiunto era un rimando testuale — «cerca anche in: Emeroteca» — cioè una riga di prosa e niente su cui cliccare.

Ora il suggerimento porta i risultati veri: gli articoli e le testate che li contengono, ciascuno con il suo collegamento alla scheda. Un dettaglio che mi ha dato più lavoro di quanto sembri: il numero accanto alle testate e la pagina che quel numero apre ora si costruiscono dallo stesso predicato. Prima no, e il conteggio poteva dire «(1)» sopra una pagina che ne mostrava due — il genere di incoerenza che fa dubitare del programma intero.

Uwe segnalava anche che sulla scheda dell’articolo l’unico collegamento attivo era il pulsante del PDF: autore, pubblicazione e parole chiave erano testo morto. Ora sono collegamenti, e ognuno apre la ricerca degli articoli ristretta a quel valore. Su un articolo autonomo quei campi sono testo libero, non righe dei registri di autori ed editori, quindi una ricerca filtrata è l’unica destinazione onesta: dà la navigazione senza costringere a creare una scheda d’autore per qualcuno di cui si possiede un articolo solo.

Gli articoli a più firme sono stati la parte delicata. I nomi si dividono sul punto e virgola soltanto, mai sulla virgola che sta dentro «Cognome, Nome»: una riga come «Rossi, Mario; Bianchi, Anna» collega due persone, non una stringa unica che non corrisponde a nessuno.

E infine la copertina. Gli articoli ora possono averne una, come i libri; e quando non ce l’hanno ereditano l’immagine della loro testata invece di ricadere sul segnaposto generico. Così una raccolta catalogata senza immagini per singolo articolo continua a somigliare al periodico da cui viene, senza che nessuno debba fare niente.

Un backup che non si sarebbe ripristinato

Qui si cambia tono. Ho fatto revisionare la parte di backup, autenticazione e concorrenza, e ne sono usciti otto problemi. I due peggiori erano nello stesso punto, ed è il punto in cui non si può sbagliare.

Il primo: il file di dump elencava tutte le colonne di tutte le tabelle, e MySQL rifiuta un INSERT che fornisce un valore per una colonna generata. Lo schema ne ha una. Quindi il ripristino si fermava su quella tabella — e ci si accorgeva del problema esattamente nel momento in cui serviva il backup.

Il secondo è peggiore. MySQL non sa annullare le operazioni che modificano la struttura delle tabelle, quindi un archivio che fallisce a metà lascia il database per metà sostituito e per metà no. Il programma, in quel caso, toglieva la modalità manutenzione e dichiarava il ripristino riuscito, consegnando alla biblioteca un database che non è né il backup né quello che c’era prima. Ora la finestra di sostituzione è marcata: finché è aperta il sito resta chiuso, e l’errore torna indietro dicendo cosa è successo e cosa fare.

E qui c’è la parte che mi ha insegnato qualcosa. La mia prima correzione teneva accesa la manutenzione — ma il file che la segnala viene cancellato da solo dopo trenta minuti, come rete di sicurezza per un aggiornamento morto a metà. Un’importazione grossa dura più di mezz’ora. Cioè: il sito si sarebbe riaperto da solo sul database incompleto, e la mia correzione non correggeva niente. Ora il contrassegno del ripristino è esente da quel timer, e l’ho verificato con richieste vere: un contrassegno di ripristino vecchio di un’ora tiene ancora il sito chiuso, uno ordinario scade come prima.

Quando reimpostare la password non chiudeva niente

L’altra famiglia di problemi riguarda l’accesso. Reimpostare una password dimenticata serve a una cosa sola: far perdere l’account a chi ce l’aveva. Ma i cookie «Ricordami» e i token dell’app mobile sopravvivevano intatti, quindi chi era entrato restava dentro attraverso l’atto stesso compiuto per riprendersi l’account.

Chiuderlo per davvero ha richiesto più di quanto pensassi. Revocare le righe non bastava: solo gli accessi con «Ricordami» ne lasciavano una, mentre tutto il resto veniva deciso dalla sessione PHP, che il programma si fida di leggere senza chiedere niente al database. Il browser su cui l’intruso era già dentro continuava a funzionare. Ora ogni accesso lascia una riga revocabile, e quella riga è ciò su cui la revoca agisce.

Con lo stesso filo è venuto fuori il caso delle postazioni condivise, che in biblioteca non è teorico. Se qualcuno entra con «Ricordami» e poi se ne va, il suo cookie resta nel browser: la persona successiva che accede si prende la sessione, ma quel cookie sopravviveva e avrebbe riportato dentro il proprietario precedente appena la nuova sessione fosse scaduta. Ora un accesso ritira il cookie che il browser si portava dietro, e revoca la riga che lo autorizzava — costando al proprietario soltanto quel dispositivo, che è proprio quello che sta cambiando mano.

Otto problemi che ne hanno prodotti nove

Questa è la parte che scrivo per me, e che vale più delle altre.

Correggendo quegli otto problemi ne ho introdotti nove nuovi, e la revisione successiva li ha trovati tutti. Due erano peggiori degli originali. Uno l’ho già raccontato — la manutenzione che si spegneva da sola. L’altro: nel sistemare la faccenda delle colonne generate, avevo scritto una funzione che, se non riusciva a leggere l’elenco delle colonne, restituiva un elenco vuoto. E il codice che genera il backup, davanti a un elenco vuoto, saltava la tabella. Cioè: sondaggio fallito, intera tabella fuori dal backup, senza un errore da nessuna parte. Un archivio che sembra completo e ha perso dei dati. Correggendo un backup che non si ripristinava ne avevo scritto uno che si ripristinava benissimo, incompleto.

Ce n’è un terzo che mi piace citare perché è il più umano. Per far sì che una sessione ordinaria fosse revocabile le avevo dato una scadenza, presa dal valore di configurazione delle sessioni. Solo che quel valore, in PHP, è un timeout di inattività: viene rinnovato a ogni richiesta, e una sessione attiva gli sopravvive indefinitamente. Stampandolo una volta sola su una riga di database l’avevo trasformato in una scadenza assoluta. Risultato: chi stava catalogando da più tempo della finestra configurata sarebbe stato buttato fuori a metà modulo, con la sessione perfettamente viva. Con l’impostazione al minimo, dopo cinque minuti.

Il filo che lega quasi tutti questi errori è uno solo, ripetuto in punti diversi del programma: aver confuso «non ci sono riuscito» con «non c’era niente da fare». Zero colonne perché non le ho lette o perché non ce n’erano. Zero revoche perché ho fallito o perché non c’era nulla da revocare. Zero proposte da valutare perché la query è andata male o perché oggi non ce ne sono. Ogni volta lo stesso valore di ritorno serviva due significati opposti, e ogni volta il codice sceglieva quello comodo e dichiarava successo.

È un errore che non si vede rileggendo il codice, perché in lettura sembra prudente. Si vede solo chiedendosi, per ogni valore che torna, se esiste un caso in cui vorrebbe dire una cosa diversa.

Il resto

La sitemap pubblicata viene ora trattata per quello che è, una cache: un plugin possiede indirizzi pubblici, quindi attivarne o rimuoverne uno cambia le pagine che il sito dichiara di avere, e il file statico sopravviveva a quei cambiamenti continuando a mandare i motori di ricerca su pagine che nel frattempo rispondevano «non esiste». Ora viene buttato via quando lo stato dei plugin cambia davvero — e solo allora, perché buttarlo via sempre avrebbe significato rigenerarlo a ogni visita di una pagina di amministrazione.

La riparazione automatica dei plugin ha smesso di ripetersi all’infinito: leggeva «nessun hook registrato» come una sincronizzazione mancata, cosa che è permanentemente vera per un plugin i cui hook dipendono da una configurazione che nessuno gli ha dato, e quindi ne rieseguiva l’attivazione a ogni passaggio di manutenzione, per sempre.

Chi ha già Pinakes installato aggiorna dal pannello di amministrazione come al solito. Il percorso di aggiornamento dalla versione precedente è stato provato per intero prima della pubblicazione: installazione della 0.7.85 vera, aggiornamento manuale dall’interfaccia con il caricamento del pacchetto, verifica dello schema risultante e suite completa sull’installazione aggiornata.