ระบบไฟล์ส่วนตัวต้นทาง

มาตรฐานระบบไฟล์ได้เปิดตัวระบบไฟล์ส่วนตัวของต้นทาง (OPFS) เป็นปลายทางการจัดเก็บข้อมูลที่ส่วนตัวสำหรับต้นทางของหน้าเว็บ และผู้ใช้จะมองไม่เห็น ซึ่งจะให้สิทธิ์เข้าถึงไฟล์พิเศษที่ได้รับการเพิ่มประสิทธิภาพอย่างมากเพื่อประสิทธิภาพ

การสนับสนุนเบราว์เซอร์

เบราว์เซอร์สมัยใหม่รองรับระบบไฟล์ส่วนตัวของต้นทาง และกลุ่มการทำงานด้านเทคโนโลยีแอปพลิเคชันไฮเปอร์เท็กซ์บนเว็บ (WHATWG) ได้กำหนดมาตรฐานไว้ในมาตรฐานระบบไฟล์

Browser Support

  • Chrome: 86.
  • Edge: 86.
  • Firefox: 111.
  • Safari: 15.2.

Source

แรงจูงใจ

เมื่อนึกถึงไฟล์ในคอมพิวเตอร์ คุณอาจนึกถึงลำดับชั้นของไฟล์ ซึ่งก็คือไฟล์ที่จัดระเบียบไว้ในโฟลเดอร์ที่คุณสำรวจได้ด้วย File Explorer ของระบบปฏิบัติการ เช่น ใน Windows สำหรับผู้ใช้ชื่อ Tom รายการสิ่งที่ต้องทำอาจอยู่ใน C:\Users\Tom\Documents\ToDo.txt ในตัวอย่างนี้ ToDo.txt คือชื่อไฟล์ และ Users, Tom และ Documents คือชื่อโฟลเดอร์ `C:` ใน Windows แสดงถึงไดเรกทอรีรากของไดรฟ์

วิธีดั้งเดิมในการทำงานกับไฟล์บนเว็บ

หากต้องการแก้ไขรายการสิ่งที่ต้องทำในเว็บแอปพลิเคชัน ให้ทำตามขั้นตอนปกติ ดังนี้

  1. ผู้ใช้อัปโหลดไฟล์ไปยังเซิร์ฟเวอร์หรือเปิดไฟล์ในไคลเอ็นต์ด้วย <input type="file">
  2. ผู้ใช้ทำการเปลี่ยนแปลง แล้วดาวน์โหลดไฟล์ผลลัพธ์ที่มี <a download="ToDo.txt> ที่คุณตั้งโปรแกรมให้ click() ผ่าน JavaScript
  3. สำหรับการเปิดโฟลเดอร์ คุณต้องใช้แอตทริบิวต์พิเศษใน <input type="file" webkitdirectory> ซึ่งแม้จะมีชื่อที่เป็นกรรมสิทธิ์ แต่ก็ได้รับการรองรับจากเบราว์เซอร์แทบทั้งหมด

วิธีที่ทันสมัยในการทำงานกับไฟล์บนเว็บ

โฟลว์นี้ไม่ได้แสดงถึงวิธีที่ผู้ใช้คิดเกี่ยวกับการแก้ไขไฟล์ และหมายความว่าผู้ใช้จะได้รับสำเนาของไฟล์อินพุตที่ดาวน์โหลด ดังนั้น File System Access API จึงได้เปิดตัวเมธอดตัวเลือก 3 รายการ ได้แก่ showOpenFilePicker(), showSaveFilePicker() และ showDirectoryPicker() ซึ่งทำงานตรงตามชื่อของเมธอด โดยจะเปิดใช้โฟลว์ดังนี้

  1. เปิด ToDo.txt ด้วย showOpenFilePicker() แล้วรับออบเจ็กต์ FileSystemFileHandle
  2. จากออบเจ็กต์ FileSystemFileHandle ให้รับ File โดยเรียกใช้เมธอด getFile() ของแฮนเดิลไฟล์
  3. แก้ไขไฟล์ แล้วเรียกใช้ requestPermission({mode: 'readwrite'}) ในแฮนเดิล
  4. หากผู้ใช้ยอมรับคำขอสิทธิ์ ให้บันทึกการเปลี่ยนแปลงกลับไปยังไฟล์ต้นฉบับ
  5. หรือจะเรียกใช้ showSaveFilePicker() แล้วให้ผู้ใช้เลือกไฟล์ใหม่ก็ได้ (หากผู้ใช้เลือกไฟล์ที่เปิดก่อนหน้านี้ ระบบจะเขียนทับเนื้อหาของไฟล์) สำหรับการบันทึกซ้ำ คุณสามารถเก็บแฮนเดิลไฟล์ไว้ได้เพื่อไม่ต้องแสดงกล่องโต้ตอบการบันทึกไฟล์อีกครั้ง

ข้อจำกัดในการทำงานกับไฟล์บนเว็บ

ไฟล์และโฟลเดอร์ที่เข้าถึงได้ผ่านวิธีการเหล่านี้จะอยู่ในระบบไฟล์ที่เรียกว่าผู้ใช้มองเห็น ระบบจะทำเครื่องหมายไฟล์ที่บันทึกจากเว็บและไฟล์ที่เรียกใช้งานได้โดยเฉพาะด้วยเครื่องหมายของเว็บ เพื่อให้มีคำเตือนเพิ่มเติมที่ระบบปฏิบัติการแสดงได้ก่อนที่จะมีการเรียกใช้ไฟล์ที่อาจเป็นอันตราย ไฟล์ที่ได้จากเว็บจะได้รับการปกป้องโดย Safe Browsing ซึ่งเป็นฟีเจอร์ความปลอดภัยเพิ่มเติม โดยคุณสามารถคิดว่าฟีเจอร์นี้เป็นการสแกนไวรัสบนระบบคลาวด์เพื่อให้เข้าใจได้ง่ายและสอดคล้องกับบริบทของบทความนี้ เมื่อเขียนข้อมูลลงในไฟล์โดยใช้ File System Access API การเขียนจะไม่เกิดขึ้นในตำแหน่งเดิม แต่จะใช้ไฟล์ชั่วคราว ระบบจะไม่แก้ไขตัวไฟล์เอง เว้นแต่ไฟล์จะผ่านการตรวจสอบความปลอดภัยทั้งหมดนี้ ดังที่ทราบกันดีว่าการดำเนินการนี้ทำให้การดำเนินการกับไฟล์ค่อนข้างช้า แม้ว่าจะมีการปรับปรุงในกรณีที่ทำได้ เช่น ใน macOS อย่างไรก็ตาม การเรียกใช้ write() ทุกครั้งจะอยู่ในตัวเอง ดังนั้นเบื้องหลังจึงเปิดไฟล์ ค้นหาออฟเซ็ตที่ระบุ และเขียนข้อมูลในที่สุด

ไฟล์เป็นพื้นฐานของการประมวลผล

ในขณะเดียวกัน ไฟล์ก็เป็นวิธีที่ยอดเยี่ยมในการบันทึกข้อมูล เช่น SQLite จะจัดเก็บฐานข้อมูลทั้งหมดไว้ในไฟล์เดียว อีกตัวอย่างหนึ่งคือ Mipmap ที่ใช้ในการประมวลผลรูปภาพ Mipmap คือลำดับของรูปภาพที่ได้รับการคำนวณล่วงหน้าและเพิ่มประสิทธิภาพแล้ว โดยแต่ละรูปภาพจะมีความละเอียดต่ำลงเรื่อยๆ จากรูปภาพก่อนหน้า ซึ่งทำให้การดำเนินการหลายอย่าง เช่น การซูม เร็วขึ้น แล้วเว็บแอปพลิเคชันจะได้รับประโยชน์จากไฟล์ได้อย่างไรโดยไม่ต้องเสียค่าใช้จ่ายด้านประสิทธิภาพในการประมวลผลไฟล์บนเว็บ คำตอบคือระบบไฟล์ส่วนตัวของต้นทาง

ระบบไฟล์ส่วนตัวของต้นทางเทียบกับระบบไฟล์ที่ผู้ใช้มองเห็นได้

ระบบไฟล์ส่วนตัวของต้นทางไม่ได้มีไว้ให้ผู้ใช้เห็น ซึ่งแตกต่างจากระบบไฟล์ที่ผู้ใช้มองเห็นซึ่งเรียกดูได้โดยใช้ File Explorer ของระบบปฏิบัติการ พร้อมทั้งไฟล์และโฟลเดอร์ที่คุณอ่าน เขียน ย้าย และเปลี่ยนชื่อได้ ไฟล์และโฟลเดอร์ในระบบไฟล์ส่วนตัวของต้นทางจะมีความเป็นส่วนตัวตามชื่อ และมีความเป็นส่วนตัวสำหรับต้นทางของเว็บไซต์ ค้นหาต้นทางของหน้าเว็บโดยพิมพ์ location.origin ในคอนโซล DevTools ตัวอย่างเช่น ต้นทางของหน้า https://developer.chrome.com/articles/ คือ https://developer.chrome.com (นั่นคือส่วน /articles ไม่ใช่ส่วนหนึ่งของต้นทาง) คุณอ่านเพิ่มเติมเกี่ยวกับทฤษฎีของต้นทางได้ในทำความเข้าใจ "เว็บไซต์เดียวกัน" และ "ต้นทางเดียวกัน" หน้าเว็บทั้งหมดที่แชร์ต้นทางเดียวกันจะดูข้อมูลระบบไฟล์ส่วนตัวของต้นทางเดียวกันได้ ดังนั้น https://developer.chrome.com/docs/extensions/mv3/getstarted/extensions-101/ จะดูรายละเอียดเดียวกันกับตัวอย่างก่อนหน้าได้ แต่ละต้นทางจะมีระบบไฟล์ส่วนตัวของต้นทางที่เป็นอิสระของตนเอง ซึ่งหมายความว่าระบบไฟล์ส่วนตัวของต้นทางของ https://developer.chrome.com จะแตกต่างจากระบบไฟล์ส่วนตัวของต้นทางของ https://web.dev โดยสิ้นเชิง ใน Windows ไดเรกทอรีรูทของระบบไฟล์ที่ผู้ใช้มองเห็นคือ C:\\ สำหรับระบบไฟล์ส่วนตัวของต้นทางเทียบเท่าคือไดเรกทอรีรูทที่ว่างเปล่าในตอนแรกต่อต้นทางที่เข้าถึงโดยการเรียกใช้เมธอดแบบอะซิงโครนัส navigator.storage.getDirectory() ดูการเปรียบเทียบระบบไฟล์ที่ผู้ใช้มองเห็นและระบบไฟล์ส่วนตัวของแหล่งที่มาได้ในแผนภาพต่อไปนี้ แผนภาพแสดงให้เห็นว่านอกเหนือจากไดเรกทอรีรากแล้ว ทุกอย่างจะเหมือนกันในเชิงแนวคิด โดยมีลำดับชั้นของไฟล์และโฟลเดอร์เพื่อจัดระเบียบและจัดเรียงตามความจำเป็นสำหรับความต้องการด้านข้อมูลและการจัดเก็บ

แผนภาพระบบไฟล์ที่ผู้ใช้มองเห็นและระบบไฟล์ส่วนตัวของต้นทางที่มีลำดับชั้นของไฟล์ตัวอย่าง 2 รายการ จุดแรกเข้าสำหรับระบบไฟล์ที่ผู้ใช้มองเห็นคือฮาร์ดดิสก์เชิงสัญลักษณ์ จุดแรกเข้าสำหรับระบบไฟล์ส่วนตัวของต้นทางคือการเรียกใช้เมธอด &quot;navigator.storage.getDirectory&quot;

รายละเอียดของระบบไฟล์ส่วนตัวของต้นทาง

เช่นเดียวกับกลไกการจัดเก็บข้อมูลอื่นๆ ในเบราว์เซอร์ (เช่น localStorage หรือ IndexedDB) ระบบไฟล์ส่วนตัวของต้นทางจะอยู่ภายใต้ข้อจํากัดโควต้าของเบราว์เซอร์ เมื่อผู้ใช้ล้างข้อมูลการท่องเว็บทั้งหมดหรือข้อมูลเว็บไซต์ทั้งหมด ระบบจะลบระบบไฟล์ส่วนตัวของต้นทางด้วย เรียกใช้ navigator.storage.estimate() และในออบเจ็กต์การตอบกลับที่ได้ ให้ดูรายการ usage เพื่อดูว่าแอปใช้พื้นที่เก็บข้อมูลไปแล้วเท่าใด ซึ่งจะแบ่งตามกลไกการจัดเก็บในออบเจ็กต์ usageDetails โดยคุณจะต้องดูรายการ fileSystem โดยเฉพาะ เนื่องจากผู้ใช้มองไม่เห็นระบบไฟล์ส่วนตัวของต้นทาง จึงไม่มีข้อความแจ้งขอสิทธิ์และไม่มีการตรวจสอบ Google Safe Browsing

การขอสิทธิ์เข้าถึงไดเรกทอรีราก

หากต้องการเข้าถึงไดเรกทอรีราก ให้เรียกใช้คำสั่งต่อไปนี้ คุณจะได้รับแฮนเดิลไดเรกทอรีที่ว่างเปล่า หรือกล่าวอย่างเจาะจงคือ FileSystemDirectoryHandle

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

เทรดหลักหรือ Web Worker

การใช้ระบบไฟล์ส่วนตัวของต้นทางทำได้ 2 วิธี ได้แก่ ในเธรดหลักหรือใน Web Worker Web Worker จะบล็อกเทรดหลักไม่ได้ ซึ่งหมายความว่าในบริบทนี้ API จะซิงโครนัสได้ ซึ่งเป็นรูปแบบที่โดยทั่วไปแล้วไม่อนุญาตในเทรดหลัก API แบบซิงโครนัสอาจเร็วกว่าเนื่องจากไม่ต้องจัดการกับ Promise และโดยปกติแล้วการดำเนินการกับไฟล์จะเป็นแบบซิงโครนัสในภาษาต่างๆ เช่น C ซึ่งคอมไพล์เป็น WebAssembly ได้

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

หากต้องการดำเนินการกับไฟล์ให้เร็วที่สุดเท่าที่จะเป็นไปได้ หรือหากคุณใช้ WebAssembly ให้ข้ามไปที่ใช้ระบบไฟล์ส่วนตัวของต้นทางใน Web Worker หรือจะอ่านต่อก็ได้

ใช้ระบบไฟล์ส่วนตัวของต้นทางในเทรดหลัก

สร้างไฟล์และโฟลเดอร์ใหม่

เมื่อมีโฟลเดอร์รูทแล้ว ให้สร้างไฟล์และโฟลเดอร์โดยใช้เมธอด getFileHandle() และ getDirectoryHandle() ตามลำดับ หากส่ง {create: true} ระบบจะสร้างไฟล์หรือโฟลเดอร์ให้หากยังไม่มี สร้างลำดับชั้นของไฟล์โดยเรียกใช้ฟังก์ชันเหล่านี้โดยใช้ไดเรกทอรีที่สร้างขึ้นใหม่เป็นจุดเริ่มต้น

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

ลำดับชั้นของไฟล์ที่ได้จากตัวอย่างโค้ดก่อนหน้า

เข้าถึงไฟล์และโฟลเดอร์ที่มีอยู่

หากทราบชื่อ ให้เข้าถึงไฟล์และโฟลเดอร์ที่สร้างไว้ก่อนหน้านี้โดยเรียกใช้เมธอด getFileHandle() หรือ getDirectoryHandle() แล้วส่งชื่อไฟล์หรือโฟลเดอร์

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

รับไฟล์ที่เชื่อมโยงกับแฮนเดิลไฟล์สำหรับการอ่าน

FileSystemFileHandle แสดงถึงไฟล์ในระบบไฟล์ หากต้องการรับ File ที่เชื่อมโยง ให้ใช้วิธี getFile() ออบเจ็กต์ File เป็นBlob ประเภทหนึ่ง และใช้ได้ในทุกบริบทที่ Blob ใช้ได้ โดยเฉพาะอย่างยิ่ง FileReader, URL.createObjectURL(), createImageBitmap() และ XMLHttpRequest.send() ยอมรับทั้ง Blobs และ Files หากคุณได้รับ File จาก FileSystemFileHandle จะเป็นการ "ปลด" ข้อมูลเพื่อให้คุณเข้าถึงและทำให้ข้อมูลพร้อมใช้งานในระบบไฟล์ที่ผู้ใช้มองเห็นได้

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

เขียนไปยังไฟล์โดยการสตรีม

สตรีมข้อมูลลงในไฟล์โดยเรียกใช้ createWritable() ซึ่งจะสร้าง FileSystemWritableFileStream จากนั้นคุณจะ write() เนื้อหา เมื่อสิ้นสุดแล้ว คุณต้องclose()สตรีม

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

ลบไฟล์และโฟลเดอร์

ลบไฟล์และโฟลเดอร์โดยเรียกใช้เมธอด remove() ที่เฉพาะเจาะจงของแฮนเดิลไฟล์หรือไดเรกทอรี หากต้องการลบโฟลเดอร์รวมถึงโฟลเดอร์ย่อยทั้งหมด ให้ส่งตัวเลือก {recursive: true}

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

อีกวิธีหนึ่งคือหากคุณทราบชื่อไฟล์หรือโฟลเดอร์ที่จะลบในไดเรกทอรี ให้ใช้วิธี removeEntry()

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

การย้ายและเปลี่ยนชื่อไฟล์และโฟลเดอร์

เปลี่ยนชื่อและย้ายไฟล์และโฟลเดอร์โดยใช้เมธอด move() การย้ายและการเปลี่ยนชื่ออาจเกิดขึ้นพร้อมกันหรือแยกกันก็ได้

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

แก้ไขเส้นทางของไฟล์หรือโฟลเดอร์

หากต้องการทราบตำแหน่งของไฟล์หรือโฟลเดอร์ที่กำหนดเมื่อเทียบกับไดเรกทอรีอ้างอิง ให้ใช้วิธี resolve() โดยส่ง FileSystemHandle เป็นอาร์กิวเมนต์ หากต้องการรับเส้นทางแบบเต็มของไฟล์หรือโฟลเดอร์ในระบบไฟล์ส่วนตัวของต้นทาง ให้ใช้ไดเรกทอรีรากเป็นไดเรกทอรีอ้างอิงที่ได้รับผ่าน navigator.storage.getDirectory()

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

ตรวจสอบว่าแฮนเดิลของไฟล์หรือโฟลเดอร์ 2 รายการชี้ไปยังไฟล์หรือโฟลเดอร์เดียวกันหรือไม่

บางครั้งคุณอาจมีแฮนเดิล 2 รายการและไม่ทราบว่าแฮนเดิลทั้ง 2 รายการชี้ไปยังไฟล์หรือโฟลเดอร์เดียวกันหรือไม่ หากต้องการตรวจสอบว่ากรณีดังกล่าวเกิดขึ้นหรือไม่ ให้ใช้วิธีisSameEntry()

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

แสดงรายการเนื้อหาของโฟลเดอร์

FileSystemDirectoryHandle คือตัววนซ้ำแบบอะซิงโครนัสที่คุณวนซ้ำด้วยลูป for await…of ในฐานะเครื่องวนซ้ำแบบไม่พร้อมกัน เครื่องมือนี้ยังรองรับเมธอด entries(), values() และ keys() ซึ่งคุณเลือกใช้ได้ตามข้อมูลที่ต้องการ

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()) {}

แสดงเนื้อหาของโฟลเดอร์และโฟลเดอร์ย่อยทั้งหมดแบบเรียกซ้ำ

การจัดการกับลูปและฟังก์ชันแบบไม่พร้อมกันที่จับคู่กับการเรียกซ้ำนั้นทำได้ง่าย ฟังก์ชันด้านล่างนี้สามารถใช้เป็นจุดเริ่มต้นในการแสดงเนื้อหาของโฟลเดอร์และโฟลเดอร์ย่อยทั้งหมด รวมถึงไฟล์ทั้งหมดและขนาดของไฟล์ คุณสามารถทำให้ฟังก์ชันง่ายขึ้นได้หากไม่ต้องการขนาดไฟล์ โดยไม่ต้องส่งพรอมิส handle.getFile() แต่ส่ง handle โดยตรงแทนdirectoryEntryPromises.push

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

ใช้ระบบไฟล์ส่วนตัวของต้นทางใน Web Worker

ดังที่กล่าวไว้ก่อนหน้านี้ Web Worker จะบล็อกเทรดหลักไม่ได้ จึงอนุญาตให้ใช้วิธีการแบบซิงโครนัสในบริบทนี้

การรับแฮนเดิลการเข้าถึงแบบซิงโครนัส

จุดแรกเข้าสำหรับการดำเนินการกับไฟล์ที่เร็วที่สุดเท่าที่จะเป็นไปได้คือ FileSystemSyncAccessHandle ซึ่งได้มาจาก FileSystemFileHandle ปกติโดยการเรียกใช้ createSyncAccessHandle()

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

วิธีการไฟล์แบบซิงโครนัสในตำแหน่ง

เมื่อมีแฮนเดิลการเข้าถึงแบบซิงโครนัส คุณจะได้รับสิทธิ์เข้าถึงเมธอดไฟล์แบบแทนที่ที่รวดเร็วซึ่งเป็นแบบซิงโครนัสทั้งหมด

  • getSize(): แสดงผลขนาดของไฟล์ในหน่วยไบต์
  • write(): เขียนเนื้อหาของบัฟเฟอร์ลงในไฟล์ โดยอาจเขียนที่ออฟเซ็ตที่ระบุ และแสดงผลจำนวนไบต์ที่เขียน การตรวจสอบจำนวนไบต์ที่เขียนแล้วซึ่งส่งคืนจะช่วยให้ผู้โทรตรวจหาและจัดการข้อผิดพลาดและการเขียนบางส่วนได้
  • read(): อ่านเนื้อหาของไฟล์ลงในบัฟเฟอร์ โดยอาจระบุออฟเซ็ตที่ต้องการหรือไม่ก็ได้
  • truncate(): ปรับขนาดไฟล์เป็นขนาดที่ระบุ
  • flush(): ตรวจสอบว่าเนื้อหาของไฟล์มีการแก้ไขทั้งหมดที่ดำเนินการผ่าน write()
  • close(): ปิดแฮนเดิลการเข้าถึง

ต่อไปนี้เป็นตัวอย่างที่ใช้วิธีการทั้งหมดที่กล่าวถึงข้างต้น

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

คัดลอกไฟล์จากระบบไฟล์ส่วนตัวต้นทางไปยังระบบไฟล์ที่ผู้ใช้มองเห็น

ดังที่กล่าวไว้ข้างต้น คุณจะย้ายไฟล์จากระบบไฟล์ส่วนตัวต้นทางไปยังระบบไฟล์ที่ผู้ใช้มองเห็นไม่ได้ แต่สามารถคัดลอกไฟล์ได้ เนื่องจาก showSaveFilePicker() จะแสดงในเทรดหลักเท่านั้น แต่จะไม่แสดงในเทรด Worker คุณจึงต้องเรียกใช้โค้ดในเทรดหลัก

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

แก้ไขข้อบกพร่องของระบบไฟล์ส่วนตัวของต้นทาง

จนกว่าจะมีการเพิ่มการรองรับเครื่องมือสำหรับนักพัฒนาเว็บในตัว (ดู crbug/1284595) ให้ใช้ส่วนขยาย Chrome OPFS Explorer เพื่อแก้ไขข้อบกพร่องของระบบไฟล์ส่วนตัวของต้นทาง ภาพหน้าจอด้านบนจากส่วนการสร้างไฟล์และโฟลเดอร์ใหม่มาจากส่วนขยายโดยตรง

ส่วนขยาย OPFS Explorer สำหรับเครื่องมือสำหรับนักพัฒนาเว็บใน Chrome ใน Chrome เว็บสโตร์

หลังจากติดตั้งส่วนขยายแล้ว ให้เปิดเครื่องมือสำหรับนักพัฒนาเว็บใน Chrome เลือกแท็บ OPFS Explorer จากนั้นคุณก็พร้อมที่จะตรวจสอบลำดับชั้นของไฟล์ บันทึกไฟล์จากระบบไฟล์ส่วนตัวของต้นทางไปยังระบบไฟล์ที่ผู้ใช้มองเห็นได้โดยคลิกชื่อไฟล์ และลบไฟล์และโฟลเดอร์โดยคลิกไอคอนถังขยะ

สาธิต

ดูระบบไฟล์ส่วนตัวของต้นทางในการทำงาน (หากคุณติดตั้งส่วนขยาย OPFS Explorer) ในการสาธิตที่ใช้เป็นแบ็กเอนด์สำหรับฐานข้อมูล SQLite ที่คอมไพล์เป็น WebAssembly อย่าลืมดูซอร์สโค้ดใน Glitch โปรดสังเกตว่าเวอร์ชันที่ฝังด้านล่างไม่ได้ใช้แบ็กเอนด์ระบบไฟล์ส่วนตัวของต้นทาง (เนื่องจาก iframe เป็นแบบข้ามต้นทาง) แต่เมื่อคุณเปิดเดโมในแท็บแยกต่างหาก ระบบจะใช้แบ็กเอนด์ดังกล่าว

บทสรุป

ระบบไฟล์ส่วนตัวของต้นทางตามที่ WHATWG ระบุได้กำหนดวิธีที่เราใช้และโต้ตอบกับไฟล์บนเว็บ ซึ่งทำให้เกิด Use Case ใหม่ๆ ที่ไม่สามารถทำได้ด้วยระบบไฟล์ที่ผู้ใช้มองเห็น ผู้ให้บริการเบราว์เซอร์รายใหญ่ทั้งหมด ได้แก่ Apple, Mozilla และ Google ต่างก็เข้าร่วมและมีวิสัยทัศน์ร่วมกัน การพัฒนา Origin Private File System เป็นความพยายามร่วมกันอย่างมาก และความคิดเห็นจากนักพัฒนาซอฟต์แวร์และผู้ใช้เป็นสิ่งสำคัญต่อความคืบหน้า ในขณะที่เราปรับแต่งและปรับปรุงมาตรฐานอย่างต่อเนื่อง เรายินดีรับฟังความคิดเห็นเกี่ยวกับที่เก็บ whatwg/fs ในรูปแบบของปัญหาหรือคำขอผสานรวม

การรับทราบ

บทความนี้ได้รับการตรวจสอบโดย Austin Sully, Etienne Noël และ Rachel Andrew รูปภาพหลักโดย Christina Rumpf ใน Unsplash