Sistem file pribadi origin

File System Standard memperkenalkan sistem file pribadi asal (OPFS) sebagai endpoint penyimpanan yang bersifat pribadi untuk asal halaman dan tidak terlihat oleh pengguna yang memberikan akses opsional ke jenis file khusus yang sangat dioptimalkan untuk performa.

Dukungan browser

Sistem file pribadi origin didukung oleh browser modern dan distandarkan oleh Web Hypertext Application Technology Working Group (HOWWG) dalam File System Living Standard.

Dukungan Browser

  • 86
  • 86
  • 111
  • 15.2

Sumber

Motivasi

Ketika Anda memikirkan file di komputer Anda, Anda mungkin berpikir tentang hierarki file: file yang terorganisir dalam folder yang dapat Anda jelajahi dengan penjelajah file sistem operasi Anda. Misalnya, di Windows, untuk pengguna bernama Tomi, daftar Tugasnya mungkin terdapat di C:\Users\Tom\Documents\ToDo.txt. Dalam contoh ini, ToDo.txt adalah nama file, dan Users, Tom, dan Documents adalah nama folder. `C:` di Windows menunjukkan direktori root drive.

Cara tradisional bekerja dengan file di web

Untuk mengedit daftar Tugas di aplikasi web, ini adalah alur yang biasa:

  1. Pengguna mengupload file ke server atau membuka file di klien dengan <input type="file">.
  2. Pengguna membuat perubahan, lalu mendownload file yang dihasilkan dengan memasukkan <a download="ToDo.txt> yang secara terprogram Anda click() melalui JavaScript.
  3. Untuk membuka folder, Anda menggunakan atribut khusus di <input type="file" webkitdirectory>, yang meskipun memiliki nama eksklusif, secara praktis memiliki dukungan browser universal.

Cara modern bekerja dengan file di web

Alur ini tidak mewakili pendapat pengguna terkait pengeditan file, dan berarti pengguna akan mendapatkan salinan file input yang didownload. Oleh karena itu, File System Access API memperkenalkan tiga metode pemilih—showOpenFilePicker(), showSaveFilePicker(), dan showDirectoryPicker()—yang melakukan persis seperti yang disarankan oleh namanya. Keduanya memungkinkan alur sebagai berikut:

  1. Buka ToDo.txt dengan showOpenFilePicker(), dan dapatkan objek FileSystemFileHandle.
  2. Dari objek FileSystemFileHandle, dapatkan File dengan memanggil metode getFile() nama sebutan channel.
  3. Ubah file, lalu panggil requestPermission({mode: 'readwrite'}) di nama sebutan channel.
  4. Jika pengguna menyetujui permintaan izin, simpan perubahan kembali ke file asli.
  5. Atau, panggil showSaveFilePicker() dan biarkan pengguna memilih file baru. (Jika pengguna memilih file yang dibuka sebelumnya, konten file tersebut akan ditimpa.) Untuk penyimpanan berulang, Anda dapat menyimpan handle file, sehingga Anda tidak perlu menampilkan dialog penyimpanan file lagi.

Batasan bekerja dengan file di web

File dan folder yang dapat diakses melalui metode ini berada dalam sistem file yang terlihat oleh pengguna. File yang disimpan dari web, dan file yang dapat dieksekusi secara khusus, ditandai dengan tanda web. Jadi, ada peringatan tambahan yang dapat ditampilkan sistem operasi sebelum file yang berpotensi berbahaya dieksekusi. Sebagai fitur keamanan tambahan, file yang diperoleh dari web juga dilindungi oleh Safe Browsing, yang, demi kesederhanaan dan sesuai konteks artikel ini, Anda dapat menganggap sebagai pemindaian virus berbasis cloud. Saat Anda menulis data ke file menggunakan File System Access API, operasi tulis tidak akan diterapkan, melainkan menggunakan file sementara. File tersebut tidak dimodifikasi kecuali jika lulus semua pemeriksaan keamanan ini. Seperti yang dapat Anda bayangkan, cara ini membuat operasi file relatif lambat, meskipun peningkatan telah diterapkan jika memungkinkan, misalnya, di macOS. Namun, setiap panggilan write() bersifat mandiri, sehingga di balik layar, fungsi tersebut akan membuka file, mencari ke offset yang diberikan, dan terakhir menulis data.

File sebagai dasar pemrosesan

Pada saat yang sama, file adalah cara terbaik untuk merekam data. Misalnya, SQLite menyimpan seluruh database dalam satu file. Contoh lain adalah mipmap yang digunakan dalam pemrosesan gambar. Mipmap adalah urutan gambar yang telah dihitung dan dioptimalkan, masing-masing merupakan representasi resolusi yang semakin rendah dari sebelumnya, sehingga membuat banyak operasi seperti melakukan zoom dengan lebih cepat. Jadi bagaimana aplikasi web bisa mendapatkan manfaat file, tetapi tanpa biaya kinerja pemrosesan file berbasis web? Jawabannya adalah sistem file pribadi asal.

Sistem file pribadi yang terlihat oleh pengguna versus sistem file pribadi origin

Tidak seperti sistem file yang terlihat oleh pengguna yang dijelajahi menggunakan file explorer sistem operasi, dengan file dan folder yang bisa Anda baca, tulis, pindahkan, dan ganti nama, sistem file pribadi asal tidak dimaksudkan untuk dilihat oleh pengguna. File dan folder di sistem file pribadi origin, seperti namanya, bersifat pribadi, dan lebih konkret, bersifat pribadi untuk origin situs. Temukan asal halaman dengan mengetik location.origin di DevTools Console. Misalnya, origin halaman https://developer.chrome.com/articles/ adalah https://developer.chrome.com (yaitu, bagian /articles bukan bagian dari origin). Anda dapat membaca lebih lanjut tentang teori asal dalam Memahami "situs yang sama" dan "origin yang sama". Semua halaman yang memiliki origin yang sama dapat melihat data sistem file pribadi origin yang sama, sehingga https://developer.chrome.com/docs/extensions/mv3/getstarted/extensions-101/ dapat melihat detail yang sama seperti contoh sebelumnya. Setiap origin memiliki sistem file pribadi origin independen sendiri, yang berarti sistem file pribadi origin https://developer.chrome.com benar-benar berbeda dengan sistem file, misalnya, https://web.dev. Di Windows, direktori utama sistem file yang terlihat oleh pengguna adalah C:\\. Yang setara untuk sistem file pribadi origin adalah direktori root yang awalnya kosong per origin yang diakses dengan memanggil metode asinkron navigator.storage.getDirectory(). Untuk perbandingan sistem file yang terlihat oleh pengguna dan sistem file pribadi asal, lihat diagram berikut. Diagram menunjukkan bahwa terlepas dari direktori root, hal lainnya secara konseptual sama, dengan hierarki file dan folder untuk mengatur dan mengatur sesuai kebutuhan untuk kebutuhan data dan penyimpanan Anda.

Diagram sistem file yang terlihat oleh pengguna dan sistem file pribadi asal dengan dua contoh hierarki file. Titik entri sistem file yang terlihat oleh pengguna adalah harddisk simbolis, titik entri untuk sistem file pribadi asal memanggil metode &#39;navigator.storage.getDirectory&#39;.

Detail sistem file pribadi origin

Sama seperti mekanisme penyimpanan lain di browser (misalnya, localStorage atau IndexedDB), sistem file pribadi asal tunduk pada pembatasan kuota browser. Jika pengguna menghapus semua data penjelajahan atau semua data situs, sistem file pribadi origin juga akan dihapus. Panggil navigator.storage.estimate() dan dalam objek respons yang dihasilkan, lihat entri usage untuk melihat berapa banyak penyimpanan yang telah digunakan aplikasi Anda, yang dikelompokkan berdasarkan mekanisme penyimpanan dalam objek usageDetails, tempat Anda ingin melihat entri fileSystem secara spesifik. Karena sistem file pribadi origin tidak terlihat oleh pengguna, tidak ada permintaan izin dan tidak ada pemeriksaan Safe Browsing.

Mendapatkan akses ke direktori {i>root<i}

Untuk mendapatkan akses ke direktori utama, jalankan perintah berikut. Anda akan memiliki handle direktori kosong, lebih khusus lagi, FileSystemDirectoryHandle.

const opfsRoot = await navigator.storage.getDirectory();
// A FileSystemDirectoryHandle whose type is "directory"
// and whose name is "".
console.log(opfsRoot);

Thread utama atau Web Worker

Ada dua cara untuk menggunakan sistem file pribadi origin: di thread utama atau di Web Worker. Web Worker tidak dapat memblokir thread utama, yang berarti dalam konteks ini, API bisa bersifat sinkron, pola yang umumnya tidak diizinkan di thread utama. API sinkron bisa lebih cepat karena tidak perlu menangani promise, dan operasi file biasanya sinkron dalam bahasa seperti C yang dapat dikompilasi ke WebAssembly.

// This is synchronous C code.
FILE *f;
f = fopen("example.txt", "w+");
fputs("Some text\n", f);
fclose(f);

Jika Anda memerlukan operasi file secepat mungkin, atau menangani WebAssembly, lanjutkan ke bagian Menggunakan sistem file pribadi asal di Web Worker. Atau, Anda dapat melanjutkan membaca.

Menggunakan sistem file pribadi origin di thread utama

Membuat file dan folder baru

Setelah memiliki folder root, buat file dan folder menggunakan metode getFileHandle() dan getDirectoryHandle(). Dengan meneruskan {create: true}, file atau folder akan dibuat jika tidak ada. Bangun hierarki file dengan memanggil fungsi tersebut menggunakan direktori yang baru dibuat sebagai titik awal.

const fileHandle = await opfsRoot
    .getFileHandle('my first file', {create: true});
const directoryHandle = await opfsRoot
    .getDirectoryHandle('my first folder', {create: true});
const nestedFileHandle = await directoryHandle
    .getFileHandle('my first nested file', {create: true});
const nestedDirectoryHandle = await directoryHandle
    .getDirectoryHandle('my first nested folder', {create: true});

Hierarki file yang dihasilkan dari contoh kode sebelumnya.

Mengakses file dan folder yang ada

Jika Anda mengetahui namanya, akses file dan folder yang dibuat sebelumnya dengan memanggil metode getFileHandle() atau getDirectoryHandle(), yang meneruskan nama file atau folder.

const existingFileHandle = await opfsRoot.getFileHandle('my first file');
const existingDirectoryHandle = await opfsRoot
    .getDirectoryHandle('my first folder');

Mendapatkan file yang terkait dengan handle file untuk membaca

FileSystemFileHandle mewakili file pada sistem file. Untuk mendapatkan File yang terkait, gunakan metode getFile(). Objek File adalah jenis Blob tertentu, dan dapat digunakan dalam konteks apa pun yang dapat dilakukan Blob. Secara khusus, FileReader, URL.createObjectURL(), createImageBitmap(), dan XMLHttpRequest.send() menerima Blobs dan Files. Jika perlu, mendapatkan File dari FileSystemFileHandle akan "membebaskan" data, sehingga Anda dapat mengaksesnya dan menyediakannya untuk sistem file yang terlihat oleh pengguna.

const file = await fileHandle.getFile();
console.log(await file.text());

Menulis ke file melalui streaming

Streaming data ke file dengan memanggil createWritable() yang membuat FileSystemWritableFileStream ke file tersebut kemudian write() konten. Di bagian akhir, Anda harus melakukan close() streaming.

const contents = 'Some text';
// Get a writable stream.
const writable = await fileHandle.createWritable();
// Write the contents of the file to the stream.
await writable.write(contents);
// Close the stream, which persists the contents.
await writable.close();

Menghapus file dan folder

Hapus file dan folder dengan memanggil metode remove() khusus nama sebutan channel atau nama file. Untuk menghapus folder termasuk semua subfolder, teruskan opsi {recursive: true}.

await fileHandle.remove();
await directoryHandle.remove({recursive: true});

Sebagai alternatif, jika Anda mengetahui nama file atau folder yang akan dihapus dalam direktori, gunakan metode removeEntry().

directoryHandle.removeEntry('my first nested file');

Memindahkan serta mengganti nama file dan folder

Ganti nama serta pindahkan file dan folder menggunakan metode move(). Pemindahan dan penggantian nama dapat terjadi secara bersamaan atau terpisah.

// Rename a file.
await fileHandle.move('my first renamed file');
// Move a file to another directory.
await fileHandle.move(nestedDirectoryHandle);
// Move a file to another directory and rename it.
await fileHandle
    .move(nestedDirectoryHandle, 'my first renamed and now nested file');

Menentukan jalur file atau folder

Untuk mengetahui lokasi file atau folder tertentu dalam kaitannya dengan direktori referensi, gunakan metode resolve(), dengan meneruskan FileSystemHandle sebagai argumen. Untuk mendapatkan jalur lengkap file atau folder dalam sistem file pribadi asal, gunakan direktori utama sebagai direktori referensi yang diperoleh melalui navigator.storage.getDirectory().

const relativePath = await opfsRoot.resolve(nestedDirectoryHandle);
// `relativePath` is `['my first folder', 'my first nested folder']`.

Periksa apakah dua file atau folder menangani titik ke file atau folder yang sama

Terkadang Anda memiliki dua nama sebutan channel dan tidak tahu apakah keduanya mengarah ke file atau folder yang sama. Untuk memeriksa apakah hal ini terjadi, gunakan metode isSameEntry().

fileHandle.isSameEntry(nestedFileHandle);
// Returns `false`.

Menampilkan daftar isi folder

FileSystemDirectoryHandle adalah iterator asinkron yang Anda iterasi dengan loop for await…of. Sebagai iterator asinkron, ia juga mendukung metode entries(), values(), dan keys(), yang dapat Anda pilih berdasarkan informasi yang Anda butuhkan:

for await (let [name, handle] of directoryHandle) {}
for await (let [name, handle] of directoryHandle.entries()) {}
for await (let handle of directoryHandle.values()) {}
for await (let name of directoryHandle.keys()) {}

Menampilkan daftar isi folder dan semua subfolder secara rekursif

Berurusan dengan loop dan fungsi asinkron yang dipasangkan dengan rekursi mudah salah. Fungsi di bawah ini dapat berfungsi sebagai titik awal untuk membuat daftar isi folder dan semua subfoldernya, termasuk semua file dan ukurannya. Anda dapat menyederhanakan fungsi jika tidak memerlukan ukuran file, yang bertuliskan directoryEntryPromises.push, bukan mendorong promise handle.getFile(), tetapi handle secara langsung.

  const getDirectoryEntriesRecursive = async (
    directoryHandle,
    relativePath = '.',
  ) => {
    const fileHandles = [];
    const directoryHandles = [];
    const entries = {};
    // Get an iterator of the files and folders in the directory.
    const directoryIterator = directoryHandle.values();
    const directoryEntryPromises = [];
    for await (const handle of directoryIterator) {
      const nestedPath = `${relativePath}/${handle.name}`;
      if (handle.kind === 'file') {
        fileHandles.push({ handle, nestedPath });
        directoryEntryPromises.push(
          handle.getFile().then((file) => {
            return {
              name: handle.name,
              kind: handle.kind,
              size: file.size,
              type: file.type,
              lastModified: file.lastModified,
              relativePath: nestedPath,
              handle
            };
          }),
        );
      } else if (handle.kind === 'directory') {
        directoryHandles.push({ handle, nestedPath });
        directoryEntryPromises.push(
          (async () => {
            return {
              name: handle.name,
              kind: handle.kind,
              relativePath: nestedPath,
              entries:
                  await getDirectoryEntriesRecursive(handle, nestedPath),
              handle,
            };
          })(),
        );
      }
    }
    const directoryEntries = await Promise.all(directoryEntryPromises);
    directoryEntries.forEach((directoryEntry) => {
      entries[directoryEntry.name] = directoryEntry;
    });
    return entries;
  };

Menggunakan sistem file pribadi origin di Web Worker

Seperti yang diuraikan sebelumnya, Web Worker tidak dapat memblokir thread utama. Itulah sebabnya, metode sinkron dalam konteks ini diizinkan.

Mendapatkan handle akses sinkron

Titik entri ke operasi file tercepat adalah FileSystemSyncAccessHandle, yang diperoleh dari FileSystemFileHandle reguler dengan memanggil createSyncAccessHandle().

const fileHandle = await opfsRoot
    .getFileHandle('my highspeed file.txt', {create: true});
const syncAccessHandle = await fileHandle.createSyncAccessHandle();

Metode file di tempat sinkron

Setelah Anda memiliki handle akses sinkron, Anda mendapatkan akses ke metode file yang langsung di tempat dan semuanya sinkron.

  • getSize(): Menampilkan ukuran file dalam byte.
  • write(): Menulis konten buffer ke dalam file, secara opsional pada offset tertentu, dan menampilkan jumlah byte tertulis. Memeriksa jumlah byte tertulis yang ditampilkan memungkinkan pemanggil mendeteksi dan menangani error dan penulisan parsial.
  • read(): Membaca konten file ke buffer, secara opsional pada offset tertentu.
  • truncate(): Mengubah ukuran file ke ukuran yang ditentukan.
  • flush(): Memastikan konten file berisi semua perubahan yang dilakukan melalui write().
  • close(): Menutup tuas akses.

Berikut ini contoh yang menggunakan semua metode yang disebutkan di atas.

const opfsRoot = await navigator.storage.getDirectory();
const fileHandle = await opfsRoot.getFileHandle('fast', {create: true});
const accessHandle = await fileHandle.createSyncAccessHandle();

const textEncoder = new TextEncoder();
const textDecoder = new TextDecoder();

// Initialize this variable for the size of the file.
let size;
// The current size of the file, initially `0`.
size = accessHandle.getSize();
// Encode content to write to the file.
const content = textEncoder.encode('Some text');
// Write the content at the beginning of the file.
accessHandle.write(content, {at: size});
// Flush the changes.
accessHandle.flush();
// The current size of the file, now `9` (the length of "Some text").
size = accessHandle.getSize();

// Encode more content to write to the file.
const moreContent = textEncoder.encode('More content');
// Write the content at the end of the file.
accessHandle.write(moreContent, {at: size});
// Flush the changes.
accessHandle.flush();
// The current size of the file, now `21` (the length of
// "Some textMore content").
size = accessHandle.getSize();

// Prepare a data view of the length of the file.
const dataView = new DataView(new ArrayBuffer(size));

// Read the entire file into the data view.
accessHandle.read(dataView);
// Logs `"Some textMore content"`.
console.log(textDecoder.decode(dataView));

// Read starting at offset 9 into the data view.
accessHandle.read(dataView, {at: 9});
// Logs `"More content"`.
console.log(textDecoder.decode(dataView));

// Truncate the file after 4 bytes.
accessHandle.truncate(4);

Menyalin file dari sistem file pribadi origin ke sistem file yang terlihat oleh pengguna

Seperti disebutkan di atas, memindahkan file dari sistem file pribadi asal ke sistem file yang terlihat oleh pengguna tidak mungkin dilakukan, tetapi Anda dapat menyalin file. Karena showSaveFilePicker() hanya ditampilkan pada thread utama, tetapi tidak dalam thread Pekerja, pastikan untuk menjalankan kode di sana.

// On the main thread, not in the Worker. This assumes
// `fileHandle` is the `FileSystemFileHandle` you obtained
// the `FileSystemSyncAccessHandle` from in the Worker
// thread. Be sure to close the file in the Worker thread first.
const fileHandle = await opfsRoot.getFileHandle('fast');
try {
  // Obtain a file handle to a new file in the user-visible file system
  // with the same name as the file in the origin private file system.
  const saveHandle = await showSaveFilePicker({
    suggestedName: fileHandle.name || ''
  });
  const writable = await saveHandle.createWritable();
  await writable.write(await fileHandle.getFile());
  await writable.close();
} catch (err) {
  console.error(err.name, err.message);
}

Men-debug sistem file pribadi origin

Sampai dukungan DevTools bawaan ditambahkan (lihat crbug/1284595), gunakan ekstensi Chrome OPFS Explorer untuk men-debug sistem file pribadi origin. Screenshot di atas dari bagian Membuat file dan folder baru diambil langsung dari ekstensi.

Ekstensi Chrome DevTools OPFS Explorer di Chrome Web Store.

Setelah menginstal ekstensi, buka Chrome DevTools, pilih tab OPFS Explorer, lalu Anda siap untuk memeriksa hierarki file. Simpan file dari sistem file pribadi origin ke sistem file yang terlihat oleh pengguna dengan mengklik nama file serta hapus file dan folder dengan mengklik ikon tempat sampah.

Demo

Lihat cara kerja sistem file pribadi origin (jika Anda menginstal ekstensi OPFS Explorer) dalam demo yang menggunakannya sebagai backend untuk database SQLite yang dikompilasi ke WebAssembly. Pastikan untuk melihat kode sumber di Glitch. Perlu diperhatikan bahwa versi yang disematkan di bawah tidak menggunakan backend sistem file pribadi origin (karena iframe bersifat lintas origin), tetapi saat Anda membuka demo di tab terpisah, hal tersebut akan digunakan.

Kesimpulan

Sistem file pribadi asal, seperti yang ditentukan oleh HOWWG, telah membentuk cara kita menggunakan dan berinteraksi dengan file di web. Alat ini memungkinkan kasus penggunaan baru yang tidak mungkin dicapai dengan sistem file yang terlihat oleh pengguna. Semua vendor browser utama—Apple, Mozilla, dan Google—sudah bergabung dan berbagi visi bersama. Pengembangan sistem file pribadi origin merupakan upaya kolaboratif, dan masukan dari developer serta pengguna sangat penting untuk progresnya. Seiring kami terus meningkatkan kualitas dan meningkatkan standar ini, kami mengharapkan masukan terkait repositori whatwg/fs dalam bentuk Masalah atau Permintaan Pull.

Ucapan terima kasih

Artikel ini diulas oleh Austin Sully, Etienne Noël, dan Rachel Andrew. Banner besar oleh Christina Rumpf di Unsplash.