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 in piedi su un tavolo semplice con una sedia di plastica sulla sinistra. Lo sfondo sembra una scuola in un paese in via di sviluppo.

Questo case study esplora come Kiwix, un'organizzazione non profit, utilizza la tecnologia app web progressiva e l'API File System Access per consentire agli utenti di scaricare e archiviare grandi archivi Internet per l'utilizzo offline. Scopri l'implementazione tecnica del codice gestito con OPFS (Origin Private File System), una nuova funzionalità del browser all'interno della PWA Kiwix che migliora la gestione dei file, fornendo un accesso migliore agli archivi senza richieste di autorizzazione. L'articolo tratta le sfide ed evidenzia i potenziali sviluppi futuri in questo nuovo file system.

Informazioni su Kiwix

Più di 30 anni dopo la nascita del web, un terzo della popolazione mondiale attende ancora un accesso affidabile a internet secondo l'Unione internazionale delle telecomunicazioni. È qui che finisce la storia? Ovviamente no. Le persone di Kiwix, un'organizzazione non profit con sede in Svizzera, hanno sviluppato un ecosistema di app e contenuti open source che mira a rendere le conoscenze disponibili a chi ha un accesso a Internet limitato o assente. L'idea è che, se tu non riesci ad accedere facilmente a internet, qualcuno può scaricare per te le risorse chiave, dove e quando è disponibile la connettività e archiviarle localmente per utilizzarle offline in un secondo momento. Molti siti importanti, ad esempio Wikipedia, Project Gutenberg, Stack Exchange o persino TED talk, ora possono essere convertiti in archivi altamente compressi, chiamati file ZIM, e letti al volo dal browser Kiwix.

Gli archivi ZIM utilizzano una compressione Zstandard (ZSTD) estremamente efficiente (le versioni precedenti utilizzavano XZ), principalmente per la memorizzazione di HTML, JavaScript e CSS, mentre le immagini vengono generalmente 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 in formato ZIM, il che sembra molto, finché non ci si rende conto che la somma di tutte le conoscenze umane può ora stare su uno smartphone Android di fascia media. Vengono inoltre offerte molte risorse minori, tra cui versioni di Wikipedia a tema, come matematica, medicina e così via.

Kiwix offre una gamma di app native destinate all'utilizzo da computer (Windows/Linux/macOS) e mobile (iOS/Android). Questo case study, tuttavia, si concentrerà sull'app web progressiva (PWA), che mira a essere una soluzione semplice e universale per qualsiasi dispositivo dotato di un browser moderno.

Esamineremo le sfide poste nello sviluppo di un'app web universale che deve fornire un accesso rapido ad archivi di contenuti di grandi dimensioni completamente offline e di 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 eclettici con molte esigenze diverse e Kiwix ha poco o nessun controllo sui dispositivi e sistemi operativi su cui accetteranno ai loro contenuti. Alcuni di questi dispositivi potrebbero essere lenti o obsoleti, soprattutto nelle aree a basso reddito del mondo. Kiwix cerca di coprire il maggior numero possibile di casi d'uso, ma l'organizzazione si è anche resa conto che poteva raggiungere ancora più utenti utilizzando il software più universale su qualsiasi dispositivo: il browser web. Pertanto, in base alla legge di Atwood, che afferma che qualsiasi applicazione che può essere scritta in JavaScript verrà infine scritta in JavaScript, alcuni sviluppatori Kiwix, circa 10 anni fa, si occuperanno di trasferire il software Kiwix da C++ a JavaScript.

La prima versione di questa porta, chiamata Kiwix HTML5, era destinata all'ormai defunto sistema operativo Firefox e alle estensioni del browser. Alla base c'era (ed è) un motore di decompressione C++ (XZ e ZSTD) compilato nel linguaggio JavaScript intermedio di ASM.js e successivamente Wasm, o WebAssembly, utilizzando il compilatore Emscripten. Successivamente rinominate Kiwix JS, le estensioni del browser sono ancora sviluppate attivamente.

Browser Kiwix JS offline

Inserisci l'app web progressiva (PWA). Realizzando il potenziale di questa tecnologia, gli sviluppatori della Kiwix hanno creato una versione PWA dedicata di Kiwix JS e hanno deciso di aggiungere integrazioni del sistema operativo che avrebbero consentito all'app di offrire funzionalità simili a quelle native, in particolare nei settori dell'utilizzo offline, dell'installazione, della gestione dei file e dell'accesso al file system.

Le PWA offline sono estremamente leggere, quindi sono perfette per contesti in cui c'è una connessione a internet mobile intermittente o costosa. La tecnologia alla base di questo processo è 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 l'articolo principale visualizzati e reindirizzandole al backend (JS) per estrarre e creare una risposta dall'archivio ZIM.

Spazio di archiviazione ovunque

Data le grandi dimensioni degli archivi ZIM, lo spazio di archiviazione e l'accesso, in particolare sui dispositivi mobili, sono probabilmente il problema maggiore per gli sviluppatori Kiwix. Molti utenti finali Kiwix scaricano contenuti in-app, quando è disponibile internet, per utilizzarli in un secondo momento offline. Altri utenti scaricano file su un PC utilizzando un torrent, quindi li trasferiscono su un dispositivo mobile o tablet e alcuni si scambiano contenuti su chiavette USB o dischi rigidi portatili in aree con connessione internet mobile instabile o costosa. Tutti questi modi per accedere ai contenuti da posizioni arbitrarie accessibili dagli utenti devono essere supportati da Kiwix JS e Kiwix PWA.

Inizialmente, Kiwix JS ha potuto leggere enormi archivi, su centinaia di GB (uno dei nostri archivi ZIM pari a 166 GB, anche su dispositivi con memoria ridotta, è stata l'API File. Questa API è universalmente supportata in qualsiasi browser, anche nei browser molto vecchi, pertanto agisce come riserva universale di riserva quando non sono supportate le API più recenti. Nel caso di Kiwix, nel caso di Kiwix è facile quanto definire un elemento input in HTML:

<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 nello spazio di archiviazione. Tecnicamente, il backend di Kiwix orientato agli oggetti, scritto in JavaScript lato client puro, legge piccole sezioni del grande archivio secondo necessità. Se queste sezioni devono essere decompresse, il backend le passa al decompressore Wasm e, se richiesto, riceve ulteriori sezioni, fino a quando non viene decompresso un BLOB completo (di solito un articolo o una risorsa). Ciò significa che l'archivio di grandi dimensioni non deve mai essere letto interamente in memoria.

Universale così com'è, l'API File presenta uno svantaggio che fa apparire le app Kiwix JS inconsuete e obsolete rispetto alle app native: richiede all'utente di scegliere gli archivi utilizzando un selettore di file o trascinare un file nell'app ogni volta che viene avviata, perché con questa API non è possibile mantenere le autorizzazioni di accesso da una sessione all'altra.

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

  • Funziona solo su sistemi operativi desktop.
  • Le dimensioni sono grandi e pesanti (70-100 MB).

Le dimensioni delle app Electron, dato che una copia completa di Chromium è inclusa in ogni app, sono sfavorevoli rispetto a solo 5,1 MB per la PWA ridotta e in bundle.

Quindi, c'era un modo in cui Kiwix poteva migliorare la situazione per gli utenti della PWA?

Puoi ricorrere all'API File System Access

Intorno al 2019, Kiwix è venuta a conoscenza di un'API emergente che era in fase di prova dell'origine in Chrome 78, quindi chiamata API Native File System. Prometteva la possibilità di ottenere un handle per un file o una cartella e di archiviarlo in un database IndexedDB. Fondamentalmente, questo handle persiste tra le sessioni dell'app, quindi l'utente non è costretto a scegliere di nuovo il file o la cartella al riavvio dell'app (anche se deve rispondere a una rapida richiesta di autorizzazione). Al momento della produzione, è stata rinominata API File System Access e le parti principali standardizzate da WhatWG come API File System (FSA).

Dunque, come funziona la parte dell'API relativa all'accesso al file system? Alcuni punti importanti da notare:

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

Il codice è relativamente semplice, a parte l'utilizzo dell'API IndexedDB per archiviare gli handle di file e directory. La buona notizia è che ci sono un paio di librerie che svolgono molte delle attività più impegnative per te, come browser-fs-access. Noi di 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 file è simile al seguente (in questo caso, utilizzando Promises, ma se preferisci lo zucchero async/await, 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 non consente di sceglierne più di uno). Se vuoi consentire la scelta di più file con { multiple: true }, devi semplicemente aggregare tutte le promesse che elaborano ciascun 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 di Kiwix tendono a organizzare tutti i loro file ZIM nella stessa directory. Il codice per avviare il selettore di directory è quasi uguale a quello sopra, ad eccezione del fatto che utilizzi window.showDirectoryPicker.then(function (dirHandle) { … });.

Elaborazione dell'handle del file o della directory in corso...

Una volta ottenuto l'handle, devi elaborarlo, quindi la funzione processFileHandle potrebbe presentarsi come segue:

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; non esistono metodi per farlo, a meno che non utilizzi una libreria di astrazioni. L'implementazione di Kiwix in tal senso può essere osservata nel file cache.js, ma potrebbe essere notevolmente semplificata se utilizzata solo per archiviare e recuperare un handle di file o cartella.

L'elaborazione delle directory è un po' più complicata perché devi eseguire l'iterazione delle voci nella directory selezionata con il valore asincrono entries.next() per trovare i file o i tipi di file desiderati. Ci sono vari modi per farlo, ma questo è il codice utilizzato nella PWA Kiwix, per riassumere:

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 ottenere il file con entry.getFile().then(function (file) { … }) quando devi utilizzarlo o l'equivalente utilizzando const file = await entry.getFile() in un async function.

Possiamo andare oltre?

Il requisito che prevede che l'utente conceda l'autorizzazione avviato con un gesto dell'utente ai successivi lanci dell'app aggiunge un piccolo problema alla riapertura (ri)apertura di file e cartelle, ma è comunque molto più fluido rispetto alla necessità di riselezionare un file. Gli sviluppatori di Chromium stanno attualmente finalizzando il codice che consentirebbe di disporre di autorizzazioni permanenti per le PWA installate. Questo è un aspetto che molti sviluppatori di PWA richiedono e sono molto attesi.

E se non dovessimo aspettare? Gli sviluppatori di Kiwix hanno recentemente scoperto che al momento è possibile eliminare tutte le richieste di autorizzazione, utilizzando una nuovissima funzionalità dell'API File Access supportata dai browser Chromium e Firefox (e parzialmente supportata da Safari, ma ancora mancante FileSystemWritableFileStream). Questa nuova funzionalità è l'Origin Private File System.

Modalità completamente nativa: il file system privato di origine

Il file system privato di origine (OPFS) è ancora una funzionalità sperimentale nella PWA Kiwix, ma il team è davvero entusiasta di incoraggiare gli utenti a provarlo perché colma in gran parte il divario tra app native e app web. Ecco i principali vantaggi:

  • È possibile accedere agli archivi in OPFS senza richieste di autorizzazione, anche all'avvio. Gli utenti possono riprendere a leggere un articolo e a sfogliare un archivio dal punto in cui l'avevano interrotto in una sessione precedente, senza alcun problema.
  • Fornisce un accesso altamente ottimizzato ai file archiviati: su Android notiamo miglioramenti della velocità da cinque a dieci volte più velocemente.

L'accesso standard ai file in Android con l'API File è estremamente lento, in particolare (come spesso avviene per gli utenti del 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. Anche se la maggior parte degli utenti non è in grado di archiviare un file da 97 GB in OPFS (che utilizza spazio di archiviazione sul dispositivo, non su scheda microSD), è perfetta per archiviare file di piccole e medie dimensioni. Vuoi l'enciclopedia medica più completa di WikiProject Medicine? Nessun problema, a 1,7 GB si adatta facilmente all'OPFS! Suggerimento: cerca othermdwiki_en_all_maxi nella raccolta in-app.

Come funziona OPFS

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

Per utilizzare il file OPFS, il primo passaggio è richiedere l'accesso utilizzando navigator.storage.getDirectory() (di nuovo, se preferisci visualizzare il codice utilizzando await, leggi The Origin Private File System):

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 è lo stesso tipo di FileSystemDirectoryHandle che hai ricevuto da window.showDirectoryPicker() menzionato sopra, il che significa che puoi riutilizzare il codice che lo gestisce (per fortuna, non c'è bisogno di archiviarlo in indexedDB: basta riceverlo quando ne hai bisogno). Supponiamo che tu abbia già alcuni file in OPFS e che tu voglia utilizzarli, quindi, utilizzando la funzione iterateAsyncDirEntries() mostrata in precedenza, puoi eseguire azioni come:

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 di utilizzare ancora getFile() su qualsiasi voce con cui vuoi lavorare dall'array archiveList.

Importazione di file in OPFS

Allora, come si fa a inserire i file in OPFS? Non così in fretta! Innanzitutto, devi stimare la quantità di spazio di archiviazione da utilizzare e assicurarti che gli utenti non provino a inserire un file da 97 GB se non sta per adattarsi.

Ottenere la quota stimata è facile: navigator.storage.estimate().then(function (estimate) { … });. Un po' più difficile è capire come mostrare questo all'utente. Nell'app Kiwix, abbiamo optato per un piccolo riquadro in-app visibile accanto alla casella di controllo che consente agli utenti di provare la funzionalità 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, esiste anche un pulsante che consente agli utenti di aggiungere file in OPFS dal file system visibile all'utente. La buona notizia è che puoi semplicemente utilizzare l'API File per ottenere gli oggetti File necessari da importare. Infatti, è importante non utilizzare window.showOpenFilePicker() perché questo metodo non è supportato da Firefox, mentre il formato OPFS è decisamente supportato.

Il pulsante Aggiungi file visibile nello screenshot sopra non è un selettore file precedente, ma click() un selettore precedente nascosto (elemento <input type="file" multiple … />) quando viene fatto clic o toccato. L'app acquisisce quindi solo l'evento change dell'input del file nascosto, controlla le dimensioni dei file e li rifiuta se sono troppo grandi per la quota. Se non c'è problema, 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!)
      });
  }
});

Finestra di 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 è l'operazione più veloce, Kiwix mostra anche un banner e una piccola rotellina durante l'importazione degli archivi. Il team non ha capito come aggiungere un indicatore di avanzamento per questa operazione: se la gestisci, rispondi su una cartolina.

Quindi, in che modo Kiwix ha implementato la funzione importOPFSEntries()? Ciò comporta l'utilizzo del metodo fileHandle.createWriteable(), che consente di trasmettere in streaming ogni file in OPFS. Tutto il lavoro viene gestito dal browser. (Kiwix sta usando Promises in questo caso per motivi legati al nostro codebase legacy, ma va detto che in questo caso await produce una sintassi più semplice ed evita la piramide dell'effetto catastrofico.)

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);
    });
}

Download di un flusso di file direttamente in OPFS

Una variante è la possibilità di trasmettere un flusso di un file da internet direttamente in OPFS o in qualsiasi directory per la quale disponi di un handle di directory (ovvero directory selezionate con window.showDirectoryPicker()). Utilizza gli stessi principi del codice precedente, ma crea un Response costituito da ReadableStream e da un controller che accoda i byte letti dal file remoto. Il risultato Response.body viene quindi indirizzato all'autore del nuovo file all'interno dell'OPFS.

In questo caso, Kiwix è in grado di conteggiare i byte che passano attraverso ReadableStream, quindi fornire un indicatore di avanzamento all'utente e avvisarlo di non chiudere l'app durante il download. Il codice è un po' troppo contorto per essere mostrato qui, ma poiché la nostra app è un'app FOSS, puoi esaminare il codice sorgente se ti interessa fare qualcosa di simile. Ecco l'aspetto dell'interfaccia utente di Kiwix (i diversi valori di avanzamento mostrati di seguito riguardano il fatto che aggiorna il banner solo quando la percentuale cambia, ma aggiorna il riquadro Avanzamento del download 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 l'app liberamente durante l'operazione, ma garantisce che il banner venga sempre visualizzato, in modo che agli utenti venga ricordato di non chiudere l'app fino al completamento dell'operazione di download.

Implementazione di un gestore di file mini in-app

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

Un rapido ringraziamento alla favolosa estensione OPFS Explorer per Chrome (funziona anche in Edge). Aggiunge una scheda negli strumenti per sviluppatori che consente di vedere esattamente cosa è presente nel file OPFS e di eliminare i file non autorizzati o non riusciti. È stato prezioso per verificare se il codice funzionava, monitorare il comportamento dei download e, in generale, ripulire i nostri esperimenti di sviluppo.

L'esportazione dei file dipende dalla capacità di ottenere un handle di file su un file o una directory selezionati in cui Kiwix salverà il file esportato, quindi funziona solo in contesti in cui è possibile utilizzare il metodo window.showSaveFilePicker(). Se i file Kiwix fossero più piccoli di diversi GB, saremmo in grado di creare un blob in memoria, dargli un URL e quindi scaricarlo nel file system visibile all'utente. Purtroppo, con archivi così grandi, questo non è possibile. Se supportata, l'esportazione è piuttosto semplice: praticamente la stessa, al contrario, del salvataggio di un file in OPFS (ottieni un handle sul file da salvare, chiedi all'utente di scegliere una posizione in cui salvarlo, poi usa createWriteable() su saveHandle). Puoi vedere il codice nel repository.window.showSaveFilePicker()

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 poter verificare prima che il file selezionato esista e chiedere conferma, ma questo potrebbe non essere necessario per tutti. Se ti interessa, puoi anche esaminare il nostro codice.

Abbiamo deciso di non ingombrare l'interfaccia utente di Kiwix con pulsanti che offrono queste opzioni, bensì di posizionare piccole icone direttamente sotto l'elenco di archivi. Toccando una di queste icone, il colore dell'elenco in archivio cambierà, come un indizio visivo per l'utente su cosa sta per fare. L'utente poi fa clic o tocca uno degli archivi e l'operazione corrispondente (esportazione o eliminazione) viene eseguita (dopo la conferma).

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

Infine, ecco una demo degli screencast di tutte le funzionalità di gestione dei file di cui sopra: aggiungendo un file al file OPFS, scaricando direttamente un file al suo interno, eliminando un file ed esportandolo nel file system visibile all'utente.

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 davvero potenti che aiutano a colmare il divario tra le app native e le app web. Ma gli sviluppatori sono una squadra desolata, non sono mai abbastanza soddisfatti. Il file OPFS è quasi perfetto, ma non esattamente... È fantastico che le funzionalità principali funzionino sia nei browser Chromium che in Firefox e che vengono implementate sia su Android che su desktop. Ci auguriamo che a breve l'insieme completo di funzionalità venga implementato anche in Safari e iOS. Permangono i seguenti problemi:

  • Al momento Firefox impone un limite di 10 GB alla quota OPFS, indipendentemente dalla quantità di spazio su disco sottostante disponibile. Sebbene per la maggior parte degli autori di PWA questo possa essere ampio, per Kiwix è abbastanza restrittivo. 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 a Firefox per desktop, perché window.showSaveFilePicker() non è implementato. In questi browser, i file di grandi dimensioni vengono effettivamente bloccati in OPFS. Ciò è in contrasto con l'etica del Kiwix dell'accesso libero ai contenuti e la possibilità di condividere archivi tra gli utenti, soprattutto in aree con connessione a internet intermittente o costosa.
  • Gli utenti non possono controllare lo spazio di archiviazione che verrà utilizzato dal file system virtuale OPFS. Ciò è particolarmente problematico sui dispositivi mobili, in cui gli utenti potrebbero avere grandi quantità di spazio sulla scheda microSD, ma una quantità molto ridotta di spazio di archiviazione del dispositivo.

Ma nel complesso si tratta di piccoli problemi in ciò che altrimenti rappresenta un enorme passo avanti per l'accesso ai file nelle PWA. Il team PWA Kiwix è molto grato agli sviluppatori e ai sostenitori di Chromium che hanno proposto e progettato per la prima volta l'API File System Access, nonché per il duro lavoro necessario a raggiungere il consenso tra i fornitori di browser sull'importanza dell'Origin Private File System. Per Kiwix JS PWA, ha risolto molti dei problemi di UX che hanno tormentato l'app in passato e ci aiuta nel nostro tentativo di migliorare l'accessibilità dei contenuti Kiwix per tutti. Provate la PWA Kiwix e dite agli sviluppatori cosa ne pensate.

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