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

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

Orang-orang yang berkumpul di sekitar laptop, berdiri di atas meja simpel dengan kursi plastik di sebelah kiri. Latar belakang yang tampak seperti sekolah di negara berkembang.

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

Tentang Kiwix

Lebih dari 30 tahun setelah kelahiran web, sepertiga populasi dunia masih menunggu akses andal ke internet menurut International Telecommunication Union. Apakah di mana ceritanya berakhir? Tentu saja tidak. Orang-orang di Kiwix, sebuah lembaga nirlaba yang berbasis di Swiss, telah mengembangkan ekosistem aplikasi dan konten open source yang bertujuan untuk menyediakan pengetahuan bagi orang-orang dengan akses internet yang terbatas atau tidak memiliki akses sama sekali. idenya adalah jika Anda tidak dapat mengakses internet dengan mudah, maka seseorang dapat mendownload resource utama untuk Anda, di mana dan kapan konektivitas tersedia, serta menyimpannya secara lokal untuk digunakan secara offline di lain waktu. Banyak situs penting, misalnya Wikipedia, Project Gutenberg, Stack Exchange, atau bahkan TED talks, sekarang 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, karena keseluruhan Wikipedia dalam bahasa Inggris (6,4 juta artikel, plus gambar) dikompresi menjadi 97 GB setelah konversi ke format ZIM, yang terdengar sangat banyak sampai Anda menyadari bahwa jumlah semua pengetahuan manusia kini dapat muat di ponsel Android kelas menengah. Banyak resource yang lebih kecil juga ditawarkan, termasuk versi Wikipedia bertema, 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 dalam pengembangan aplikasi Web universal yang perlu menyediakan 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 mengatasi tantangan tersebut.

Aplikasi Web untuk penggunaan offline?

Pengguna Kiwix adalah pengguna eklektik dengan berbagai kebutuhan, dan Kiwix memiliki sedikit atau tidak memiliki kontrol atas perangkat dan sistem operasi tempat mereka akan mengakses konten mereka. Beberapa perangkat tersebut mungkin lambat atau sudah usang, terutama di wilayah berpendapatan 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 Atwood's Law, 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 porta ini, yang disebut Kiwix HTML5, digunakan untuk OS Firefox yang kini sudah tidak berfungsi dan untuk ekstensi browser. Pada intinya adalah (dan sekarang) mesin dekompresi C++ (XZ dan ZSTD) 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 Kiwix JS khusus, dan mulai menambahkan integrasi OS yang akan memungkinkan aplikasi menawarkan kemampuan seperti aplikasi native, terutama di area penggunaan offline, penginstalan, penanganan file, dan akses sistem file.

PWA offline-first sangat ringan, sehingga sangat cocok untuk konteks di mana ada internet seluler yang terputus-putus atau mahal. Teknologi di balik ini adalah Service Worker API dan Cache API terkait, yang digunakan oleh semua aplikasi berdasarkan Kiwix JS. API ini memungkinkan aplikasi untuk bertindak sebagai server, mencegat Permintaan Pengambilan dari dokumen atau artikel utama yang dilihat, dan mengalihkannya ke backend (JS) untuk mengekstrak dan membuat Respons dari arsip ZIM.

Penyimpanan, penyimpanan di mana saja

Mengingat besarnya ukuran arsip ZIM, penyimpanan dan akses ke sana, terutama pada perangkat seluler, mungkin merupakan masalah terbesar bagi developer Kiwix. Banyak pengguna akhir Kiwix mendownload konten dalam aplikasi, saat Internet tersedia, untuk digunakan secara offline nanti. Pengguna lain mendownload file di PC menggunakan torrent, lalu mentransfernya ke perangkat seluler atau tablet, dan bertukar konten di stik USB atau hard drive portabel di area dengan Internet seluler yang sulit diakses atau mahal. Semua cara untuk mengakses konten dari sembarang lokasi yang dapat diakses pengguna ini harus didukung oleh Kiwix JS dan Kiwix PWA.

Yang awalnya memungkinkan Kiwix JS membaca arsip yang sangat besar, dari ratusan GB (salah satu arsip ZIM kami berukuran 166 GB!) bahkan pada perangkat bermemori rendah, adalah File API. API ini didukung secara universal di browser apa pun, bahkan browser yang sangat lama pun, sehingga bertindak sebagai penggantian universal ketika API yang lebih baru tidak didukung. 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 menyimpan objek File yang pada dasarnya merupakan metadata yang merujuk ke data pokok dalam penyimpanan. Secara teknis, backend berorientasi objek Kiwix, yang ditulis dalam JavaScript murni sisi klien, membaca potongan kecil arsip besar sesuai kebutuhan. Jika potongan tersebut perlu didekompresi, backend akan meneruskannya ke dekompresi, yang akan mendapatkan potongan lebih lanjut jika diminta, hingga blob penuh didekompresi (biasanya artikel atau aset). Artinya, arsip besar tidak perlu dibaca sepenuhnya ke dalam memori.

Universal seperti apa adanya, File API memiliki kelemahan yang membuat aplikasi Kiwix JS tampak kaku dan kuno dibandingkan dengan aplikasi native: API 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 lainnya, developer Kiwix JS pada awalnya menggunakan rute Elektron. ElectronJS adalah framework luar biasa yang menyediakan fitur canggih, termasuk akses penuh ke sistem file menggunakan Node API. Namun, cara ini memiliki beberapa kelemahan umum:

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

Ukuran aplikasi Electron, karena salinan lengkap Chromium disertakan dengan setiap aplikasi, sangat buruk dibandingkan dengan hanya 5,1 MB untuk PWA yang diperkecil dan dipaketkan.

Jadi, apakah ada cara Kiwix dapat memperbaiki situasi bagi pengguna PWA?

File System Access API siap membantu

Sekitar tahun 2019, Kiwix menemukan API baru yang sedang menjalani uji coba asal di Chrome 78, lalu disebut Native File System API. Plugin ini menjanjikan kemampuan untuk mendapatkan handle file untuk sebuah file atau folder dan menyimpannya di database IndexedDB. Yang terpenting, handle 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 prompt izin cepat). Pada saat mencapai produksi, nama ini telah diganti namanya menjadi File System Access API, dan bagian intinya distandarkan oleh HOWWG sebagai File System API (FSA).

Jadi, bagaimana cara kerja bagian Akses Sistem File pada API? Beberapa hal penting yang perlu diperhatikan:

  • Ini adalah API asinkron (kecuali untuk fungsi khusus di Pekerja Web).
  • Pemilih file atau direktori harus diluncurkan secara terprogram dengan menangkap gestur pengguna (klik atau ketuk pada 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 menampilkan permintaan izin jika tidak dimulai oleh gestur pengguna.

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

Membuka pemilih file dan direktori

Membuka pemilih file terlihat seperti ini (di sini menggunakan Promise, tetapi jika Anda lebih menyukai async/await sugar, 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,
    );
  });

Perlu diperhatikan bahwa agar lebih praktis, kode ini hanya memproses file yang pertama kali dipilih (dan melarang memilih lebih dari satu). Jika Anda ingin mengizinkan pemilihan beberapa file dengan { multiple: true }, Anda cukup menggabungkan 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 bisa dikatakan lebih baik dengan meminta pengguna memilih direktori yang berisi file tersebut, bukan file individual di dalamnya, terutama karena pengguna Kiwix cenderung mengatur semua file ZIM dalam direktori yang sama. Kode untuk meluncurkan pemilih direktori hampir sama seperti di atas, kecuali Anda menggunakan window.showDirectoryPicker.then(function (dirHandle) { … });.

Memproses nama 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 hal ini, kecuali jika Anda menggunakan library abstraksi. Implementasi Kiwix untuk hal ini dapat dilihat di file cache.js, tetapi dapat disederhanakan secara signifikan jika hanya digunakan untuk menyimpan dan mengambil file atau handle folder.

Memproses direktori sedikit lebih rumit karena Anda harus melakukan iterasi melalui entri dalam 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 PWA Kiwix, secara 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 dalam entryList, Anda nanti harus mendapatkan file dengan entry.getFile().then(function (file) { … }) saat perlu menggunakannya, atau yang setara menggunakan const file = await entry.getFile() dalam async function.

Bisakah kita melangkah lebih jauh?

Persyaratan bagi pengguna untuk memberikan izin yang dimulai dengan gestur pengguna pada peluncuran aplikasi berikutnya akan menambahkan sedikit hambatan untuk membuka file dan folder (kembali), tetapi masih jauh lebih mudah daripada dipaksa untuk memilih kembali file. Developer Chromium saat ini menyelesaikan kode yang akan memberikan izin persisten untuk PWA yang diinstal. Hal ini dicari oleh banyak developer PWA, dan sangat diantisipasi.

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

Menjadi yang sepenuhnya native: Origin Private File System

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

  • Arsip di OPFS dapat diakses tanpa perintah izin, bahkan saat diluncurkan. Pengguna dapat melanjutkan membaca artikel, dan menjelajahi arsip, dari bagian terakhir yang mereka tinggalkan di sesi sebelumnya, tanpa hambatan.
  • Fitur ini memberikan akses yang sangat dioptimalkan ke file yang tersimpan di dalamnya: di Android kami melihat peningkatan kecepatan antara lima dan sepuluh kali lebih cepat.

Akses file standar di Android yang menggunakan File API sangat lambat, terutama (seperti yang sering terjadi bagi pengguna Kiwix) jika arsip besar disimpan di kartu microSD, bukan di penyimpanan perangkat. Itu semua berubah dengan API baru ini. Meskipun sebagian besar pengguna tidak dapat menyimpan file sebesar 97 GB di OPFS (yang memakai penyimpanan perangkat, bukan penyimpanan kartu microSD), file ini sangat cocok untuk menyimpan arsip berukuran kecil hingga sedang. Anda menginginkan ensiklopedia medis paling lengkap dari WikiProject Medicine? Tidak masalah, pada 1,7 GB sangat cocok di OPFS! (Tips: cari othermdwiki_en_all_maxi di in-app library.)

Cara kerja OPFS

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

Untuk menggunakan OPFS, langkah pertama adalah meminta akses ke OPFS, 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 channel 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 membutuhkannya). Anggaplah Anda sudah memiliki beberapa file di OPFS dan ingin menggunakannya, lalu, dengan 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 harus menggunakan getFile() pada entri apa pun yang ingin Anda gunakan dari array archiveList.

Mengimpor file ke OPFS

Jadi, bagaimana Anda memasukkan file ke OPFS sejak awal? Tidak secepat itu. Pertama, Anda perlu memperkirakan jumlah penyimpanan yang dapat digunakan, dan memastikan pengguna tidak mencoba memasukkan file berukuran 97 GB jika ukurannya tidak sesuai.

Mendapatkan estimasi kuota itu mudah: navigator.storage.estimate().then(function (estimate) { … });. Sedikit lebih sulit untuk menampilkan ini kepada pengguna. Di aplikasi Kiwix, kami memilih panel dalam aplikasi kecil 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 Anda lihat, ada juga tombol yang memungkinkan pengguna menambahkan file ke OPFS dari sistem file yang terlihat oleh pengguna. Kabar baiknya di sini adalah Anda dapat menggunakan File API untuk mendapatkan objek (atau objek) File yang diperlukan yang akan diimpor. Bahkan, sebaiknya Anda tidak menggunakan window.showOpenFilePicker() karena metode ini tidak didukung oleh Firefox, sedangkan OPFS paling didukung.

Tombol Tambahkan file yang terlihat dalam screenshot di atas bukanlah alat pilih file lama, tetapi tombol ini melakukan click() alat pilih lama yang tersembunyi (elemen <input type="file" multiple … />) saat diklik atau diketuk. Aplikasi kemudian hanya menangkap peristiwa change dari input file tersembunyi, memeriksa ukuran file, dan menolaknya jika terlalu besar untuk kuota. Jika semuanya berjalan lancar, 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 pengguna apakah mereka ingin menambahkan daftar file .zim ke sistem file pribadi asal.

Karena pada beberapa sistem operasi, seperti Android, mengimpor arsip bukanlah operasi yang paling cepat, Kiwix juga menampilkan banner dan indikator lingkaran berputar kecil saat arsip sedang diimpor. Tim tidak tahu cara menambahkan indikator progres untuk operasi ini: jika Anda berhasil, tolong jawab melalui kartu pos.

Jadi, bagaimana cara Kiwix menerapkan fungsi importOPFSEntries()? Hal ini melibatkan penggunaan metode fileHandle.createWriteable(), yang secara efektif memungkinkan setiap file di-streaming ke OPFS. Semua kerja keras 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 streaming file langsung ke OPFS

Variasi dari hal ini adalah kemampuan untuk melakukan streaming file dari Internet secara langsung ke OPFS, atau ke direktori apa pun yang sudah memiliki handle direktori (yaitu, direktori yang dipilih dengan window.showDirectoryPicker()). Kode 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 disalurkan ke penulis file baru dalam OPFS.

Dalam hal ini, Kiwix dapat menghitung byte yang melewati ReadableStream, sehingga memberikan indikator progres kepada pengguna, serta memperingatkan mereka untuk tidak keluar dari aplikasi selama proses download. Kode ini agak terlalu rumit untuk ditampilkan di sini, tetapi karena aplikasi kita adalah aplikasi FOSS, Anda dapat melihat sumbernya jika tertarik untuk melakukan hal serupa. Seperti inilah tampilan UI Kiwix (nilai progres yang berbeda yang ditampilkan di bawah ini adalah karena hanya mengupdate banner saat persentase berubah, tetapi memperbarui panel Progres download secara lebih teratur):

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

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

Mengimplementasikan pengelola file mini dalam aplikasi

Pada tahap ini, developer PWA Kiwix menyadari bahwa menambahkan file ke OPFS tidaklah cukup. Aplikasi ini juga perlu memberi pengguna cara untuk menghapus file yang tidak lagi diperlukan dari area penyimpanan ini, dan idealnya, juga, untuk mengekspor file yang terkunci di OPFS kembali ke sistem file yang terlihat oleh pengguna. Oleh karena itu, penerapan sistem pengelolaan file mini di dalam aplikasi menjadi penting.

Sekilas tentang ekstensi OPFS Explorer yang luar biasa untuk Chrome (juga dapat digunakan di Edge). Update ini menambahkan tab di alat developer yang memungkinkan Anda melihat persis apa yang ada di OPFS, dan juga menghapus file jahat atau gagal. Kami sangat berharga 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 memungkinkan Kiwix menggunakan metode window.showSaveFilePicker(). Jika file Kiwix lebih kecil dari beberapa GB, kami akan dapat membuat blob di memori, memberikan URL, lalu mendownloadnya ke sistem file yang terlihat oleh pengguna. Sayangnya, hal itu tidak mungkin dilakukan dengan arsip sebesar itu. Jika didukung, ekspor cukup mudah: hampir sama, secara terbalik, seperti menyimpan file ke OPFS (mendapatkan handle pada file yang akan disimpan, minta pengguna memilih lokasi untuk menyimpannya dengan window.showSaveFilePicker(), lalu gunakan createWriteable() pada saveHandle). Anda dapat melihat kode di repo.

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

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 bagi pengguna tentang apa yang akan mereka lakukan. Pengguna kemudian mengklik atau mengetuk salah satu arsip, dan operasi yang sesuai (ekspor atau hapus) dilakukan (setelah konfirmasi).

Dialog yang menanyakan 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 oleh pengguna.

Pekerjaan developer tidak pernah selesai

OPFS adalah inovasi hebat bagi developer PWA, yang menyediakan fitur pengelolaan file yang sangat canggih yang sangat berguna untuk menutup kesenjangan antara aplikasi native dan aplikasi Web. Namun, developer adalah kelompok yang menyedihkan—mereka tidak pernah puas! OPFS hampir sempurna, namun belum cukup... Hebatnya, fitur utama dapat berfungsi di browser Chromium dan Firefox, serta dapat diimplementasikan di Android dan desktop. Kami berharap rangkaian fitur lengkap juga akan segera diterapkan di Safari dan iOS. Masalah berikut akan tetap ada:

  • Firefox saat ini menetapkan batas 10 GB untuk kuota OPFS, tidak peduli berapa banyak kapasitas disk yang ada. Meskipun bagi sebagian besar penulis PWA, ini mungkin cukup, untuk Kiwix, itu cukup terbatas. Untungnya, browser Chromium jauh lebih murah.
  • Saat ini, mengekspor file besar dari OPFS ke sistem file yang terlihat oleh pengguna di browser seluler atau Firefox desktop tidak dapat dilakukan karena window.showSaveFilePicker() tidak diterapkan. Dalam browser ini, file besar secara efektif terjebak dalam OPFS. Hal ini bertentangan dengan etos Kiwix tentang akses terbuka ke konten, dan kemampuan untuk berbagi arsip antarpengguna, terutama di area konektivitas Internet yang terputus-putus atau mahal.
  • Pengguna tidak dapat mengontrol penyimpanan mana yang akan digunakan oleh sistem file virtual OPFS. Hal ini terutama menimbulkan masalah pada perangkat seluler, yang mungkin memiliki ruang penyimpanan perangkat besar pada kartu microSD, tetapi ukuran yang sangat kecil pada penyimpanan perangkat.

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

Untuk beberapa sumber daya hebat tentang kemampuan PWA, lihat situs-situs ini: