Cara PWA Kiwix memungkinkan pengguna menyimpan data dalam Gigabyte dari Internet untuk penggunaan offline

Orang-orang berkumpul di sekitar laptop yang berdiri di atas meja sederhana dengan kursi plastik di sebelah kiri. Latar belakangnya terlihat seperti sekolah di negara berkembang.

Studi kasus ini mengeksplorasi cara Kiwix, sebuah organisasi nonprofit, menggunakan teknologi Progressive Web App dan File System Access API untuk memungkinkan pengguna mendownload dan menyimpan arsip Internet berukuran besar untuk penggunaan offline. Pelajari penerapan teknis kode yang menangani Origin Private File System (OPFS), fitur browser baru dalam PWA Kiwix yang meningkatkan pengelolaan file, sehingga memberikan akses yang lebih baik ke arsip tanpa permintaan izin. Artikel ini membahas tantangan dan menyoroti potensi pengembangan di masa mendatang dalam sistem file baru ini.

Tentang Kiwix

Lebih dari 30 tahun setelah kelahiran web, sepertiga populasi dunia masih menunggu akses internet yang andal menurut International Telecommunication Union. Apakah di sinilah kisahnya berakhir? Tentu saja tidak. Tim di Kiwix, organisasi nirlaba yang berbasis di Swiss, telah mengembangkan ekosistem aplikasi dan konten open source yang bertujuan untuk menyediakan pengetahuan bagi orang-orang dengan akses Internet terbatas atau tidak ada. Idenya adalah jika Anda tidak dapat mengakses Internet dengan mudah, seseorang dapat mendownload resource utama untuk Anda, di mana dan kapan konektivitas tersedia, dan menyimpan resource tersebut secara lokal untuk digunakan secara offline nanti. Banyak situs penting, misalnya Wikipedia, Project Gutenberg, Stack Exchange, atau bahkan TED Talks, kini dapat dikonversi menjadi arsip yang sangat terkompresi, yang disebut file ZIM, dan dibaca dengan cepat oleh browser Kiwix.

Arsip ZIM menggunakan kompresi Zstandard (ZSTD) yang sangat efisien (versi lama menggunakan XZ), sebagian besar untuk menyimpan HTML, JavaScript, dan CSS, sedangkan gambar biasanya dikonversi ke format WebP yang dikompresi. Setiap ZIM juga menyertakan URL dan indeks judul. Kompresi adalah kuncinya di sini, karena seluruh Wikipedia dalam bahasa Inggris (6,4 juta artikel, ditambah gambar) dikompresi menjadi 97 GB setelah dikonversi ke format ZIM, yang terdengar seperti banyak sampai Anda menyadari bahwa jumlah semua pengetahuan manusia kini dapat muat di ponsel Android menengah. Banyak referensi yang lebih kecil juga ditawarkan, termasuk versi tematik Wikipedia, seperti matematika, kedokteran, dan sebagainya.

Kiwix menawarkan berbagai aplikasi native yang menargetkan penggunaan desktop (Windows/Linux/macOS) serta seluler (iOS/Android). Namun, studi kasus ini akan berfokus pada Progressive Web App (PWA) yang bertujuan menjadi solusi universal dan sederhana untuk perangkat apa pun yang memiliki browser modern.

Kita akan melihat tantangan yang dihadapi dalam mengembangkan aplikasi Web universal yang perlu memberikan akses cepat ke arsip konten besar secara offline sepenuhnya, dan beberapa JavaScript API modern, terutama File System Access API dan Origin Private File System, yang memberikan solusi inovatif dan menarik untuk tantangan tersebut.

Aplikasi Web untuk penggunaan offline?

Pengguna Kiwix adalah kelompok yang beragam dengan berbagai kebutuhan, dan Kiwix memiliki sedikit atau tidak ada kontrol atas perangkat dan sistem operasi tempat mereka akan mengakses konten mereka. Beberapa perangkat ini mungkin lambat atau sudah usang, terutama di area berpenghasilan rendah di dunia. Meskipun Kiwix mencoba mencakup sebanyak mungkin kasus penggunaan, organisasi ini juga menyadari bahwa mereka dapat menjangkau lebih banyak pengguna dengan menggunakan software paling universal di perangkat apa pun: browser web. Jadi, terinspirasi oleh Hukum Atwood, yang menyatakan bahwa Aplikasi apa pun yang dapat ditulis dalam JavaScript, pada akhirnya akan ditulis dalam JavaScript, beberapa developer Kiwix, sekitar 10 tahun yang lalu, mulai mem-porting software Kiwix dari C++ ke JavaScript.

Versi pertama port ini, yang disebut Kiwix HTML5, ditujukan untuk Firefox OS yang kini tidak digunakan lagi dan untuk ekstensi browser. Pada intinya adalah (dan masih) mesin dekompresi C++ (XZ dan ZSTD) yang dikompilasi ke bahasa JavaScript perantara ASM.js, dan kemudian Wasm, atau WebAssembly, menggunakan compiler Emscripten. Kemudian diganti namanya menjadi Kiwix JS, ekstensi browser masih dikembangkan secara aktif.

Browser Offline Kiwix JS

Masukkan Progressive Web App (PWA). Menyadari potensi teknologi ini, developer Kiwix membuat versi PWA khusus Kiwix JS, dan mulai menambahkan integrasi OS yang akan memungkinkan aplikasi menawarkan kemampuan seperti native, terutama di area penggunaan offline, penginstalan, penanganan file, dan akses sistem file.

PWA offline-first sangat ringan, sehingga sangat cocok untuk konteks dengan Internet seluler yang tidak stabil atau mahal. Teknologi di balik hal ini adalah Service Worker API dan Cache API terkait, yang digunakan oleh semua aplikasi berdasarkan Kiwix JS. API ini memungkinkan aplikasi bertindak sebagai server, mencegat Permintaan Pengambilan dari dokumen atau artikel utama yang sedang dilihat, dan mengalihkan permintaan tersebut ke backend (JS) untuk mengekstrak dan membuat Respons dari arsip ZIM.

Penyimpanan, penyimpanan di mana saja

Mengingat ukuran arsip ZIM yang besar, penyimpanan dan akses ke arsip tersebut, terutama di perangkat seluler, mungkin merupakan masalah terbesar bagi developer Kiwix. Banyak pengguna akhir Kiwix mendownload konten dalam aplikasi, saat Internet tersedia, untuk penggunaan offline nanti. Pengguna lain mendownload di PC menggunakan torrent, lalu mentransfernya ke perangkat seluler atau tablet, dan beberapa pengguna bertukar konten di flash drive USB atau hard drive portabel di area dengan Internet seluler yang tidak stabil atau mahal. Semua cara mengakses konten dari lokasi arbitrer yang dapat diakses pengguna harus didukung oleh Kiwix JS dan Kiwix PWA.

Yang awalnya memungkinkan Kiwix JS membaca arsip yang sangat besar, berukuran ratusan GB (satu arsip ZIM kami berukuran 166 GB!) bahkan di perangkat dengan memori rendah, adalah File API. API ini didukung secara universal di browser apa pun, bahkan browser yang sangat lama, sehingga berfungsi sebagai penggantian universal, untuk saat API yang lebih baru tidak didukung. Caranya semudah menentukan elemen input dalam HTML, dalam kasus Kiwix:

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

Setelah dipilih, elemen input akan menyimpan objek File yang pada dasarnya adalah metadata yang mereferensikan data pokok dalam penyimpanan. Secara teknis, backend berorientasi objek Kiwix, yang ditulis dalam JavaScript sisi klien murni, membaca potongan kecil dari arsip besar sesuai kebutuhan. Jika slice tersebut perlu didekompresi, backend akan meneruskannya ke dekompresor Wasm, mendapatkan slice lebih lanjut jika diminta, hingga blob lengkap didekompresi (biasanya artikel atau aset). Artinya, arsip besar tidak perlu dibaca sepenuhnya ke memori.

Meskipun bersifat universal, File API memiliki kelemahan yang membuat aplikasi Kiwix JS tampak clunky dan kuno dibandingkan dengan aplikasi native: aplikasi ini mengharuskan pengguna memilih arsip menggunakan pemilih file, atau tarik lalu lepas file ke dalam aplikasi, setiap kali aplikasi diluncurkan, karena dengan API ini, tidak ada cara untuk mempertahankan izin akses dari satu sesi ke sesi berikutnya.

Untuk mengurangi UX yang buruk ini, seperti banyak developer, developer Kiwix JS awalnya menggunakan rute Electron. ElectronJS adalah framework luar biasa yang menyediakan fitur canggih, termasuk akses penuh ke sistem file menggunakan Node API. Namun, metode ini memiliki beberapa kelemahan yang sudah diketahui:

  • Aplikasi ini hanya berjalan di sistem operasi desktop.
  • File ini besar dan berat (70 MB–100 MB).

Ukuran aplikasi Electron, karena fakta bahwa salinan lengkap Chromium disertakan dengan setiap aplikasi, dibandingkan dengan hanya 5,1 MB untuk PWA yang diminimalkan dan dipaketkan.

Jadi, apakah ada cara bagi Kiwix untuk meningkatkan situasi bagi pengguna PWA?

File System Access API untuk membantu

Sekitar tahun 2019, Kiwix mengetahui API baru yang sedang menjalani uji coba origin di Chrome 78, yang kemudian disebut Native File System API. API ini menjanjikan kemampuan untuk mendapatkan handle file untuk file atau folder dan menyimpannya dalam database IndexedDB. Yang terpenting, nama sebutan ini tetap ada di antara sesi aplikasi, sehingga pengguna tidak dipaksa untuk memilih file atau folder lagi saat meluncurkan kembali aplikasi (meskipun mereka harus menjawab perintah izin cepat). Pada saat mencapai produksi, API ini telah diganti namanya menjadi File System Access API, dan bagian inti distandarisasi oleh WHATWG sebagai File System API (FSA).

Jadi, bagaimana cara kerja bagian File System Access dari API? Beberapa poin penting yang perlu diperhatikan:

  • Ini adalah API asinkron (kecuali untuk fungsi khusus di Web Worker).
  • Pemilih file atau direktori harus diluncurkan secara terprogram dengan mengambil gestur pengguna (klik atau ketuk elemen UI).
  • Agar pengguna dapat memberikan izin lagi untuk mengakses file yang dipilih sebelumnya (dalam sesi baru), gestur pengguna juga diperlukan—bahkan browser akan menolak untuk menampilkan dialog izin jika tidak dimulai oleh gestur pengguna.

Kode ini relatif mudah, selain harus menggunakan IndexedDB API yang canggung untuk menyimpan handle file dan direktori. Kabar baiknya, ada beberapa library yang melakukan banyak pekerjaan berat untuk Anda, seperti browser-fs-access. Di Kiwix JS, kami memutuskan untuk bekerja langsung dengan API, yang sangat didokumentasikan dengan baik.

Membuka pemilih file dan direktori

Membuka pemilih file terlihat seperti ini (di sini menggunakan Promises, tetapi jika Anda lebih memilih sugar async/await, lihat tutorial Chrome untuk Developer):

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

Perhatikan bahwa untuk memudahkan, kode ini hanya memproses file pertama yang dipilih (dan melarang pemilihan lebih dari satu). Jika ingin mengizinkan pemilihan beberapa file dengan { multiple: true }, cukup gabungkan semua Promise yang memproses setiap handle dalam pernyataan Promise.all().then(...), misalnya:

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

Namun, memilih beberapa file mungkin lebih baik dilakukan dengan meminta pengguna untuk memilih direktori yang berisi file tersebut, bukan file individual di direktori tersebut, terutama karena pengguna Kiwix cenderung mengatur semua file ZIM mereka di direktori yang sama. Kode untuk meluncurkan pemilih direktori hampir sama dengan di atas, kecuali Anda menggunakan window.showDirectoryPicker.then(function (dirHandle) { … });.

Memproses handle file atau direktori

Setelah memiliki handle, Anda perlu memprosesnya, sehingga fungsi processFileHandle dapat terlihat seperti ini:

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

Perhatikan bahwa Anda harus menyediakan fungsi untuk menyimpan handle file, tidak ada metode praktis untuk ini, kecuali jika Anda menggunakan library abstraksi. Implementasi Kiwix untuk hal ini dapat dilihat dalam file cache.js, tetapi dapat disederhanakan secara signifikan jika hanya digunakan untuk menyimpan dan mengambil handle file atau folder.

Memproses direktori sedikit lebih rumit karena Anda harus melakukan iterasi pada entri di direktori yang dipilih dengan entries.next() asinkron untuk menemukan file atau jenis file yang Anda inginkan. Ada berbagai cara untuk melakukannya, tetapi ini adalah kode yang digunakan di Kiwix PWA, dalam garis besar:

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

Perhatikan bahwa untuk setiap entri di entryList, Anda nantinya harus mendapatkan file dengan entry.getFile().then(function (file) { … }) saat perlu menggunakannya, atau yang setara menggunakan const file = await entry.getFile() di async function.

Bisakah kita melangkah lebih jauh?

Persyaratan bagi pengguna untuk memberikan izin yang dimulai dengan gestur pengguna pada peluncuran aplikasi berikutnya menambahkan sedikit hambatan untuk pembukaan (ulang) file dan folder, tetapi masih jauh lebih lancar daripada dipaksa untuk memilih ulang file. Developer Chromium saat ini menyelesaikan kode yang akan memungkinkan izin persisten untuk PWA yang diinstal. Hal ini merupakan sesuatu yang telah banyak diminta oleh developer PWA, dan sangat diantisipasi.

Tapi, bagaimana jika kita tidak perlu menunggu? Developer Kiwix baru-baru ini menemukan bahwa saat ini Anda dapat menghapus semua permintaan izin, dengan menggunakan fitur baru yang menarik dari File Access API yang didukung oleh browser Chromium dan Firefox (dan sebagian didukung oleh Safari, tetapi masih tidak memiliki FileSystemWritableFileStream). Fitur baru ini adalah Origin Private File System.

Beralih sepenuhnya ke native: Sistem File Pribadi Origin

Origin Private File System (OPFS) masih merupakan fitur eksperimental di PWA Kiwix, tetapi tim sangat senang untuk mendorong pengguna mencobanya karena fitur ini sebagian besar menjembatani kesenjangan antara aplikasi native dan aplikasi Web. Berikut adalah manfaat utamanya:

  • Arsip di OPFS dapat diakses tanpa permintaan izin, bahkan saat peluncuran. Pengguna dapat melanjutkan membaca artikel, dan menjelajahi arsip, dari bagian yang terakhir dibaca di sesi sebelumnya, tanpa hambatan sama sekali.
  • Fitur ini memberikan akses yang sangat dioptimalkan ke file yang disimpan di dalamnya: di Android, kami melihat peningkatan kecepatan antara lima hingga sepuluh kali lebih cepat.

Akses file standar di Android menggunakan File API sangat lambat, terutama (seperti yang sering terjadi pada pengguna Kiwix) jika arsip besar disimpan di kartu microSD, bukan di penyimpanan perangkat. Semuanya berubah dengan API baru ini. Meskipun sebagian besar pengguna tidak akan dapat menyimpan file berukuran 97 GB di OPFS (yang menggunakan penyimpanan perangkat, bukan penyimpanan kartu microSD), OPFS sangat cocok untuk menyimpan arsip berukuran kecil hingga sedang. Anda ingin ensiklopedia medis terlengkap dari WikiProject Medicine? Tidak masalah, dengan ukuran 1,7 GB, file tersebut mudah muat di OPFS. (Tips: cari othermdwiki_en_all_maxi di library dalam aplikasi.)

Cara kerja OPFS

OPFS adalah sistem file yang disediakan oleh browser, terpisah untuk setiap origin, yang dapat dianggap mirip dengan penyimpanan cakupan aplikasi di Android. File dapat diimpor ke OPFS dari sistem file yang terlihat pengguna, atau dapat didownload langsung ke dalamnya (API juga memungkinkan pembuatan file di OPFS). Setelah berada di OPFS, file tersebut akan terisolasi dari bagian perangkat lainnya. Di browser berbasis Chromium desktop, Anda juga dapat mengekspor file kembali dari OPFS ke sistem file yang terlihat pengguna.

Untuk menggunakan OPFS, langkah pertama adalah meminta akses ke sistem file tersebut, menggunakan navigator.storage.getDirectory() (sekali lagi, jika Anda lebih suka melihat kode menggunakan await, baca Sistem File Pribadi Origin):

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

Nama sebutan yang Anda dapatkan dari ini adalah jenis FileSystemDirectoryHandle yang sama dengan yang Anda dapatkan dari window.showDirectoryPicker() yang disebutkan di atas, yang berarti Anda dapat menggunakan kembali kode yang menanganinya (dan untungnya tidak perlu menyimpannya di indexedDB – cukup dapatkan saat Anda memerlukannya). Misalkan Anda sudah memiliki beberapa file di OPFS dan ingin menggunakannya, lalu, menggunakan fungsi iterateAsyncDirEntries() yang ditampilkan sebelumnya, Anda dapat melakukan hal seperti:

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

Jangan lupa bahwa Anda masih perlu menggunakan getFile() pada entri yang ingin Anda gunakan dari array archiveList.

Mengimpor file ke OPFS

Jadi, bagaimana cara memasukkan file ke OPFS? Tidak semudah itu. Pertama, Anda perlu memperkirakan jumlah penyimpanan yang harus digunakan, dan memastikan bahwa pengguna tidak mencoba memasukkan file berukuran 97 GB jika tidak muat.

Mendapatkan estimasi kuota sangat mudah: navigator.storage.estimate().then(function (estimate) { … });. Yang sedikit lebih sulit adalah mencari cara untuk menampilkannya kepada pengguna. Di aplikasi Kiwix, kami memilih panel kecil dalam aplikasi yang terlihat tepat di samping kotak centang yang memungkinkan pengguna mencoba OPFS:

Panel yang menampilkan penyimpanan yang digunakan dalam persen dan sisa penyimpanan yang tersedia dalam Gigabyte.

Panel diisi menggunakan estimate.quota dan estimate.usage, misalnya:

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

Seperti yang dapat Anda lihat, ada juga tombol yang memungkinkan pengguna menambahkan file ke OPFS dari sistem file yang terlihat pengguna. Kabar baiknya adalah Anda cukup menggunakan File API untuk mendapatkan objek File (atau beberapa objek) yang diperlukan yang akan diimpor. Bahkan, sebaiknya jangan gunakan window.showOpenFilePicker() karena metode ini tidak didukung oleh Firefox, sedangkan OPFS pasti didukung.

Tombol Tambahkan file yang terlihat dan Anda lihat pada screenshot di atas bukan pemilih file lama, tetapi tombol tersebut akan click() pemilih lama yang tersembunyi (elemen <input type="file" multiple … />) saat diklik atau diketuk. Aplikasi kemudian hanya merekam peristiwa change dari input file tersembunyi, memeriksa ukuran file, dan menolaknya jika terlalu besar untuk kuota. Jika semuanya baik, tanyakan kepada pengguna apakah mereka ingin menambahkannya:

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

Dialog yang menanyakan apakah pengguna ingin menambahkan daftar file .zim ke sistem file pribadi asal.

Karena pada beberapa sistem operasi, seperti Android, mengimpor arsip bukanlah operasi tercepat, Kiwix juga menampilkan banner dan indikator lingkaran berputar kecil saat arsip sedang diimpor. Tim tidak mengetahui cara menambahkan indikator progres untuk operasi ini: jika Anda mengetahuinya, harap kirimkan jawabannya melalui kartu pos.

Jadi, bagaimana Kiwix menerapkan fungsi importOPFSEntries()? Hal ini melibatkan penggunaan metode fileHandle.createWriteable(), yang secara efektif memungkinkan setiap file di-streaming ke OPFS. Semua pekerjaan berat ditangani oleh browser. (Kiwix menggunakan Promise di sini karena alasan yang berkaitan dengan codebase lama kami, tetapi harus dikatakan bahwa dalam hal ini await menghasilkan sintaksis yang lebih sederhana, dan menghindari efek piramida malapetaka.)

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

Mendownload aliran file langsung ke OPFS

Variasi dari hal ini adalah kemampuan untuk melakukan streaming file dari Internet langsung ke OPFS, atau ke direktori mana pun yang memiliki handle direktori (yaitu, direktori yang dipilih dengan window.showDirectoryPicker()). Variasi ini menggunakan prinsip yang sama dengan kode di atas, tetapi membuat Response yang terdiri dari ReadableStream dan pengontrol yang mengantrekan byte yang dibaca dari file jarak jauh. Response.body yang dihasilkan kemudian di-piping ke penulis file baru di dalam OPFS.

Dalam hal ini, Kiwix dapat menghitung byte yang melewati ReadableStream, sehingga memberikan indikator progres kepada pengguna, dan juga memperingatkan mereka agar tidak keluar dari aplikasi selama download. Kodenya agak terlalu rumit untuk ditampilkan di sini, tetapi karena aplikasi kami adalah aplikasi FOSS, Anda dapat melihat sumbernya jika tertarik untuk melakukan hal serupa. Tampilan UI Kiwix seperti ini (nilai progres yang berbeda yang ditampilkan di bawah ini karena hanya memperbarui banner saat persentase berubah, tetapi memperbarui panel Progres download lebih rutin):

Antarmuka pengguna Kiwix dengan panel di bagian bawah yang memperingatkan pengguna untuk tidak keluar dari aplikasi dan menampilkan progres download arsip .zim.

Karena proses download dapat berlangsung cukup lama, Kiwix memungkinkan pengguna menggunakan aplikasi secara bebas selama operasi, tetapi memastikan banner selalu ditampilkan, sehingga pengguna diingatkan untuk tidak menutup aplikasi hingga operasi download selesai.

Menerapkan pengelola file mini dalam aplikasi

Pada tahap ini, developer PWA Kiwix menyadari bahwa menambahkan file ke OPFS saja tidak cukup. Aplikasi juga perlu memberi pengguna cara untuk menghapus file yang tidak lagi mereka perlukan dari area penyimpanan ini, dan idealnya, juga, untuk mengekspor file apa pun yang dikunci di OPFS kembali ke sistem file yang terlihat pengguna. Secara efektif, Anda perlu menerapkan sistem pengelolaan file mini di dalam aplikasi.

Kami ingin menyampaikan apresiasi kepada ekstensi OPFS Explorer yang luar biasa untuk Chrome (ekstensi ini juga berfungsi di Edge). Fitur ini menambahkan tab di alat developer yang memungkinkan Anda melihat apa yang ada di OPFS, dan juga menghapus file yang tidak sah atau gagal. Hal ini sangat penting untuk memeriksa apakah kode berfungsi, memantau perilaku download, dan secara umum membersihkan eksperimen pengembangan kami.

Ekspor file bergantung pada kemampuan untuk mendapatkan handle file pada file atau direktori yang dipilih tempat Kiwix akan menyimpan file yang diekspor, sehingga ini hanya berfungsi dalam konteks yang dapat menggunakan metode window.showSaveFilePicker(). Jika file Kiwix lebih kecil dari beberapa GB, kita dapat membuat blob di memori, memberinya URL, lalu mendownloadnya ke sistem file yang terlihat pengguna. Sayangnya, hal itu tidak dapat dilakukan dengan arsip yang begitu besar. Jika didukung, ekspor cukup mudah: secara virtual sama, secara terbalik, seperti menyimpan file ke dalam OPFS (dapatkan handle pada file yang akan disimpan, minta pengguna untuk memilih lokasi untuk menyimpannya dengan window.showSaveFilePicker(), lalu gunakan createWriteable() di saveHandle). Anda dapat melihat kode di repo.

Penghapusan file didukung oleh semua browser, dan dapat dilakukan dengan dirHandle.removeEntry('filename') sederhana. Dalam kasus Kiwix, sebaiknya kita mengulangi entri OPFS seperti yang kita lakukan di atas, sehingga kita dapat memeriksa apakah file yang dipilih ada terlebih dahulu dan meminta konfirmasi, tetapi hal itu mungkin tidak diperlukan untuk semua orang. Sekali lagi, Anda dapat memeriksa kode kami jika berminat.

Diputuskan untuk tidak mengacaukan UI Kiwix dengan tombol yang menawarkan opsi ini, dan sebagai gantinya menempatkan ikon kecil langsung di bawah daftar arsip. Mengetuk salah satu ikon ini akan mengubah warna daftar arsip, sebagai petunjuk visual kepada pengguna tentang tindakan yang akan mereka lakukan. Kemudian, pengguna mengklik atau mengetuk salah satu arsip, dan operasi yang sesuai (ekspor atau hapus) dilakukan (setelah konfirmasi).

Dialog yang menanyakan kepada pengguna apakah mereka ingin menghapus file .zim.

Terakhir, berikut adalah demo screencast dari semua fitur pengelolaan file yang dibahas di atas—menambahkan file ke OPFS, langsung mendownload file ke dalamnya, menghapus file, dan mengekspor ke sistem file yang terlihat pengguna.

Pekerjaan developer tidak pernah selesai

OPFS adalah inovasi yang bagus bagi developer PWA, yang menyediakan fitur pengelolaan file yang sangat canggih dan sangat membantu dalam menutup kesenjangan antara aplikasi native dan aplikasi Web. Namun, developer adalah sekelompok orang yang menyedihkan—mereka tidak pernah puas! OPFS hampir sempurna, tetapi tidak sepenuhnya… Sangat bagus bahwa fitur utama berfungsi di browser Chromium dan Firefox, serta diterapkan di Android dan desktop. Kami berharap kumpulan fitur lengkap juga akan diimplementasikan di Safari dan iOS dalam waktu dekat. Masalah berikut masih ada:

  • Firefox saat ini menetapkan batas 10 GB pada kuota OPFS, berapa pun jumlah ruang disk yang ada. Meskipun bagi sebagian besar penulis PWA, hal ini mungkin cukup, tetapi bagi Kiwix, hal ini cukup membatasi. Untungnya, browser Chromium jauh lebih longgar.
  • Saat ini, Anda tidak dapat mengekspor file besar dari OPFS ke sistem file yang terlihat pengguna di browser seluler, atau Firefox desktop, karena window.showSaveFilePicker() tidak diterapkan. Di browser ini, file besar terperangkap secara efektif di OPFS. Hal ini bertentangan dengan etos Kiwix tentang akses terbuka ke konten, dan kemampuan untuk berbagi arsip antarpengguna terutama di area dengan konektivitas Internet yang terputus-putus atau mahal.
  • Pengguna tidak dapat mengontrol penyimpanan yang akan digunakan sistem file virtual OPFS. Hal ini sangat bermasalah pada perangkat seluler, tempat pengguna mungkin memiliki ruang penyimpanan yang besar di kartu microSD, tetapi sangat sedikit di penyimpanan perangkat.

Namun, secara keseluruhan, ini adalah masalah kecil dalam langkah besar untuk akses file di PWA. Tim PWA Kiwix sangat berterima kasih kepada developer dan pendukung Chromium yang pertama kali mengusulkan dan mendesain File System Access API, serta atas kerja kerasnya dalam mencapai konsensus di antara vendor browser tentang pentingnya Origin Private File System. Untuk Kiwix JS PWA, aplikasi ini telah menyelesaikan banyak masalah UX yang telah menghambat aplikasi di masa lalu, dan membantu kami dalam upaya meningkatkan aksesibilitas konten Kiwix untuk semua orang. Harap coba Kiwix PWA dan beri tahu developer pendapat Anda.

Untuk beberapa referensi yang bagus tentang kemampuan PWA, lihat situs berikut: