Comment la PWA Kiwix permet aux utilisateurs de stocker des gigaoctets de données sur Internet pour une utilisation hors connexion

Des personnes se rassemblent autour d'un ordinateur portable posé sur une table simple avec une chaise en plastique à gauche. L'arrière-plan ressemble à une école dans un pays en développement.

Cette étude de cas explore comment Kiwix, une organisation à but non lucratif, utilise la technologie d'application Web progressive et l'API File System Access pour permettre aux utilisateurs de télécharger et de stocker de grandes archives Internet à utiliser hors connexion. Découvrez l'implémentation technique du code traitant du système de fichiers privés d'origine (OPFS, Origin Private File System), une nouvelle fonctionnalité de navigateur dans la PWA Kiwix qui améliore la gestion des fichiers et offre un accès amélioré aux archives sans invites d'autorisation. L'article aborde les défis et met en avant les futurs développements potentiels de ce nouveau système de fichiers.

À propos de Kiwix

Plus de 30 ans après la naissance du Web, un tiers de la population mondiale attend toujours un accès fiable à Internet, selon l'Union internationale des télécommunications. Est-ce là où l'histoire se termine ? Bien sûr que non. Les membres de Kiwix, une organisation à but non lucratif basée en Suisse, ont développé un écosystème d'applications et de contenus Open Source visant à mettre les connaissances à la disposition des personnes ayant un accès limité à Internet ou n'y ayant pas accès du tout. L'idée est que si vous ne pouvez pas facilement accéder à Internet, quelqu'un peut télécharger pour vous des ressources clés, là où et quand la connectivité est disponible, et les stocker localement pour une utilisation hors connexion ultérieure. De nombreux sites essentiels, comme Wikipedia, Project Gutenberg, Stack Exchange ou même les conférences TED, peuvent désormais être convertis en archives hautement compressées, appelées fichiers ZIM, et lus instantanément par le navigateur Kiwix.

Les archives ZIM utilisent une compression Zstandard (ZSTD) très efficace (les versions antérieures utilisaient XZ), principalement pour stocker du code HTML, JavaScript et CSS, tandis que les images sont généralement converties au format WebP compressé. Chaque fichier ZIM inclut également une URL et un index de titres. La compression est essentielle ici, car l'intégralité de Wikipédia en anglais (6,4 millions d'articles, plus des images) est compressée à 97 Go après conversion au format ZIM, ce qui semble beaucoup jusqu'à ce que vous réalisiez que la somme de toutes les connaissances humaines peut désormais tenir sur un téléphone Android de milieu de gamme. De nombreuses ressources plus petites sont également proposées, y compris des versions thématiques de Wikipédia, telles que les mathématiques, la médecine, etc.

Kiwix propose une gamme d'applications natives ciblant à la fois l'utilisation sur ordinateur (Windows/Linux/macOS) et sur mobile (iOS/Android). Cette étude de cas se concentrera toutefois sur les progressive web apps (PWA), qui visent à être une solution universelle et simple pour tout appareil doté d'un navigateur moderne.

Nous allons examiner les défis posés par le développement d'une application Web universelle qui doit fournir un accès rapide à de grandes archives de contenu entièrement hors connexion, ainsi que certaines API JavaScript modernes, en particulier l'API File System Access et le Origin Private File System, qui fournissent des solutions innovantes et intéressantes à ces défis.

Une application Web pour une utilisation hors connexion ?

Les utilisateurs de Kiwix sont un groupe éclectique aux besoins très différents. Kiwix a peu ou pas de contrôle sur les appareils et les systèmes d'exploitation sur lesquels ils accéderont à leurs contenus. Certains de ces appareils peuvent être lents ou obsolètes, en particulier dans les régions à faible revenu du monde. Bien que Kiwix essaie de couvrir autant de cas d'utilisation que possible, l'organisation a également réalisé qu'elle pouvait toucher encore plus d'utilisateurs en utilisant le logiciel le plus universel sur n'importe quel appareil: le navigateur Web. Inspirés par la loi d'Atwood, qui stipule que toute application pouvant être écrite en JavaScript finira par l'être, certains développeurs Kiwix, il y a environ 10 ans, se sont lancés dans le portage du logiciel Kiwix de C++ vers JavaScript.

La première version de ce port, appelée Kiwix HTML5, était destinée à l'OS Firefox (désormais disparu) et aux extensions de navigateur. Au cœur de WebAssembly se trouvait (et se trouve) un moteur de décompression C++ (XZ et ZSTD) compilé dans le langage JavaScript intermédiaire ASM.js, puis Wasm ou WebAssembly, à l'aide du compilateur Emscripten. Renommées plus tard Kiwix JS, les extensions de navigateur sont toujours en cours de développement.

Navigateur hors connexion Kiwix JS

Saisissez la progressive web app (PWA). Conscients du potentiel de cette technologie, les développeurs de Kiwix ont créé une version PWA dédiée de Kiwix JS et se sont mis à ajouter des intégrations d'OS qui permettraient à l'application d'offrir des fonctionnalités semblables aux fonctionnalités natives, en particulier en termes d'utilisation hors connexion, d'installation, de gestion des fichiers et d'accès au système de fichiers.

Les PWA axées sur le mode hors connexion sont extrêmement légères et sont donc parfaites pour les contextes où la connexion Internet mobile est intermittente ou coûteuse. La technologie sous-jacente est l'API Service Worker et l'API Cache associée, utilisées par toutes les applications basées sur Kiwix JS. Ces API permettent aux applications de jouer le rôle de serveur, en interceptant les requêtes de récupération à partir du document ou de l'article principal consulté, et en les redirigeant vers le backend (JS) pour extraire et créer une réponse à partir de l'archive ZIM.

Stockage, stockage partout

Compte tenu de la grande taille des archives ZIM, le stockage et l'accès à celles-ci, en particulier sur les appareils mobiles, constituent probablement le plus grand casse-tête pour les développeurs Kiwix. De nombreux utilisateurs finaux de Kiwix téléchargent du contenu dans l'application, lorsque l'accès à Internet est disponible, pour l'utiliser hors connexion plus tard. D'autres utilisateurs téléchargent des contenus sur un PC à l'aide d'un torrent, puis les transfèrent sur un appareil mobile ou une tablette. Certains échangent des contenus sur des clés USB ou des disques durs portables dans des zones où l'accès à Internet mobile est inégal ou coûteux. Toutes ces méthodes d'accès au contenu à partir d'emplacements arbitraires accessibles par l'utilisateur doivent être compatibles avec Kiwix JS et Kiwix PWA.

C'est l'API File qui a initialement permis à Kiwix JS de lire d'énormes archives de centaines de Go (une de nos archives ZIM fait 166 Go !), même sur des appareils à faible mémoire. Cette API est universellement compatible avec tous les navigateurs, même les très anciens. Elle sert donc de solution de secours universelle lorsque les API plus récentes ne sont pas prises en charge. C'est aussi simple que de définir un élément input en HTML, dans le cas de Kiwix:

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

Une fois sélectionné, l'élément d'entrée contient les objets File, qui sont essentiellement des métadonnées faisant référence aux données sous-jacentes stockées. Techniquement, le backend orienté objet de Kiwix, écrit en JavaScript pur côté client, lit de petites tranches de la grande archive si nécessaire. Si ces tranches doivent être décompressées, le backend les transmet au décompresseur Wasm, obtenant d'autres tranches si nécessaire, jusqu'à ce qu'un blob complet soit décompressé (généralement un article ou un composant). Cela signifie que l'archive volumineuse n'a jamais besoin d'être entièrement lue dans la mémoire.

Bien qu'elle soit universelle, l'API File présente un inconvénient qui fait que les applications Kiwix JS semblent lourdes et démodées par rapport aux applications natives: l'utilisateur doit sélectionner des archives à l'aide d'un sélecteur de fichiers ou faire glisser un fichier dans l'application à chaque fois qu'il lance l'application, car avec cette API, il n'est pas possible de conserver les autorisations d'accès d'une session à l'autre.

Pour atténuer cette mauvaise expérience utilisateur, comme de nombreux développeurs, les développeurs Kiwix JS ont d'abord opté pour Electron. ElectronJS est un framework incroyable qui fournit des fonctionnalités puissantes, y compris un accès complet au système de fichiers à l'aide des API Node. Cependant, cette approche présente des inconvénients bien connus:

  • Il ne s'exécute que sur les systèmes d'exploitation pour ordinateur.
  • Il est volumineux (70 Mo à 100 Mo).

La taille des applications Electron, en raison du fait qu'une copie complète de Chromium est incluse avec chaque application, est très défavorable par rapport à seulement 5,1 Mo pour la PWA réduite et groupée.

Kiwix pouvait-il améliorer la situation pour les utilisateurs de la PWA ?

L'API File System Access à la rescousse

Vers 2019, Kiwix a pris connaissance d'une API émergente qui était en cours d'évaluation dans Chrome 78, sous le nom d'API Native File System. Il promettait de pouvoir obtenir un gestionnaire de fichiers pour un fichier ou un dossier, et de le stocker dans une base de données IndexedDB. Surtout, ce handle persiste entre les sessions de l'application. L'utilisateur n'est donc pas obligé de sélectionner à nouveau le fichier ou le dossier lors du redémarrage de l'application (mais il doit répondre à une invite d'autorisation rapide). Au moment de sa mise en production, elle a été rebaptisée API File System Access, et les parties principales ont été standardisées par le WHATWG sous le nom d'API File System (FSA).

Comment fonctionne la partie File System Access de l'API ? Voici quelques points importants à retenir:

  • Il s'agit d'une API asynchrone (à l'exception des fonctions spécialisées dans les Web Workers).
  • Les sélecteurs de fichiers ou de répertoires doivent être lancés par programmation en capturant un geste utilisateur (cliquer ou appuyer sur un élément d'interface utilisateur).
  • Pour que l'utilisateur autorise à nouveau l'accès à un fichier précédemment sélectionné (dans une nouvelle session), un geste de l'utilisateur est également nécessaire. En fait, le navigateur refusera d'afficher l'invite d'autorisation si elle n'est pas lancée par un geste de l'utilisateur.

Le code est relativement simple, à l'exception de l'utilisation de l'API IndexedDB peu pratique pour stocker les poignées de fichiers et de répertoires. La bonne nouvelle est qu'il existe quelques bibliothèques qui effectuent une grande partie du travail pour vous, comme browser-fs-access. Chez Kiwix JS, nous avons décidé de travailler directement avec les API, qui sont très bien documentées.

Ouvrir des sélecteurs de fichiers et de répertoires

L'ouverture d'un sélecteur de fichiers se présente comme suit (ici, en utilisant des promesses, mais si vous préférez le sucre async/await, consultez le tutoriel Chrome pour les développeurs):

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

Notez que, par souci de simplicité, ce code ne traite que le premier fichier sélectionné (et interdit d'en sélectionner plusieurs). Si vous souhaitez autoriser la sélection de plusieurs fichiers avec { multiple: true }, il vous suffit d'encapsuler toutes les promesses qui traitent chaque poignée dans une instruction Promise.all().then(...), par exemple:

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

Toutefois, il est préférable de demander à l'utilisateur de sélectionner le répertoire contenant ces fichiers plutôt que les fichiers individuels qu'il contient, en particulier parce que les utilisateurs de Kiwix ont tendance à organiser tous leurs fichiers ZIM dans le même répertoire. Le code permettant de lancer le sélecteur de répertoire est presque identique à celui ci-dessus, sauf que vous utilisez window.showDirectoryPicker.then(function (dirHandle) { … });.

Traitement du gestionnaire de fichier ou de répertoire

Une fois que vous avez le handle, vous devez le traiter. La fonction processFileHandle peut donc se présenter comme suit:

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

Notez que vous devez fournir la fonction pour stocker le gestionnaire de fichiers. Il n'existe aucune méthode pratique pour cela, sauf si vous utilisez une bibliothèque d'abstraction. L'implémentation de Kiwix peut être consultée dans le fichier cache.js, mais elle pourrait être considérablement simplifiée si elle n'est utilisée que pour stocker et récupérer un gestionnaire de fichiers ou de dossiers.

Le traitement des répertoires est un peu plus compliqué, car vous devez itérer les entrées du répertoire sélectionné avec entries.next() asynchrone pour trouver les fichiers ou types de fichiers souhaités. Il existe plusieurs façons de procéder, mais voici le code utilisé dans la PWA Kiwix, en résumé:

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

Notez que pour chaque entrée de entryList, vous devrez ensuite récupérer le fichier avec entry.getFile().then(function (file) { … }) lorsque vous devrez l'utiliser, ou l'équivalent à l'aide de const file = await entry.getFile() dans un async function.

Pouvons-nous aller plus loin ?

L'obligation pour l'utilisateur d'accorder une autorisation déclenchée par un geste utilisateur lors des lancements ultérieurs de l'application ajoute une légère friction à l'ouverture (ré)ouverte des fichiers et des dossiers, mais elle est toujours beaucoup plus fluide que d'être obligé de choisir à nouveau un fichier. Les développeurs Chromium finalisent actuellement le code qui permettrait d'utiliser des autorisations persistantes pour les PWA installés. C'est une fonctionnalité que de nombreux développeurs de PWA réclament depuis longtemps et qui est très attendue.

Mais que faire si nous n'avons pas besoin d'attendre ? Les développeurs de Kiwix ont récemment découvert qu'il était possible d'éliminer toutes les invites d'autorisation dès maintenant, en utilisant une nouvelle fonctionnalité de l'API File Access compatible avec les navigateurs Chromium et Firefox (et partiellement compatible avec Safari, mais FileSystemWritableFileStream est toujours manquant). Cette nouvelle fonctionnalité est le système de fichiers privés d'origine.

Passer au natif complet: le système de fichiers privé Origin

Le système de fichiers privé d'origine (OPFS) est toujours une fonctionnalité expérimentale dans la PWA Kiwix, mais l'équipe est ravie d'encourager les utilisateurs à l'essayer, car il comble en grande partie l'écart entre les applications natives et les applications Web. Voici les principaux avantages:

  • Les archives de l'OPFS sont accessibles sans invite d'autorisation, même au démarrage. Les utilisateurs peuvent reprendre la lecture d'un article et la navigation dans une archive à partir de l'endroit où ils s'étaient arrêtés lors d'une session précédente, sans aucune friction.
  • Il offre un accès hautement optimisé aux fichiers qui y sont stockés: sur Android, nous constatons une amélioration de la vitesse de cinq à dix fois.

L'accès aux fichiers standard dans Android à l'aide de l'API File est extrêmement lent, en particulier (comme c'est souvent le cas pour les utilisateurs de Kiwix) si de grandes archives sont stockées sur une carte microSD plutôt que dans l'espace de stockage de l'appareil. Tout cela change avec cette nouvelle API. Bien que la plupart des utilisateurs ne puissent pas stocker un fichier de 97 Go dans l'OPFS (qui consomme l'espace de stockage de l'appareil, et non celui de la carte microSD), il est parfait pour stocker des archives de petite à moyenne taille. Vous voulez consulter l'encyclopédie médicale la plus complète de WikiProject Medicine ? Aucun problème. Avec 1,7 Go, il tient facilement dans l'OPFS. (Conseil : recherchez Autre → mdwiki_en_all_maxi dans la bibliothèque de l'application.)

Fonctionnement de l'OPFS

L'OPFS est un système de fichiers fourni par le navigateur, distinct pour chaque origine, qui peut être considéré comme semblable au stockage de portée application sur Android. Les fichiers peuvent être importés dans l'OPFS à partir du système de fichiers visible par l'utilisateur ou téléchargés directement dans celui-ci (l'API permet également de créer des fichiers dans l'OPFS). Une fois dans l'OPFS, ils sont isolés du reste de l'appareil. Sur les navigateurs basés sur Chromium pour ordinateur, il est également possible d'exporter des fichiers depuis l'OPFS vers le système de fichiers visible par l'utilisateur.

Pour utiliser l'OPFS, la première étape consiste à en demander l'accès à l'aide de navigator.storage.getDirectory() (encore une fois, si vous préférez voir du code utilisant await, consultez 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);
  });

La poignée que vous obtenez est exactement le même type de FileSystemDirectoryHandle que celui que vous obtenez de window.showDirectoryPicker() mentionné ci-dessus, ce qui signifie que vous pouvez réutiliser le code qui gère cela (et heureusement, il n'est pas nécessaire de le stocker dans indexedDB. Il vous suffit de l'obtenir lorsque vous en avez besoin). Supposons que vous disposiez déjà de certains fichiers dans l'OPFS et que vous souhaitiez les utiliser. À l'aide de la fonction iterateAsyncDirEntries() indiquée précédemment, vous pouvez procéder comme suit:

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

N'oubliez pas que vous devez toujours utiliser getFile() sur toute entrée avec laquelle vous souhaitez travailler à partir du tableau archiveList.

Importer des fichiers dans l'OPFS

Comment insérer des fichiers dans l'OPFS ? Pas si vite ! Tout d'abord, vous devez estimer la quantité d'espace de stockage dont vous disposez et vous assurer que les utilisateurs n'essaient pas d'importer un fichier de 97 Go s'il ne rentre pas.

Pour obtenir le quota estimé, rien de plus simple : navigator.storage.estimate().then(function (estimate) { … });. Il est un peu plus difficile de déterminer comment l'afficher à l'utilisateur. Dans l'application Kiwix, nous avons opté pour un petit panneau intégré à l'application visible juste à côté de la case à cocher qui permet aux utilisateurs de tester l'OPFS:

Panneau affichant l&#39;espace de stockage utilisé en pourcentage et l&#39;espace de stockage restant disponible en gigaoctets.

Le panneau est renseigné à l'aide de estimate.quota et de estimate.usage, par exemple:

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

Comme vous pouvez le constater, il existe également un bouton permettant aux utilisateurs d'ajouter des fichiers à l'OPFS à partir du système de fichiers visible par l'utilisateur. La bonne nouvelle est que vous pouvez simplement utiliser l'API File pour obtenir l'objet (ou les objets) File nécessaires à l'importation. En fait, il est important de ne pas utiliser window.showOpenFilePicker(), car cette méthode n'est pas compatible avec Firefox, tandis que l'OPFS est tout à fait compatible.

Le bouton Ajouter un ou plusieurs fichiers visible dans la capture d'écran ci-dessus n'est pas un ancien sélecteur de fichiers, mais il click() un ancien sélecteur masqué (élément <input type="file" multiple … />) lorsqu'il est cliqué ou appuyé. L'application ne fait alors que capturer l'événement change de l'entrée de fichier masquée, vérifier la taille des fichiers et les rejeter s'ils sont trop volumineux pour le quota. Si tout va bien, demandez à l'utilisateur s'il souhaite les ajouter:

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

Boîte de dialogue demandant à l&#39;utilisateur s&#39;il souhaite ajouter une liste de fichiers .zim au système de fichiers privé d&#39;origine.

Comme l'importation d'archives n'est pas l'opération la plus rapide sur certains systèmes d'exploitation, comme Android, Kiwix affiche également une bannière et une petite icône de chargement pendant l'importation des archives. L'équipe n'a pas réussi à ajouter un indicateur de progression à cette opération. Si vous trouvez la solution, envoyez-nous la réponse sur une carte postale.

Comment Kiwix a-t-il implémenté la fonction importOPFSEntries() ? Pour ce faire, vous devez utiliser la méthode fileHandle.createWriteable(), qui permet de diffuser chaque fichier dans l'OPFS. Tout le travail est géré par le navigateur. (Kiwix utilise des promesses ici pour des raisons liées à notre ancien codebase, mais il faut dire que dans ce cas, await produit une syntaxe plus simple et évite l'effet de pyramide de l'enfer.)

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

Télécharger un flux de fichiers directement dans l'OPFS

Une variante de cette méthode consiste à diffuser un fichier depuis Internet directement dans l'OPFS ou dans n'importe quel répertoire pour lequel vous disposez d'un gestionnaire de répertoires (c'est-à-dire les répertoires sélectionnés avec window.showDirectoryPicker()). Il utilise les mêmes principes que le code ci-dessus, mais crée un Response composé d'un ReadableStream et d'un contrôleur qui met en file d'attente les octets lus à partir du fichier distant. Le Response.body obtenu est ensuite transmis à l'écrivain du nouveau fichier dans l'OPFS.

Dans ce cas, Kiwix peut compter les octets passant par ReadableStream, et ainsi fournir un indicateur de progression à l'utilisateur, et l'avertir de ne pas quitter l'application pendant le téléchargement. Le code est un peu trop complexe pour être présenté ici, mais comme notre application est une application Open Source, vous pouvez consulter le code source si vous souhaitez faire quelque chose de similaire. Voici à quoi ressemble l'interface utilisateur de Kiwix (les différentes valeurs de progression affichées ci-dessous sont dues au fait qu'elle ne met à jour la bannière que lorsque le pourcentage change, mais qu'elle met à jour le panneau Progression du téléchargement plus régulièrement):

Interface utilisateur de Kiwix avec une barre en bas avertissant l&#39;utilisateur de ne pas quitter l&#39;application et affichant la progression du téléchargement de l&#39;archive .zim.

Étant donné que le téléchargement peut être une opération assez longue, Kiwix permet aux utilisateurs d'utiliser librement l'application pendant l'opération, mais s'assure que la bannière est toujours affichée pour rappeler aux utilisateurs de ne pas fermer l'application tant que l'opération de téléchargement n'est pas terminée.

Implémenter un mini gestionnaire de fichiers dans l'application

À ce stade, les développeurs de la PWA Kiwix ont réalisé qu'il ne suffisait pas d'ajouter des fichiers à l'OPFS. L'application devait également permettre aux utilisateurs de supprimer les fichiers dont ils n'avaient plus besoin de cette zone de stockage et, dans l'idéal, d'exporter les fichiers verrouillés dans l'OPFS vers le système de fichiers visible par l'utilisateur. En fait, il a fallu implémenter un mini système de gestion de fichiers dans l'application.

Je voudrais faire un clin d'œil à l'extension OPFS Explorer pour Chrome (elle fonctionne également dans Edge). Il ajoute un onglet dans les outils pour les développeurs qui vous permet de voir exactement ce qui se trouve dans l'OPFS, et de supprimer également les fichiers indésirables ou ayant échoué. Il était indispensable pour vérifier si le code fonctionnait, surveiller le comportement des téléchargements et nettoyer nos expériences de développement en général.

L'exportation de fichiers dépend de la possibilité d'obtenir un gestionnaire de fichiers sur un fichier ou un répertoire sélectionné dans lequel Kiwix va enregistrer le fichier exporté. Par conséquent, cela ne fonctionne que dans les contextes où il peut utiliser la méthode window.showSaveFilePicker(). Si les fichiers Kiwix étaient inférieurs à plusieurs Go, nous pourrions créer un blob en mémoire, lui attribuer une URL, puis le télécharger dans le système de fichiers visible par l'utilisateur. Malheureusement, ce n'est pas possible avec des archives aussi volumineuses. Si l'exportation est prise en charge, elle est relativement simple: elle est pratiquement identique, à l'inverse, à l'enregistrement d'un fichier dans l'OPFS (obtenez un gestionnaire du fichier à enregistrer, demandez à l'utilisateur de choisir un emplacement où l'enregistrer avec window.showSaveFilePicker(), puis utilisez createWriteable() sur saveHandle). Vous pouvez consulter le code dans le dépôt.

La suppression de fichiers est prise en charge par tous les navigateurs et peut être effectuée avec un simple dirHandle.removeEntry('filename'). Dans le cas de Kiwix, nous avons préféré itérer les entrées OPFS comme nous l'avons fait ci-dessus, afin de vérifier d'abord que le fichier sélectionné existe et de demander une confirmation, mais cela n'est peut-être pas nécessaire pour tout le monde. Encore une fois, vous pouvez examiner notre code si vous le souhaitez.

Il a été décidé de ne pas encombrer l'interface utilisateur de Kiwix avec des boutons proposant ces options, mais de placer de petites icônes directement sous la liste des archives. Si l'utilisateur appuie sur l'une de ces icônes, la couleur de la liste des archives change, ce qui lui indique visuellement ce qu'il va faire. L'utilisateur clique ou appuie ensuite sur l'une des archives, et l'opération correspondante (exportation ou suppression) est effectuée (après confirmation).

Boîte de dialogue demandant à l&#39;utilisateur s&#39;il souhaite supprimer un fichier .zim.

Enfin, voici une démonstration en écran partagé de toutes les fonctionnalités de gestion de fichiers abordées ci-dessus : ajout d'un fichier à l'OPFS, téléchargement direct d'un fichier dans l'OPFS, suppression d'un fichier et exportation vers le système de fichiers visible par l'utilisateur.

Le travail d'un développeur ne s'arrête jamais

L'OPFS est une excellente innovation pour les développeurs de PWA, car il offre des fonctionnalités de gestion de fichiers très puissantes qui permettent de combler l'écart entre les applications natives et les applications Web. Mais les développeurs sont un groupe misérable : ils ne sont jamais tout à fait satisfaits ! L'OPFS est presque parfait, mais pas tout à fait… C'est formidable que les principales fonctionnalités fonctionnent à la fois dans les navigateurs Chromium et Firefox, et qu'elles soient implémentées sur Android et sur ordinateur. Nous espérons que l'ensemble des fonctionnalités sera bientôt implémenté dans Safari et iOS. Les problèmes suivants sont toujours en cours:

  • Firefox limite actuellement le quota OPFS à 10 Go, quel que soit l'espace disque sous-jacent. Bien que cela puisse être suffisant pour la plupart des auteurs de PWA, pour Kiwix, c'est assez restrictif. Heureusement, les navigateurs Chromium sont beaucoup plus généreux.
  • Il n'est actuellement pas possible d'exporter de gros fichiers de l'OPFS vers le système de fichiers visible par l'utilisateur dans les navigateurs mobiles ou Firefox pour ordinateur, car window.showSaveFilePicker() n'est pas implémenté. Dans ces navigateurs, les fichiers de grande taille sont effectivement piégés dans l'OPFS. Cela va à l'encontre de l'esprit de Kiwix, qui est de permettre un accès ouvert aux contenus et de partager des archives entre utilisateurs, en particulier dans les zones où la connectivité Internet est intermittente ou coûteuse.
  • Les utilisateurs ne peuvent pas contrôler le stockage que le système de fichiers virtuel OPFS consommera. Cela est particulièrement problématique sur les appareils mobiles, où les utilisateurs peuvent disposer de grandes quantités d'espace sur une carte microSD, mais d'une quantité très faible sur l'espace de stockage de l'appareil.

Mais dans l'ensemble, il s'agit de problèmes mineurs dans ce qui constitue un grand pas en avant pour l'accès aux fichiers dans les PWA. L'équipe Kiwix PWA est très reconnaissante aux développeurs et défenseurs de Chromium qui ont proposé et conçu pour la première fois l'API File System Access, ainsi que pour le travail acharné qu'ils ont fourni pour parvenir à un consensus entre les fournisseurs de navigateurs sur l'importance du système de fichiers privé d'origine. Pour la PWA Kiwix JS, elle a résolu un grand nombre des problèmes d'expérience utilisateur qui ont entravé l'application par le passé et nous aide dans notre quête d'amélioration de l'accessibilité du contenu Kiwix pour tous. Veuillez essayer la PWA Kiwix et en faire part aux développeurs.

Pour obtenir d'excellentes ressources sur les fonctionnalités des applications Web progressives, consultez les sites suivants: