Как Kiwix PWA позволяет пользователям хранить гигабайты данных из Интернета для автономного использования

Люди собираются вокруг ноутбука, стоящего на простом столе с пластиковым стулом слева. Фон выглядит как школа в развивающейся стране.

В этом тематическом исследовании рассматривается, как некоммерческая организация Kiwix использует технологию Progressive Web App и API доступа к файловой системе, чтобы дать пользователям возможность загружать и хранить большие интернет-архивы для автономного использования. Узнайте о технической реализации кода, связанного с частной файловой системой Origin (OPFS), новой функцией браузера в Kiwix PWA, которая расширяет возможности управления файлами, обеспечивая улучшенный доступ к архивам без запроса разрешения. В статье обсуждаются проблемы и освещаются потенциальные будущие разработки в этой новой файловой системе.

О Кивиксе

По данным Международного союза электросвязи, спустя более 30 лет после появления Интернета треть населения мира все еще ждет надежного доступа к Интернету . На этом история заканчивается? Конечно, нет. Ребята из Kiwix , швейцарской некоммерческой организации, разработали экосистему приложений и контента с открытым исходным кодом, целью которой является сделать знания доступными для людей с ограниченным доступом в Интернет или без него. Их идея заключается в том, что если вы не можете легко получить доступ к Интернету, кто-то может загрузить для вас ключевые ресурсы там, где и когда доступно подключение, и сохранить их локально для последующего использования в автономном режиме. Многие жизненно важные сайты, например Wikipedia, Project Gutenberg, Stack Exchange или даже выступления TED, теперь можно конвертировать в сильно сжатые архивы, называемые ZIM-файлами, и читать их на лету браузером Kiwix.

В архивах ZIM используется высокоэффективное сжатие Zstandard (ZSTD) (более старые версии использовали XZ ), в основном для хранения HTML, JavaScript и CSS, а изображения обычно конвертируются в сжатый формат WebP . Каждый ZIM также включает URL-адрес и индекс заголовка. Сжатие здесь является ключевым моментом, поскольку вся Википедия на английском языке (6,4 миллиона статей плюс изображения) сжимается до 97 ГБ после конвертации в формат ZIM, что звучит очень много, пока вы не поймете, что теперь может поместиться сумма всех человеческих знаний. на телефоне Android среднего класса. Также предлагается множество небольших ресурсов, включая тематические версии Википедии, такие как математика, медицина и т. д.

Kiwix предлагает ряд нативных приложений, предназначенных для настольных компьютеров (Windows/Linux/macOS), а также мобильных устройств (iOS/Android). Однако в этом тематическом исследовании основное внимание будет уделено прогрессивному веб-приложению (PWA) , которое призвано стать универсальным и простым решением для любого устройства с современным браузером.

Мы рассмотрим проблемы, возникающие при разработке универсального веб-приложения, которое должно обеспечивать быстрый доступ к большим архивам контента в автономном режиме, а также некоторые современные API-интерфейсы JavaScript, в частности API доступа к файловой системе и частную файловую систему Origin , которые обеспечивают инновационные и захватывающие возможности. решения этих проблем.

Веб-приложение для автономного использования?

Пользователи Kiwix — это разнородная группа людей со множеством различных потребностей, и Kiwix практически не имеет контроля над устройствами и операционными системами, на которых они будут получать доступ к своему контенту. Некоторые из этих устройств могут быть медленными или устаревшими, особенно в регионах мира с низким уровнем дохода. Хотя Kiwix пытается охватить как можно больше вариантов использования, организация также осознала, что может охватить еще больше пользователей, используя самое универсальное программное обеспечение на любом устройстве: веб-браузер. Итак, вдохновленные Законом Этвуда , который гласит, что любое приложение, которое может быть написано на JavaScript, в конечном итоге будет написано на JavaScript , некоторые разработчики Kiwix около 10 лет назад приступили к портированию программного обеспечения Kiwix с C++ на JavaScript.

Первая версия этого порта, получившая название Kiwix HTML5, предназначалась для ныне несуществующей ОС Firefox и расширений браузера. В его основе лежал (и есть) механизм декомпрессии C++ (XZ и ZSTD), скомпилированный в промежуточный язык JavaScript ASM.js, а позже Wasm, или WebAssembly , с использованием компилятора Emscripten . Позже переименованный в Kiwix JS , расширения для браузера до сих пор активно разрабатываются.

Офлайн-браузер Kiwix JS

Введите Progressive Web App (PWA). Осознавая потенциал этой технологии, разработчики Kiwix создали специальную PWA-версию Kiwix JS и приступили к добавлению интеграции с ОС , которая позволит приложению предлагать возможности, аналогичные нативным, особенно в областях автономного использования, установки, обработки файлов и доступ к файловой системе.

PWA, ориентированные на офлайн-режим, чрезвычайно легки и поэтому идеально подходят для условий с нестабильным или дорогим мобильным Интернетом. В основе лежит технология Service Worker API и связанный с ним Cache API , используемые всеми приложениями на базе Kiwix JS. Эти API позволяют приложениям действовать как сервер, перехватывая запросы на выборку из основного просматриваемого документа или статьи и перенаправляя их на серверную часть (JS) для извлечения и создания ответа из архива ZIM.

Хранение, хранение повсюду

Учитывая большой размер ZIM-архивов, хранение и доступ к нему, особенно на мобильных устройствах, является, пожалуй, самой большой головной болью для разработчиков Kiwix. Многие конечные пользователи Kiwix загружают контент в приложение, когда доступен Интернет, для последующего использования в автономном режиме. Другие пользователи скачивают на ПК через торрент, а затем переносят на мобильное или планшетное устройство, а некоторые обмениваются контентом на флешках или портативных жестких дисках в районах с нестабильным или дорогим мобильным интернетом. Все эти способы доступа к контенту из произвольных доступных пользователю мест должны поддерживаться Kiwix JS и Kiwix PWA.

То, что изначально позволило Kiwix JS читать огромные архивы размером в сотни ГБ ( один из наших ZIM-архивов имеет размер 166 ГБ!) даже на устройствах с небольшим объемом памяти, — это File API . Этот API универсально поддерживается в любом браузере, даже в очень старых браузерах , поэтому он действует как универсальный запасной вариант на случай, если новые API не поддерживаются. В случае Kiwix это так же просто, как определить элемент input в HTML:

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

После выбора элемент ввода содержит объекты File, которые по сути являются метаданными, ссылающимися на базовые данные в хранилище. Технически, объектно-ориентированная серверная часть Kiwix, написанная на чистом клиентском JavaScript, по мере необходимости считывает небольшие фрагменты большого архива. Если эти фрагменты необходимо распаковать, серверная часть передает их распаковщику Wasm, получая дополнительные фрагменты по запросу, пока не будет распакован полный большой двоичный объект (обычно статья или ресурс). Это означает, что большой архив никогда не нужно полностью считывать в память.

Несмотря на свою универсальность, File API имеет недостаток, из-за которого приложения Kiwix JS кажутся неуклюжими и старомодными по сравнению с собственными приложениями: он требует от пользователя выбирать архивы с помощью средства выбора файлов или перетаскивать файл в приложение. , каждый раз при запуске приложения , поскольку с помощью этого API невозможно сохранить права доступа от одного сеанса к другому.

Чтобы смягчить этот плохой UX, разработчики Kiwix JS, как и многие другие разработчики, изначально пошли по пути Electron. ElectronJS — это удивительная среда, предоставляющая мощные функции, включая полный доступ к файловой системе с помощью API-интерфейсов Node. Однако у него есть некоторые хорошо известные недостатки:

  • Он работает только в настольных операционных системах.
  • Он большой и тяжелый (70–100 МБ).

Размер приложений Electron из-за того, что в каждое приложение включена полная копия Chromium, очень невыгоден по сравнению с всего лишь 5,1 МБ для свернутого и связанного PWA!

Итак, есть ли способ, которым Kiwix мог бы улучшить ситуацию для пользователей PWA?

API доступа к файловой системе придет на помощь

Примерно в 2019 году Kiwix стало известно о новом API, который проходил испытания в Chrome 78 и назывался тогда Native File System API . Обещалась возможность получить дескриптор файла или папки и сохранить его в базе данных IndexedDB. Важно отметить, что этот дескриптор сохраняется между сеансами приложения, поэтому пользователю не приходится снова выбирать файл или папку при повторном запуске приложения (хотя ему приходится отвечать на быстрый запрос разрешения). К моменту выхода в производство он был переименован в File System Access API , а основные части стандартизированы WHATWG как File System API (FSA).

Итак, как же работает часть API , посвященная доступу к файловой системе ? Несколько важных моментов, на которые следует обратить внимание:

  • Это асинхронный API (за исключением специализированных функций в Web Workers).
  • Средства выбора файлов или каталогов должны запускаться программно путем захвата жеста пользователя (щелкнуть или коснуться элемента пользовательского интерфейса).
  • Чтобы пользователь снова дал разрешение на доступ к ранее выбранному файлу (в новом сеансе), также необходим жест пользователя — фактически браузер откажется отображать запрос на разрешение, если он не будет инициирован жестом пользователя.

Код относительно прост, за исключением необходимости использовать неуклюжий API IndexedDB для хранения дескрипторов файлов и каталогов. Хорошей новостью является то, что есть пара библиотек, которые делают за вас большую часть тяжелой работы, например , Browser-fs-access . В Kiwix JS мы решили работать напрямую с API, которые очень хорошо документированы.

Открытие средств выбора файлов и каталогов

Открытие средства выбора файлов выглядит примерно так (здесь используются Promises, но если вы предпочитаете сахар async/await , см. руководство Chrome для разработчиков ):

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

Обратите внимание, что для простоты этот код обрабатывает только первый выбранный файл (и запрещает выбор более одного). Если вы хотите разрешить выбор нескольких файлов с помощью { multiple: true } , вы просто обертываете все промисы, которые обрабатывают каждый дескриптор, в оператор Promise.all().then(...) , например:

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

Однако, возможно, лучше выбрать несколько файлов, попросив пользователя выбрать каталог, содержащий эти файлы, а не отдельные файлы в нем, тем более что пользователи Kiwix склонны организовывать все свои файлы ZIM в одном каталоге. Код для запуска средства выбора каталогов почти такой же, как указано выше, за исключением того, что вы используете window.showDirectoryPicker.then(function (dirHandle) { … }); .

Обработка дескриптора файла или каталога

Если у вас есть дескриптор, вам нужно его обработать, поэтому processFileHandle может выглядеть так:

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

Обратите внимание, что вам необходимо предоставить функцию для хранения дескриптора файла , для этого нет удобных методов, если только вы не используете библиотеку абстракции. Реализацию этого в Kiwix можно увидеть в файле cache.js , но ее можно значительно упростить, если использовать только для хранения и извлечения дескриптора файла или папки.

Обработка каталогов немного сложнее, поскольку вам придется перебирать записи в выбранном каталоге с помощью async entries.next() , чтобы найти нужные файлы или типы файлов. Есть разные способы сделать это, но вот код, используемый в 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);
    });
}

Обратите внимание, что для каждой записи в entryList вам позже потребуется получить файл с помощью entry.getFile().then(function (file) { … }) , когда вам понадобится его использовать, или эквивалентно, используя const file = await entry.getFile() в async function .

Можем ли мы пойти дальше?

Требование к пользователю предоставить разрешение , инициируемое жестом пользователя при последующих запусках приложения, добавляет небольшие трудности при (повторном) открытии файлов и папок, но это все равно гораздо более гибко, чем принуждение к повторному выбору файла. . Разработчики Chromium в настоящее время завершают работу над кодом , который обеспечит постоянные разрешения для установленных PWA. Это то, к чему призывают многие разработчики PWA, и этого с нетерпением ждут.

А что, если нам не придется ждать?! Разработчики Kiwix недавно обнаружили, что можно устранить все запросы разрешений прямо сейчас, используя новую блестящую функцию File Access API, которая поддерживается браузерами Chromium и Firefox (и частично поддерживается Safari, но все еще отсутствует FileSystemWritableFileStream ). Эта новая функция — частная файловая система Origin .

Полностью нативная файловая система Origin: частная файловая система Origin

Частная файловая система Origin (OPFS) по-прежнему является экспериментальной функцией в Kiwix PWA, но команда очень рада предложить пользователям опробовать ее, поскольку она в значительной степени устраняет разрыв между нативными и веб-приложениями. Вот ключевые преимущества:

  • Доступ к архивам в OPFS возможен без запроса разрешения даже при запуске. Пользователи могут возобновить чтение статьи и просмотр архива с того места, на котором они остановились в предыдущем сеансе, без каких-либо затруднений.
  • Он обеспечивает высокооптимизированный доступ к хранящимся в нем файлам : на Android мы видим увеличение скорости в пять-десять раз.

Стандартный доступ к файлам в Android с помощью File API крайне медленный, особенно (как это часто бывает у пользователей Kiwix), если большие архивы хранятся на карте microSD, а не в памяти устройства. Все меняется с появлением нового API. Хотя большинство пользователей не смогут хранить файл размером 97 ГБ в OPFS (который занимает память устройства, а не карту microSD), он идеально подходит для хранения архивов небольшого и среднего размера. Вам нужна самая полная медицинская энциклопедия WikiProject Medicine? Нет проблем, при 1,7 ГБ он легко помещается в OPFS! (Совет: ищите другиеmdwiki_en_all_maxi во встроенной библиотеке приложения .)

Как работает ОПФС

OPFS — это файловая система, предоставляемая браузером, отдельная для каждого источника, которую можно рассматривать как аналогичную хранилищу на уровне приложений в Android. Файлы можно импортировать в OPFS из видимой пользователю файловой системы или загружать непосредственно в нее (API также позволяет создавать файлы в OPFS). Попав в OPFS, они изолируются от остального устройства. В настольных браузерах на базе Chromium также можно экспортировать файлы обратно из OPFS в видимую пользователю файловую систему.

Чтобы использовать OPFS, первым шагом является запрос доступа к ней с помощью navigator.storage.getDirectory() (опять же, если вы предпочитаете видеть код, использующий await , прочтите 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);
  });

Дескриптор, который вы получаете от этого, — это тот же тип FileSystemDirectoryHandle , который вы получаете из window.showDirectoryPicker() , упомянутого выше, что означает, что вы можете повторно использовать код, который обрабатывает это (и, к счастью, нет необходимости хранить это в indexedDB — просто получите это когда вам это нужно). Предположим, у вас уже есть несколько файлов в OPFS и вы хотите их использовать, затем, используя показанную ранее функцию iterateAsyncDirEntries() , вы можете сделать что-то вроде:

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

Не забывайте, что вам все равно нужно использовать getFile() для любой записи из массива archiveList , с которой вы хотите работать.

Импорт файлов в OPFS

Итак, как же в первую очередь загрузить файлы в OPFS? Не так быстро! Во-первых, вам необходимо оценить объем памяти, с которой вам придется работать, и убедиться, что пользователи не пытаются вставить файл размером 97 ГБ, если он не помещается.

Получить расчетную квоту легко: navigator.storage.estimate().then(function (estimate) { … }); . Немного сложнее придумать, как отобразить это пользователю. В приложении Kiwix мы выбрали небольшую панель рядом с флажком, которая позволяет пользователям опробовать OPFS:

Панель, показывающая использованное хранилище в процентах и ​​оставшееся доступное хранилище в гигабайтах.

Панель заполняется с помощью estimate.quota и estimate.usage , например:

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

Как видите, есть также кнопка, которая позволяет пользователям добавлять файлы в OPFS из видимой пользователю файловой системы. Хорошей новостью является то, что вы можете просто использовать File API для получения необходимого объекта File (или объектов), который будет импортирован. На самом деле важно не использовать window.showOpenFilePicker() , потому что этот метод не поддерживается Firefox, тогда как OPFS наверняка поддерживается .

Видимая кнопка «Добавить файл(ы)», которую вы видите на снимке экрана выше, не является средством выбора устаревших файлов, но она click() скрытое устаревшее средство выбора (элемент <input type="file" multiple … /> ) при нажатии на нее или постучали. Затем приложение просто фиксирует событие change ввода скрытого файла, проверяет размер файлов и отклоняет их, если они слишком велики для квоты. Если все в порядке, спросите пользователя, хотят ли они их добавить:

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

Диалоговое окно с вопросом, хотят ли они добавить список файлов .zim в исходную частную файловую систему.

Поскольку в некоторых операционных системах, например Android, импорт архивов не является самой быстрой операцией, Kiwix также показывает баннер и небольшой счетчик во время импорта архивов. Команда не придумала, как добавить индикатор прогресса этой операции: если получится, ответы на открытке, пожалуйста!

Итак, как Kiwix реализовал функцию importOPFSEntries() ? Это предполагает использование метода fileHandle.createWriteable() , который эффективно позволяет передавать каждый файл в потоковую передачу в OPFS. Всю тяжелую работу выполняет браузер. (Kiwix использует здесь Promises по причинам, связанным с нашей устаревшей кодовой базой, но следует сказать, что в этом случае await создает более простой синтаксис и позволяет избежать эффекта роковой пирамиды.)

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

Загрузка файлового потока непосредственно в OPFS

Вариантом этого является возможность потоковой передачи файла из Интернета непосредственно в OPFS или в любой каталог, для которого у вас есть дескриптор каталога (то есть каталоги, выбранные с помощью window.showDirectoryPicker() ). Он использует те же принципы, что и приведенный выше код, но создает Response , состоящий из ReadableStream и контроллера, который ставит в очередь байты, считанные из удаленного файла. Полученный в результате Response.body затем передается средству записи нового файла внутри OPFS.

В этом случае Kiwix может подсчитывать байты, проходящие через ReadableStream , и таким образом предоставлять пользователю индикатор прогресса, а также предупреждать его о том, что ему не следует выходить из приложения во время загрузки. Код слишком запутан, чтобы его можно было показать здесь, но, поскольку наше приложение является приложением FOSS, вы можете посмотреть исходный код , если хотите сделать что-то подобное. Вот как выглядит пользовательский интерфейс Kiwix (различные значения прогресса, показанные ниже, объясняются тем, что баннер обновляется только при изменении процентного значения, но панель прогресса загрузки обновляется более регулярно):

Пользовательский интерфейс Kiwix с полосой внизу, предупреждающей пользователя о том, что ему не следует выходить из приложения, и показывающей ход загрузки архива .zim.

Поскольку загрузка может занять довольно продолжительное время, Kiwix позволяет пользователям свободно использовать приложение во время операции, но гарантирует, что баннер всегда отображается, чтобы пользователи получали напоминание о том, что нельзя закрывать приложение до завершения операции загрузки.

Реализация мини-файлового менеджера в приложении

На этом этапе разработчики Kiwix PWA поняли, что недостаточно иметь возможность добавлять файлы в OPFS. Приложению также необходимо было предоставить пользователям возможность удалять файлы, которые им больше не нужны, из этой области хранения, а в идеале — также экспортировать любые файлы, заблокированные в OPFS, обратно в видимую пользователю файловую систему. По сути, возникла необходимость внедрить в приложение мини- систему управления файлами .

Короткая благодарность замечательному расширению OPFS Explorer для Chrome (оно также работает в Edge). Он добавляет вкладку в инструменты разработчика, которая позволяет вам точно видеть, что находится в OPFS, а также удалять посторонние или неисправные файлы. Это было неоценимо для проверки работоспособности кода, мониторинга поведения загрузок и в целом для очистки наших экспериментов по разработке.

Экспорт файла зависит от возможности получить дескриптор выбранного файла или каталога, в котором Kiwix сохранит экспортированный файл, поэтому это работает только в тех контекстах, где можно использовать метод window.showSaveFilePicker() . Если бы файлы Kiwix были меньше нескольких ГБ, мы могли бы создать объект в памяти, присвоить ему URL-адрес и затем загрузить его в видимую пользователю файловую систему. К сожалению, это невозможно с такими большими архивами. Если поддерживается, экспорт довольно прост: практически то же самое, наоборот, что и сохранение файла в OPFS (получите дескриптор сохраняемого файла, попросите пользователя выбрать место для его сохранения с помощью window.showSaveFilePicker() , затем используйте createWriteable() для saveHandle ). Вы можете увидеть код в репозитории.

Удаление файлов поддерживается всеми браузерами и может быть достигнуто с помощью простого dirHandle.removeEntry('filename') . В случае Kiwix мы предпочли перебирать записи OPFS, как мы делали выше, чтобы мы могли сначала проверить, существует ли выбранный файл, и запросить подтверждение, но это может быть необходимо не всем. Опять же, если вам интересно, вы можете изучить наш код .

Было решено не загромождать Kiwix UI кнопками, предлагающими эти опции, а вместо этого разместить небольшие значки прямо под списком архивов. Нажатие на один из этих значков изменит цвет списка архивов, что станет визуальным намеком пользователю на то, что он собирается делать. Затем пользователь щелкает или тапнет по одному из архивов, и соответствующая операция (экспорт или удаление) выполняется (после подтверждения).

Диалоговое окно с вопросом, хотят ли они удалить файл .zim.

Наконец, вот демонстрация всех функций управления файлами, описанных выше: добавление файла в OPFS, непосредственная загрузка файла в нее, удаление файла и экспорт в видимую пользователю файловую систему.

Работа разработчика никогда не заканчивается

OPFS — это отличная инновация для разработчиков PWA, предоставляющая действительно мощные функции управления файлами, которые во многом сокращают разрыв между собственными приложениями и веб-приложениями. Но разработчики — жалкая компания: они никогда не бывают полностью удовлетворены! OPFS почти идеальна, но не совсем… Замечательно, что основные функции работают как в браузерах Chromium, так и в Firefox, и что они реализованы как на Android, так и на настольных компьютерах. Мы надеемся, что полный набор функций вскоре будет реализован в Safari и iOS. Остаются следующие проблемы:

  • В настоящее время Firefox устанавливает ограничение на квоту OPFS в 10 ГБ, независимо от того, сколько места на базовом диске имеется. Хотя для большинства авторов PWA этого может быть достаточно, для Kiwix это весьма ограничительно. К счастью, браузеры Chromium гораздо более щедры.
  • В настоящее время невозможно экспортировать большие файлы из OPFS в видимую пользователю файловую систему в мобильных браузерах или настольном Firefox, поскольку window.showSaveFilePicker() не реализован. В этих браузерах большие файлы эффективно задерживаются в OPFS. Это противоречит принципам Kiwix, заключающимся в открытом доступе к контенту и возможности совместного использования архивов между пользователями, особенно в районах с нестабильным или дорогостоящим подключением к Интернету.
  • Пользователь не может контролировать, какое хранилище будет использовать виртуальная файловая система OPFS. Это особенно проблематично на мобильных устройствах, где у пользователей может быть большой объем места на карте microSD, но очень маленький объем памяти устройства.

Но в целом это незначительные мелочи, которые в противном случае являются огромным шагом вперед для доступа к файлам в PWA. Команда Kiwix PWA очень благодарна разработчикам и сторонникам Chromium, которые первыми предложили и разработали API доступа к файловой системе, а также за тяжелую работу по достижению консенсуса среди поставщиков браузеров относительно важности частной файловой системы Origin. Для Kiwix JS PWA он решил множество проблем с пользовательским интерфейсом, которые мешали приложению в прошлом, и помогает нам в нашем стремлении повысить доступность контента Kiwix для всех. Пожалуйста , оцените Kiwix PWA и расскажите разработчикам , что вы думаете!

Чтобы найти отличные ресурсы о возможностях PWA, посетите эти сайты: