In che modo la PWA Kiwix consente agli utenti di archiviare gigabyte di dati da Internet per l'utilizzo offline

Geoffrey Kantaris
Geoffrey Kantaris
Stéphane Coillet-Matillon
Stéphane Coillet-Matillon

Persone riunite intorno a un laptop appoggiato su un semplice tavolo con una sedia di plastica a sinistra. Lo sfondo assomiglia a una scuola di un paese in via di sviluppo.

Questo case study illustra in che modo Kiwix, un'organizzazione non profit, utilizza la tecnologia delle app web progressive e l'API File System Access per consentire agli utenti di scaricare e archiviare archivi di internet di grandi dimensioni per l'utilizzo offline. Scopri di più sull'implementazione tecnica del codice relativo al sistema di file privato di Origin (OPFS), una nuova funzionalità del browser all'interno della PWA Kiwix che migliora la gestione dei file, offrendo un accesso migliorato agli archivi senza richieste di autorizzazione. L'articolo discute le sfide e mette in evidenza i potenziali sviluppi futuri di questo nuovo sistema di file.

Informazioni su Kiwix

Più di 30 anni dopo la nascita del web, un terzo della popolazione mondiale è ancora in attesa di un accesso affidabile a internet secondo l'Unione internazionale delle telecomunicazioni. È qui che termina la storia? Ovviamente no. Il team di Kiwix, un'organizzazione non profit con sede in Svizzera, ha sviluppato un ecosistema di app e contenuti open source che ha lo scopo di mettere a disposizione la conoscenza a persone con accesso limitato o nullo a internet. L'idea è che se tu non riesci ad accedere facilmente a internet, qualcuno può scaricare risorse chiave per te, dove e quando è disponibile la connettività, e archiviarle localmente per un uso offline successivo. Ora molti siti importanti, ad esempio Wikipedia, Project Gutenberg, Stack Exchange o persino i talk di TED, possono essere convertiti in archivi altamente compressi, chiamati file ZIM, e letti al volo dal browser Kiwix.

Gli archivi ZIM utilizzano la compressione Zstandard (ZSTD) altamente efficiente (le versioni precedenti utilizzavano XZ), principalmente per memorizzare HTML, JavaScript e CSS, mentre le immagini vengono solitamente convertite in formato WebP compresso. Ogni ZIM include anche un URL e un indice dei titoli. La compressione è fondamentale, poiché l'intera Wikipedia in inglese (6,4 milioni di articoli, più immagini) viene compressa a 97 GB dopo la conversione al formato ZIM, che sembra molto finché non lo fai rendersi conto che la somma di tutte le conoscenze umane ora può essere inserita in uno smartphone Android di media gamma. Sono inoltre disponibili molte risorse minori, comprese versioni a tema di Wikipedia, come matematica, medicina e così via.

Kiwix offre una gamma di app native destinate all'utilizzo su computer (Windows/Linux/macOS) e dispositivi mobili (iOS/Android). Questo studi di caso, tuttavia, si concentrerà sull'app web progressiva (PWA) che ha lo scopo di essere una soluzione universale e semplice per qualsiasi dispositivo con un browser moderno.

Analizzeremo le sfide poste dallo sviluppo di un'app web universale che deve fornire un accesso rapido a grandi archivi di contenuti completamente offline e ad alcune API JavaScript moderne, in particolare l'API File System Access e l'Origin Private File System, che forniscono soluzioni innovative ed entusiasmanti a queste sfide.

Un'app web da utilizzare offline?

Gli utenti di Kiwix sono un gruppo eterogeneo con esigenze molto diverse e Kiwix ha poco o nessun controllo sui dispositivi e sui sistemi operativi su cui accederanno ai contenuti. Alcuni di questi possono essere lenti o obsoleti, soprattutto nelle zone a basso reddito del mondo. Sebbene Kiwix cerchi di coprire il maggior numero possibile di casi d'uso, l'organizzazione ha anche capito che poteva raggiungere ancora più utenti utilizzando il software più universale su qualsiasi dispositivo: il browser web. Così, ispirati dalla legge di Atwood, che afferma che qualsiasi applicazione che può essere scritta in JavaScript, verrà scritta in JavaScript, circa 10 anni fa alcuni sviluppatori di Kiwix si sono messi al lavoro per eseguire il porting del software Kiwix da C++ a JavaScript.

La prima versione di questa porta, chiamata Kiwix HTML5, era destinata all'ormai non più esistente Firefox OS e alle estensioni del browser. Al suo interno era (ed è) un motore di decompressione C++ (XZ e ZSTD) compilato nel linguaggio JavaScript intermedio di ASM.js e in seguito in Wasm o WebAssembly, utilizzando il compilatore Emscripten. Successivamente, rinominate Kiwix JS, le estensioni del browser sono ancora sviluppate attivamente.

Browser offline Kiwix JS

Inserisci l'app web progressiva (PWA). Rendendosi conto del potenziale di questa tecnologia, gli sviluppatori di Kiwix hanno creato una versione PWA dedicata di Kiwix JS e si sono adoperati per aggiungere integrazioni del sistema operativo che consentissero all'app di offrire funzionalità simili a quelle native, in particolare per quanto riguarda l'utilizzo offline, l'installazione, la gestione dei file e l'accesso al file system.

Le PWA offline sono estremamente leggere e, di conseguenza, sono perfette per contesti in cui l'uso di internet mobile è intermittente o costoso. La tecnologia alla base di questa operazione è l'API Service Worker e la relativa API Cache, utilizzate da tutte le app basate su Kiwix JS. Queste API consentono alle app di agire come server, intercettando le richieste di recupero dal documento o dall'articolo principale visualizzato e reindirizzandole al backend (JS) per estrarre e creare una risposta dall'archivio ZIM.

Spazio di archiviazione ovunque

Date le grandi dimensioni degli archivi ZIM, lo spazio di archiviazione e l'accesso, soprattutto su dispositivi mobili, sono probabilmente il problema più grande per gli sviluppatori di Kiwix. Molti Utenti finali di Kiwix scaricano i contenuti in-app, quando è disponibile internet, per usarli successivamente offline. Altri utenti scaricano i contenuti su un PC utilizzando un torrent, poi li trasferiscono su un dispositivo mobile o tablet e alcuni scambiano contenuti su chiavette USB o hard disk portatili in aree con una connessione a internet mobile intermittente o costosa. Tutti questi modi di accedere ai contenuti da posizioni arbitrarie accessibili agli utenti devono essere supportati da Kiwix JS e Kiwix PWA.

Inizialmente, è stata l'API File a consentire a Kiwix JS di leggere archivi enormi di centinaia di GB (uno dei nostri archivi ZIM è di 166 GB) anche su dispositivi con poca memoria. Questa API è supportata universalmente in qualsiasi browser, anche in quelli molto vecchi, pertanto funge da opzione di riserva universale quando le API più recenti non sono supportate. È facile come definire un elemento input in HTML, nel caso di Kiwix:

<input
  type="file"
  accept="application/octet-stream,.zim,.zimaa,.zimab,.zimac, ..."
  value="Select folder with ZIM files"
  id="archiveFilesLegacy"
  multiple
/>

Una volta selezionato, l'elemento di input contiene gli oggetti File, che sono essenzialmente metadati che fanno riferimento ai dati sottostanti archiviati. Tecnicamente, il backend object-oriented di Kiwix, scritto in JavaScript lato client puro, legge piccole sezioni dell'ampio archivio in base alle necessità. Se questi slice devono essere decompressi, il backend li passa allo scompressore Wasm, ottenendo ulteriori slice, se richiesti, fino a quando non viene decompreso un blob completo (di solito un articolo o un asset). Ciò significa che l'archivio di grandi dimensioni non deve mai essere letto interamente nella memoria.

Universale come è, l'API File ha uno svantaggio che ha fatto apparire le app Kiwix JS come scomode e antiquate rispetto alle app native: richiede all'utente di scegliere gli archivi utilizzando un selettore di file o di trascinare un file nell'app ogni volta che viene lanciata, perché con questa API non è possibile mantenere le autorizzazioni di accesso da una sessione all'altra.

Per mitigare questa scarsa UX, come molti sviluppatori, gli sviluppatori di Kiwix JS hanno inizialmente scelto la strada di Electron. ElectronJS è un fantastico framework che offre funzionalità potenti, tra cui l'accesso completo al sistema di file utilizzando le API Node. Tuttavia, presenta alcuni svantaggi ben noti:

  • Funziona solo su sistemi operativi desktop.
  • È grande e pesante (70-100 MB).

Le dimensioni delle app Electron, dovute al fatto che in ogni app è inclusa una copia completa di Chromium, sono molto svantaggiose rispetto ai soli 5,1 MB della PWA ridotta a icona e in bundle.

C'è un modo in cui Kiwix può migliorare la situazione per gli utenti della PWA?

In soccorso l'API File System Access

Intorno al 2019, Kiwix è venuta a conoscenza di un'API emergente in fase di prova dell'origine in Chrome 78, chiamata API Native File System. Prometteva la possibilità di ottenere un handle file per un file o una cartella e di archiviarlo in un database IndexedDB. È fondamentale che questo handle persista tra le sessioni dell'app, in modo che l'utente non debba scegliere di nuovo il file o la cartella quando riavvia l'app (anche se deve rispondere a una breve richiesta di autorizzazione). Al momento in cui ha raggiunto la produzione, è stata rinominata API File System Access e le parti principali standardizzate da WHATWG come API File System (FSA).

Come funziona la parte dell'API relativa all'accesso al file system? Ecco alcuni punti importanti da tenere a mente:

  • È un'API asincrona (tranne le funzioni specializzate nei web worker).
  • I selettori di file o directory devono essere avviati in modo programmatico registrando un gesto dell'utente (clic o tocco su un elemento UI).
  • Affinché l'utente possa concedere nuovamente l'autorizzazione ad accedere a un file selezionato in precedenza (in una nuova sessione), è necessario anche un gesto dell'utente, in quanto il browser rifiuterà di mostrare la richiesta di autorizzazione se non viene avviata da un gesto dell'utente.

Il codice è relativamente semplice, a parte il fatto che devi utilizzare la complicata API IndexedDB per memorizzare gli handle di file e directory. La buona notizia è che ci sono un paio di librerie che si occupano di molto del lavoro pesante per te, ad esempio browser-fs-access. In Kiwix JS abbiamo deciso di lavorare direttamente con le API, che sono molto ben documentate.

Apertura dei selettori di file e directory

L'apertura di un selettore di file ha un aspetto simile a questo (qui vengono utilizzate le promesse, ma se preferisci async/await sugar, consulta il tutorial di Chrome per gli sviluppatori):

return window
  .showOpenFilePicker({ multiple: false })
  .then(function (fileHandles) {
    return processFileHandle(fileHandles[0]);
  })
  .catch(function (err) {
    // This is normal if app is launching
    console.warn(
      'User cancelled, or cannot access fs without user gesture',
      err,
    );
  });

Tieni presente che, per semplicità, questo codice elabora solo il primo file selezionato (e vieta di selezionarne più di uno). Se vuoi consentire di selezionare più file con { multiple: true }, devi semplicemente racchiudere tutte le promesse che elaborano ogni handle in un'istruzione Promise.all().then(...), ad esempio:

let promisesForFiles = fileHandles.map(function (fileHandle) {
    return processFileHandle(fileHandle);
});
return Promise.all(promisesForFiles).then(function (arrayOfFiles) {
    // Do something with the files array
    console.log(arrayOfFiles);
}).catch(function (err) {
    // Handle any errors that occurred during processing
    console.error('Error processing file handles!', err);
)};

Tuttavia, la scelta di più file è probabilmente migliore chiedendo all'utente di scegliere la directory che contiene quei file piuttosto che i singoli file al suo interno, soprattutto perché gli utenti Kiwix tendono a organizzare tutti i loro file ZIM nella stessa directory. Il codice per avviare il selettore di directory è quasi uguale a quello riportato sopra, tranne per il fatto che devi utilizzare window.showDirectoryPicker.then(function (dirHandle) { … });.

Elaborazione dell'handle del file o della directory

Una volta ottenuto l'handle, devi elaborarlo, quindi la funzione processFileHandle potrebbe avere il seguente aspetto:

function processFileHandle(fileHandle) {
  // Serialize fileHandle to indexedDB
  serializeFSHandletoIdxDB('pickedFSHandle', fileHandle, function (val) {
    console.debug('IndexedDB responded with ' + val);
  });
  return fileHandle.getFile().then(function (file) {
    // Do something with the file
    return file;
  });
}

Tieni presente che devi fornire la funzione per archiviare l'handle del file, ma non ci sono metodi pratici per questa operazione, a meno che non utilizzi una libreria di astrazione. L'implementazione di Kiwix di questo approccio può essere vista nel file cache.js, ma potrebbe essere semplificata notevolmente se utilizzato solo per archiviare e recuperare l'handle di un file o di una cartella.

L'elaborazione delle directory è un po' più complicata in quanto devi ripetere le voci nella directory scelta con entries.next() asincrono per trovare i file o i tipi di file che ti interessano. Ci sono vari modi per farlo, ma questo è il codice usato in Kiwix PWA:

let iterableEntryList = dirHandle.entries();
return iterateAsyncDirEntries(iterableEntryList, []).then(function (entryList) {
  // Do something with the entry list
  return entryList;
});

/**
 * Iterates FileSystemDirectoryHandle iterator and adds entries to an array
 * @param {Iterator} entries An asynchronous iterator of entries
 * @param {Array} archives An array to which to add the entries (may be empty)
 * @return {Promise<Array>} A Promise for an array of entries in the directory
 */
function iterateAsyncDirEntries(entries, archives) {
  return entries
    .next()
    .then(function (result) {
      if (!result.done) {
        let entry = result.value[1];
        // Filter for the files you want
        if (/\.zim(\w\w)?$/i.test(entry.name)) {
          archives.push(entry);
        }
        return iterateAsyncDirEntryArray(entries, archives);
      } else {
        // We've processed all the entries
        if (!archives.length) {
          console.warn('No archives found in the picked directory!');
        }
        return archives;
      }
    })
    .catch(function (err) {
      console.error('There was an error processing the directory!', err);
    });
}

Tieni presente che per ogni voce in entryList, in un secondo momento dovrai recuperare il file con entry.getFile().then(function (file) { … }) quando dovrai utilizzarlo o l'equivalente utilizzando const file = await entry.getFile() in un async function.

Possiamo fare di più?

Il requisito che richiede all'utente di concedere l'autorizzazione avviata con un gesto dell'utente ai successivi avvii dell'app aggiunge un piccolo attrito alla (ri)apertura di file e cartelle, ma è comunque molto più fluido rispetto all'obbligo di scegliere di nuovo un file. Al momento gli sviluppatori di Chromium stanno finalizzando il codice che consentirebbe di autorizzazioni permanenti per le PWA installate. Si tratta di una funzionalità molto richiesta da molti sviluppatori di PWA ed è molto attesa.

Ma se non dovessimo aspettare?! Di recente, gli sviluppatori di Kiwix hanno scoperto che è possibile eliminare tutte le richieste di autorizzazione in questo momento utilizzando una nuova funzionalità dell'API File Access supportata sia dai browser Chromium che Firefox (e parzialmente da Safari, ma ancora manca FileSystemWritableFileStream). Questa nuova funzionalità è il file system privato di Origin.

Diventa completamente nativo: il file system privato di origine

L'Origin Private File System (OPFS) è ancora una funzionalità sperimentale della PWA Kiwix, ma il team è entusiasta di incoraggiare gli utenti a provarlo perché colma in gran parte il divario tra le app native e le app web. Ecco i principali vantaggi:

  • È possibile accedere agli archivi nell'OPFS senza richieste di autorizzazione, anche al primo avvio. Gli utenti possono riprendere la lettura di un articolo e la navigazione in un archivio dal punto in cui avevano interrotto in una sessione precedente, senza alcuna difficoltà.
  • Offre accesso altamente ottimizzato ai file archiviati al suo interno: su Android abbiamo riscontrato miglioramenti in termini di velocità da cinque a dieci volte.

L'accesso ai file standard in Android tramite l'API File è estremamente lento, soprattutto (come spesso accade per gli utenti di Kiwix) se gli archivi di grandi dimensioni sono archiviati su una scheda microSD anziché nello spazio di archiviazione del dispositivo. Tutto cambia con questa nuova API. Sebbene la maggior parte degli utenti non sia in grado di archiviare un file da 97 GB in OPFS (che consuma spazio di archiviazione sul dispositivo, non su scheda microSD), è perfetto per archiviare archivi di piccole e medie dimensioni. Vuoi la enciclopedia medica più completa di WikiProject Medicina? Nessun problema, con 1,7 GB si adatta facilmente all'OPFS. Suggerimento: cerca othermdwiki_en_all_maxi nella libreria in-app.

Come funziona l'OPFS

L'OPFS è un file system fornito dal browser, separato per ogni origine, che può essere considerato simile allo spazio di archiviazione basato sulle app su Android. I file possono essere importati nell'OPFS dal file system visibile all'utente o possono essere scaricati direttamente al suo interno (l'API consente anche di creare file nell'OPFS). Una volta nell'OPFS, vengono isolate dal resto del dispositivo. Sui browser basati su Chromium per computer, è anche possibile esportare nuovamente i file dall'OPFS al file system visibile all'utente.

Per utilizzare il file system privato di Origin, il primo passaggio consiste nel richiederne l'accesso utilizzando navigator.storage.getDirectory() (di nuovo, se preferisci vedere il codice che utilizza navigator.storage.getDirectory(), leggi Il file system privato di Origin):

return navigator.storage
  .getDirectory()
  .then(function (handle) {
    return processDirHandle(handle);
  })
  .catch(function (err) {
    console.warn('Unable to get the OPFS directory entry', err);
  });

L'handle che ottieni è dello stesso tipo di FileSystemDirectoryHandle che ottieni da window.showDirectoryPicker() citato sopra, il che significa che puoi riutilizzare il codice che lo gestisce (e fortunatamente non è necessario memorizzarlo in indexedDB: recuperalo quando ti serve). Supponiamo che tu abbia già alcuni file nell'OPFS e che tu voglia utilizzarli. Quindi, utilizzando la funzione iterateAsyncDirEntries() mostrata in precedenza, potresti fare qualcosa di simile:

return navigator.storage.getDirectory().then(function (dirHandle) {
  let entries = dirHandle.entries();
  return iterateAsyncDirEntries(entries, [])
    .then(function (archiveList) {
      return archiveList;
    })
    .catch(function (err) {
      console.error('Unable to iterate OPFS entries', err);
    });
});

Non dimenticare che devi comunque utilizzare getFile() su qualsiasi voce con cui vuoi lavorare dall'array archiveList.

Importazione di file nell'OPFS

Ma come si fa a inserire i file in OPFS? Non così in fretta. Innanzitutto, devi stimare la quantità di spazio di archiviazione di cui hai bisogno e assicurarti che gli utenti non provino a inserire un file di 97 GB se non è possibile.

Ottenere la quota stimata è facile: navigator.storage.estimate().then(function (estimate) { … });. Un po' più difficile è capire come mostrare queste informazioni all'utente. Nell'app Kiwix, abbiamo optato per un piccolo riquadro in-app visibile proprio accanto alla casella di controllo che consente agli utenti di provare il formato OPFS:

Riquadro che mostra lo spazio di archiviazione utilizzato in percentuale e lo spazio di archiviazione disponibile rimanente in gigabyte.

Il riquadro viene compilato utilizzando estimate.quota e estimate.usage, ad esempio:

let OPFSQuota; // Global variable, so we don't have to keep checking it
return navigator.storage.estimate().then(function (estimate) {
  const percent = ((estimate.usage / estimate.quota) * 100).toFixed(2);
  OPFSQuota = estimate.quota - estimate.usage;
  document.getElementById('OPFSQuota').innerHTML =
    '<b>OPFS storage quota:</b><br />Used:&nbsp;<b>' +
    percent +
    '%</b>; ' +
    'Remaining:&nbsp;<b>' +
    (OPFSQuota / 1024 / 1024 / 1024).toFixed(2) +
    '&nbsp;GB</b>';
});

Come puoi vedere, è presente anche un pulsante che consente agli utenti di aggiungere file all'OPFS dal file system visibile all'utente. La buona notizia è che puoi semplicemente utilizzare l'API File per ottenere l'oggetto File (o gli oggetti File) necessari da importare. In effetti, è importante non utilizzare window.showOpenFilePicker() perché questo metodo non è supportato da Firefox, mentre OPFS è sicuramente supportato.

Il pulsante Aggiungi file visibile nello screenshot sopra non è un selettore di file precedente, ma click() un selettore precedente nascosto (elemento <input type="file" multiple … />) quando viene fatto clic o toccato. L'app acquisisce quindi l'evento change dell'input del file nascosto, controlla le dimensioni dei file e li rifiuta se sono troppo grandi per la quota. Se tutto va bene, chiedi all'utente se vuole aggiungerli:

archiveFilesLegacy.addEventListener('change', function (files) {
  const filesArray = Array.from(files.target.files);
  // Abort if user didn't select any files
  if (filesArray.length === 0) return;
  // Calculate the size of the picked files
  let filesSize = 0;
  filesArray.forEach(function (file) {
    filesSize += file.size;
  });
  // Check the size of the files does not exceed the quota
  if (filesSize > OPFSQuota) {
    // Oh no, files are too big! Tell user...
    console.log('Files would exceed the OPFS quota!');
  } else {
    // Ask user if they're sure... if user said yes...
    return importOPFSEntries(filesArray)
      .then(function () {
        // Tell user we successfully imported the archives
      })
      .catch(function (err) {
        // Tell user there was an error (error catching is important!)
      });
  }
});

Dialogo che chiede all&#39;utente se vuole aggiungere un elenco di file .zim al file system privato di origine.

Poiché su alcuni sistemi operativi, come Android, l'importazione degli archivi non è un'operazione molto rapida, Kiwix mostra anche un banner e un piccolo cursore durante l'importazione degli archivi. Il team non ha capito come aggiungere un indicatore di avanzamento per questa operazione: se ci riesci, rispondi su una cartolina.

Come ha fatto Kiwix a implementare la funzione importOPFSEntries()? Ciò comporta l'utilizzo del metodo fileHandle.createWriteable(), che consente di trasmettere in streaming ogni file nell'OPFS. Il browser si occupa di tutto il lavoro. (Kiwix utilizza le promesse per motivi legati al nostro codebase precedente, ma bisogna dire che in questo caso await produce una sintassi più semplice ed evita l'effetto piramide del destino.)

function importOPFSEntries(files) {
  // Get a handle on the OPFS directory
  return navigator.storage
    .getDirectory()
    .then(function (dir) {
      // Collect the promises for each file that we want to write
      let promises = files.map(function (file) {
        // Create the file and get a writeable handle on it
        return dir
          .getFileHandle(file.name, { create: true })
          .then(function (fileHandle) {
            // Get a writer for the file
            return fileHandle.createWritable().then(function (writer) {
              // Show a banner / spinner, then write the file
              return writer
                .write(file)
                .then(function () {
                  // Finished with this writer
                  return writer.close();
                })
                .catch(function (err) {
                  console.error('There was an error writing to the OPFS!', err);
                });
            });
          })
          .catch(function (err) {
            console.error('Unable to get file handle from OPFS!', err);
          });
      });
      // Return a promise that resolves when all the files have been written
      return Promise.all(promises);
    })
    .catch(function (err) {
      console.error('Unable to get a handle on the OPFS directory!', err);
    });
}

Scaricare uno stream di file direttamente nell'OPFS

Una variante di questo approccio è la possibilità di trasmettere in streaming un file da internet direttamente nell'OPFS, o in qualsiasi directory per la quale hai un handle di directory (ovvero, directory scelte con window.showDirectoryPicker()). Utilizza gli stessi principi del codice sopra, ma costruisce un Response composto da ReadableStream e un controller che accoda i byte letti dal file remoto. Il risultato Response.body viene quindi trasferito allo scrittore del nuovo file all'interno dell'OPFS.

In questo caso, Kiwix è in grado di conteggiare i byte che passano attraverso il ReadableStream, fornendo così all'utente un indicatore di avanzamento e avvertendolo anche di non uscire dall'app durante il download. Il codice è un po' troppo complicato per essere mostrato qui, ma poiché la nostra app è un'app FOSS, puoi esaminarne il codice sorgente se ti interessa fare qualcosa di simile. Ecco come appare l'interfaccia utente di Kiwix (i diversi valori di avanzamento mostrati di seguito sono dovuti al fatto che il banner viene aggiornato solo quando la percentuale cambia, ma il riquadro Avanzamento download viene aggiornato più regolarmente):

Interfaccia utente di Kiwix con una barra nella parte inferiore che avvisa l&#39;utente di non uscire dall&#39;app e mostra l&#39;avanzamento del download dell&#39;archivio .zim.

Poiché il download può essere un'operazione piuttosto lunga, Kiwix consente agli utenti di utilizzare liberamente l'app durante l'operazione, ma garantisce che il banner venga sempre visualizzato, in modo che gli utenti non dimentichino di chiudere l'app fino al completamento dell'operazione di download.

Implementazione di un mini gestore di file in-app

A questo punto, gli sviluppatori della PWA Kiwix hanno capito che non è sufficiente poter aggiungere file all'OPFS. L'app aveva anche bisogno di offrire agli utenti un modo per eliminare i file di cui non hanno più bisogno da quest'area di archiviazione e, idealmente, anche per esportare tutti i file bloccati in OPFS nel file system visibile all'utente. In effetti, si è reso necessario implementare un mini sistema di gestione dei file all'interno dell'app.

Un breve cenno all'favolosa estensione OPFS Explorer per Chrome (funziona anche in Edge). Aggiunge una scheda negli Strumenti per sviluppatori che ti consente di vedere esattamente cosa è contenuto nell'OPFS ed eliminare anche i file non validi o con errori. È stato indispensabile per verificare il funzionamento del codice, monitorare il comportamento dei download e, in generale, per mettere in ordine i nostri esperimenti di sviluppo.

L'esportazione dei file dipende dalla possibilità di ottenere un handle file su un file o una directory selezionati in cui Kiwix salverà il file esportato, pertanto funziona solo nei contesti in cui può utilizzare il metodo window.showSaveFilePicker(). Se i file Kiwix fossero più piccoli di diversi GB, potremmo creare un blob in memoria, assegnargli un URL e scaricarlo nel file system visibile all'utente. Purtroppo, non è possibile con archivi così grandi. Se supportata, l'esportazione è abbastanza semplice: è praticamente la stessa cosa, al contrario, del salvataggio di un file nell'OPFS (ottieni un handle del file da salvare, chiedi all'utente di scegliere una posizione in cui salvarlo con window.showSaveFilePicker(), quindi utilizza createWriteable() su saveHandle). Puoi visualizzare il codice nel repository.

L'eliminazione dei file è supportata da tutti i browser e può essere eseguita con un semplice dirHandle.removeEntry('filename'). Nel caso di Kiwix, abbiamo preferito eseguire l'iterazione delle voci OPFS come abbiamo fatto sopra, in modo da verificare prima che il file selezionato esista e chiedere conferma, ma potrebbe non essere necessario per tutti. Se ti interessa, puoi anche esaminare il nostro codice.

È stato deciso di non appesantire l'interfaccia utente di Kiwix con pulsanti che offrono queste opzioni, ma di posizionare piccole icone direttamente sotto l'elenco dell'archivio. Se tocchi una di queste icone, il colore dell'elenco degli archivi cambia, dando all'utente un indizio visivo su cosa sta per fare. L'utente fa clic o tocca uno degli archivi e viene eseguita l'operazione corrispondente (esportazione o eliminazione) (dopo la conferma).

Finestra di dialogo che chiede all&#39;utente se vuole eliminare un file .zim.

Infine, ecco una demo dello screencast di tutte le funzionalità di gestione dei file discusse in precedenza: aggiunta di un file in OPFS, download diretto di un file, eliminazione di un file ed esportazione nel file system visibile agli utenti.

Il lavoro di uno sviluppatore non finisce mai

OPFS è un'ottima innovazione per gli sviluppatori di PWA, in quanto offre funzionalità di gestione dei file molto potenti che contribuiscono notevolmente a colmare il divario tra le app native e le app web. Ma gli sviluppatori sono un gruppo triste e non sono mai abbastanza soddisfatti. OPFS è quasi perfetto, ma non del tutto… È fantastico che le funzionalità principali funzionino sia nei browser Chromium che in Firefox e che siano implementate su Android e computer. Ci auguriamo che l'intero insieme di funzionalità possa essere implementato anche su Safari e iOS a breve. Rimangono i seguenti problemi:

  • Attualmente Firefox applica un limite di 10 GB alla quota OPFS, indipendentemente dallo spazio di archiviazione di base disponibile. Sebbene per la maggior parte degli autori di PWA questo possa essere sufficiente, per Kiwix è piuttosto limitativo. Fortunatamente, i browser Chromium sono molto più generosi.
  • Al momento non è possibile esportare file di grandi dimensioni da OPFS al file system visibile all'utente sui browser mobile o su Firefox sul computer desktop, perché window.showSaveFilePicker() non è implementato. In questi browser, i file di grandi dimensioni vengono effettivamente intrappolati nell'OPFS. Ciò va contro lo spirito di Kiwix, che si basa sull'accesso aperto ai contenuti e sulla possibilità di condividere gli archivi tra gli utenti, soprattutto nelle aree con connettività a internet intermittente o costosa.
  • Gli utenti non possono controllare lo spazio di archiviazione che verrà utilizzato dal file system virtuale OPFS. Questo è particolarmente problematico sui dispositivi mobili, in cui gli utenti possono avere a disposizione molto spazio su una scheda microSD, ma pochissimo nello spazio di archiviazione del dispositivo.

Ma, tutto sommato, si tratta di piccoli problemi in un contesto che rappresenta un enorme passo avanti per l'accesso ai file nelle PWA. Il team di Kiwix PWA è molto grato agli sviluppatori e ai sostenitori di Chromium che hanno proposto e progettato per primi l'API File System Access e per il duro lavoro di raggiungimento del consenso tra i fornitori di browser sull'importanza del file system privato di origine. Per la PWA Kiwix JS, ha risolto moltissimi dei problemi di UX che hanno rallentato l'app in passato e ci aiuta nel nostro intento di migliorare l'accessibilità dei contenuti di Kiwix per tutti. Prova la PWA Kiwix e comunica agli sviluppatori cosa ne pensi.

Per alcune utili risorse sulle funzionalità delle PWA, dai un'occhiata a questi siti: