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

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

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

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

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

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

แหล่งที่มา

แรงจูงใจ

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

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

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

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

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

FileSystemDirectoryHandle คือตัวดำเนินการแบบไม่พร้อมกันที่คุณใช้วนซ้ำด้วยลูป for await…of ในฐานะตัวดำเนินการแบบไม่สอดคล้องกัน Iterable นี้ยังรองรับเมธอด 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 จะไม่ส่ง Promise ของ 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

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

การรับตัวแฮนเดิลการเข้าถึงแบบซิงค์

จุดแรกเข้าสําหรับการดำเนินการกับไฟล์ที่เร็วที่สุดคือ 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);
}

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

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

ส่วนขยายเครื่องมือสำหรับนักพัฒนาเว็บใน Chrome ของ OPFS Explorer ใน 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