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

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

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

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

การรองรับเบราว์เซอร์

  • Chrome: 86
  • ขอบ: 86
  • Firefox: 111.
  • Safari: 15.2

แหล่งที่มา

แรงจูงใจ

เมื่อนึกถึงไฟล์ในคอมพิวเตอร์ คุณอาจนึกถึงลำดับชั้นของไฟล์ ซึ่งเป็นไฟล์ที่จัดอยู่ในโฟลเดอร์ ซึ่งคุณสามารถใช้สำรวจไฟล์ของระบบปฏิบัติการ ตัวอย่างเช่น ใน Windows สำหรับผู้ใช้ชื่อต้อม รายการสิ่งที่ต้องทำของผู้ใช้อาจอยู่ใน 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() แล้วให้ผู้ใช้เลือกไฟล์ใหม่ (หากผู้ใช้เลือกไฟล์ที่เปิดก่อนหน้านี้ เนื้อหาในไฟล์จะถูกเขียนทับ) สำหรับการบันทึกซ้ำ คุณสามารถเก็บไว้ที่แฮนเดิลไฟล์ จะได้ไม่ต้องแสดงกล่องโต้ตอบการบันทึกไฟล์อีกครั้ง

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

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

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

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

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

ระบบไฟล์ส่วนตัวที่ผู้ใช้มองเห็นได้ซึ่งเรียกดูโดยใช้โปรแกรมสำรวจไฟล์ของระบบปฏิบัติการ ตรงที่มีไฟล์และโฟลเดอร์ที่คุณสามารถอ่าน เขียน ย้าย และเปลี่ยนชื่อ ระบบไฟล์ส่วนตัวต้นทางไม่ได้แสดงต่อผู้ใช้ ไฟล์และโฟลเดอร์ในระบบไฟล์ส่วนตัวแบบต้นทางจะเป็นส่วนตัวและเป็นรูปธรรมมากขึ้น เป็นส่วนตัวสำหรับต้นทางของเว็บไซต์ ค้นพบต้นทางของหน้าโดยพิมพ์ location.origin ในคอนโซลเครื่องมือสำหรับนักพัฒนาเว็บ ตัวอย่างเช่น ต้นทางของหน้า 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 Workers ไม่สามารถบล็อกเทรดหลักได้ ซึ่งหมายความว่า API ของบริบทนี้เป็นแบบซิงโครนัสได้ ซึ่งเป็นรูปแบบที่มักจะไม่ได้รับอนุญาตบนเทรดหลัก API แบบซิงโครนัสจะทำงานเร็วกว่าเนื่องจากไม่ต้องจัดการกับคำสัญญา และโดยปกติแล้วการดำเนินการเกี่ยวกับไฟล์จะเป็นแบบพร้อมกันในภาษาต่างๆ เช่น 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 ตัว จึงไม่ทราบว่าแฮนเดิลเหล่านั้นชี้ไปที่ไฟล์หรือโฟลเดอร์เดียวกันหรือไม่ หากต้องการตรวจสอบว่าเป็นกรณีนี้หรือไม่ ให้ใช้เมธอด 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()) {}

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

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

  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 Workers จะบล็อกเทรดหลักไม่ได้ ซึ่งเป็นเหตุผลที่อนุญาตให้ใช้เมธอดแบบซิงโครนัสในบริบทนี้

การรับแฮนเดิลการเข้าถึงพร้อมกัน

จุดแรกเข้าไปสู่การดำเนินการกับไฟล์ที่เร็วที่สุดคือ 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() จะปรากฏเฉพาะในเทรดหลัก แต่ไม่แสดงในเทรดผู้ปฏิบัติงาน คุณจึงต้องเรียกใช้โค้ดที่นั่น

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

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

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

ส่วนขยาย OPFS Explorer สำหรับ Chrome DevTools ใน Chrome เว็บสโตร์

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

สาธิต

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

บทสรุป

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

กิตติกรรมประกาศ

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