File System Access API: ลดความซับซ้อนในการเข้าถึงไฟล์ในเครื่อง

File System Access API จะอนุญาตให้เว็บแอปอ่านหรือบันทึกการเปลี่ยนแปลงไปยังไฟล์และโฟลเดอร์ในอุปกรณ์ของผู้ใช้ได้โดยตรง

File System Access API คืออะไร

File System Access API (ก่อนหน้านี้เรียกว่า Native File System API และก่อนหน้านี้เรียกว่า Writeable Files API) ช่วยให้นักพัฒนาซอฟต์แวร์สร้างเว็บแอปที่มีประสิทธิภาพซึ่งโต้ตอบกับไฟล์ในอุปกรณ์ของผู้ใช้ได้ เช่น IDE, โปรแกรมแก้ไขรูปภาพและวิดีโอ, เครื่องมือแก้ไขข้อความ และอื่นๆ หลังจากที่ผู้ใช้ให้สิทธิ์เข้าถึงเว็บแอปแล้ว API นี้จะอนุญาตให้ผู้ใช้อ่านหรือบันทึกการเปลี่ยนแปลงไปยังไฟล์และโฟลเดอร์ในอุปกรณ์ของผู้ใช้ได้โดยตรง นอกจากการอ่านและเขียนไฟล์แล้ว File System Access API ยังสามารถเปิดไดเรกทอรีและแจกแจงเนื้อหาได้ด้วย

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

ปัจจุบัน File System Access API ใช้งานได้บนเบราว์เซอร์ Chromium ส่วนใหญ่ใน Windows, macOS, ChromeOS และ Linux ข้อยกเว้นที่สำคัญคือ Brave ซึ่งปัจจุบันมีให้บริการหลัง Flag เท่านั้น Android รองรับส่วนระบบไฟล์ส่วนตัวต้นทางของ API ใน Chromium 109 ขณะนี้ยังไม่มีแผนสำหรับวิธีการเลือก แต่คุณสามารถติดตามความคืบหน้าที่เป็นไปได้ได้ด้วยการติดดาวที่ crbug.com/1011535

การใช้ File System Access API

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

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

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

  • 86
  • 86
  • x
  • x

แหล่งที่มา

ลองเลย

ดูการทำงานของ File System Access API ในการสาธิตเครื่องมือแก้ไขข้อความ

อ่านไฟล์จากระบบไฟล์ในเครื่อง

กรณีการใช้งานแรกที่ผมแก้ปัญหาคือการขอให้ผู้ใช้เลือกไฟล์ จากนั้นเปิดและอ่านไฟล์นั้นจากดิสก์

ขอให้ผู้ใช้เลือกไฟล์ที่จะอ่าน

จุดแรกเข้าของ File System Access API คือ window.showOpenFilePicker() เมื่อเรียกใช้ กล่องโต้ตอบจะแสดงกล่องโต้ตอบเครื่องมือเลือกไฟล์ และแจ้งให้ผู้ใช้เลือกไฟล์ หลังจากที่เลือกไฟล์แล้ว API จะแสดงผลอาร์เรย์ของแฮนเดิลไฟล์ พารามิเตอร์ options (ไม่บังคับ) จะช่วยให้คุณกำหนดลักษณะการทำงานของเครื่องมือเลือกไฟล์ได้ เช่น อนุญาตให้ผู้ใช้เลือกไฟล์หลายไฟล์ ไดเรกทอรี หรือประเภทไฟล์ที่แตกต่างกัน หากไม่มีการระบุตัวเลือก เครื่องมือเลือกไฟล์จะอนุญาตให้ผู้ใช้เลือกไฟล์เดียว ซึ่งเหมาะกับเครื่องมือแก้ไขข้อความ

การเรียกใช้ showOpenFilePicker() ต้องทำในบริบทที่ปลอดภัยเช่นเดียวกับ API ที่มีประสิทธิภาพอื่นๆ อีกหลายตัว และต้องเรียกใช้จากภายในท่าทางสัมผัสของผู้ใช้

let fileHandle;
butOpenFile.addEventListener('click', async () => {
  // Destructure the one-element array.
  [fileHandle] = await window.showOpenFilePicker();
  // Do something with the file handle.
});

เมื่อผู้ใช้เลือกไฟล์ showOpenFilePicker() จะแสดงผลอาร์เรย์ของแฮนเดิล ซึ่งในกรณีนี้คืออาร์เรย์องค์ประกอบเดียวที่มี FileSystemFileHandle 1 รายการซึ่งมีพร็อพเพอร์ตี้และวิธีการที่จำเป็นต่อการโต้ตอบกับไฟล์

ขอแนะนำให้เก็บการอ้างอิงถึงแฮนเดิลไฟล์ไว้เพื่อให้ใช้ภายหลังได้ จะต้องบันทึกการเปลี่ยนแปลงลงในไฟล์ หรือดำเนินการอื่นๆ กับไฟล์

อ่านไฟล์จากระบบไฟล์

เมื่อมีแฮนเดิลไฟล์แล้ว คุณก็สามารถดูคุณสมบัติของไฟล์หรือเข้าถึงไฟล์ได้ ตอนนี้ฉันจะอ่านแค่เนื้อหาก่อนนะคะ การเรียกใช้ handle.getFile() แสดงผลออบเจ็กต์ File ซึ่งมี Blob หากต้องการดูข้อมูลจาก blob ให้เรียกใช้เมธอดของ BLOB (slice(), stream(), text() หรือ arrayBuffer())

const file = await fileHandle.getFile();
const contents = await file.text();

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

สรุปข้อมูลทั้งหมด

เมื่อผู้ใช้คลิกปุ่ม "เปิด" เบราว์เซอร์จะแสดงเครื่องมือเลือกไฟล์ เมื่อเลือกไฟล์แล้ว แอปจะอ่านเนื้อหาและใส่ลงใน <textarea>

let fileHandle;
butOpenFile.addEventListener('click', async () => {
  [fileHandle] = await window.showOpenFilePicker();
  const file = await fileHandle.getFile();
  const contents = await file.text();
  textArea.value = contents;
});

เขียนไฟล์ไปยังระบบไฟล์ในเครื่อง

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

สร้างไฟล์ใหม่

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

async function getNewFileHandle() {
  const options = {
    types: [
      {
        description: 'Text Files',
        accept: {
          'text/plain': ['.txt'],
        },
      },
    ],
  };
  const handle = await window.showSaveFilePicker(options);
  return handle;
}

บันทึกการเปลี่ยนแปลงลงดิสก์

คุณค้นหาโค้ดทั้งหมดที่ใช้ในการบันทึกการเปลี่ยนแปลงไปยังไฟล์ได้ในการสาธิตเครื่องมือแก้ไขข้อความใน GitHub การโต้ตอบกับระบบไฟล์หลักๆ อยู่ใน fs-helpers.js หากอธิบายง่ายๆ กระบวนการจะมีลักษณะเหมือนโค้ดด้านล่างนี้ เราจะอธิบายแต่ละขั้นตอนไปให้คุณ

// fileHandle is an instance of FileSystemFileHandle..
async function writeFile(fileHandle, contents) {
  // Create a FileSystemWritableFileStream to write to.
  const writable = await fileHandle.createWritable();
  // Write the contents of the file to the stream.
  await writable.write(contents);
  // Close the file and write the contents to disk.
  await writable.close();
}

การเขียนข้อมูลลงดิสก์ใช้ออบเจ็กต์ FileSystemWritableFileStream ซึ่งเป็นคลาสย่อยของ WritableStream สร้างสตรีมโดยการเรียกใช้ createWritable() ในออบเจ็กต์แฮนเดิลไฟล์ เมื่อมีการเรียก createWritable() เบราว์เซอร์จะตรวจสอบว่าผู้ใช้ให้สิทธิ์ในการเขียนไฟล์หรือไม่ก่อน หากไม่มีการให้สิทธิ์ในการเขียน เบราว์เซอร์จะแจ้งให้ผู้ใช้ขอสิทธิ์ หากไม่ได้รับอนุญาต createWritable() จะส่ง DOMException และแอปจะเขียนไปยังไฟล์ไม่ได้ ในเครื่องมือแก้ไขข้อความ ระบบจะจัดการออบเจ็กต์ DOMException ในเมธอด saveFile()

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

async function writeURLToFile(fileHandle, url) {
  // Create a FileSystemWritableFileStream to write to.
  const writable = await fileHandle.createWritable();
  // Make an HTTP request for the contents.
  const response = await fetch(url);
  // Stream the response into the file.
  await response.body.pipeTo(writable);
  // pipeTo() closes the destination pipe by default, no need to close it.
}

นอกจากนี้ คุณยัง seek() หรือ truncate() ในสตรีมเพื่ออัปเดตไฟล์ในตำแหน่งที่ต้องการหรือปรับขนาดไฟล์ได้ด้วย

ระบุชื่อไฟล์ที่แนะนำและไดเรกทอรีเริ่มต้น

ในหลายๆ กรณี คุณอาจต้องการให้แอปแนะนำชื่อไฟล์หรือตำแหน่งเริ่มต้น เช่น เครื่องมือแก้ไขข้อความอาจต้องการแนะนำชื่อไฟล์เริ่มต้น Untitled Text.txt แทน Untitled ซึ่งทำได้โดยการส่งพร็อพเพอร์ตี้ suggestedName เป็นส่วนหนึ่งของตัวเลือก showSaveFilePicker

const fileHandle = await self.showSaveFilePicker({
  suggestedName: 'Untitled Text.txt',
  types: [{
    description: 'Text documents',
    accept: {
      'text/plain': ['.txt'],
    },
  }],
});

และเช่นเดียวกันสำหรับไดเรกทอรีเริ่มต้นเริ่มต้น หากคุณกำลังสร้างโปรแกรมแก้ไขข้อความ คุณอาจต้องการเริ่มกล่องโต้ตอบการบันทึกไฟล์หรือเปิดไฟล์ในโฟลเดอร์ documents เริ่มต้น ในขณะที่สำหรับโปรแกรมแก้ไขรูปภาพ คุณอาจต้องเริ่มต้นในโฟลเดอร์ pictures ที่เป็นค่าเริ่มต้น คุณแนะนำไดเรกทอรีเริ่มต้นเริ่มต้นได้โดยการส่งพร็อพเพอร์ตี้ startIn ไปยังเมธอด showSaveFilePicker, showDirectoryPicker() หรือ showOpenFilePicker ลักษณะนี้

const fileHandle = await self.showOpenFilePicker({
  startIn: 'pictures'
});

รายการไดเรกทอรีของระบบซึ่งเป็นที่รู้จักมีดังนี้

  • desktop: ไดเรกทอรีบนเดสก์ท็อปของผู้ใช้ หากมี
  • documents: ไดเรกทอรีที่มักจะเก็บเอกสารที่ผู้ใช้สร้างไว้
  • downloads: ไดเรกทอรีที่มักจะเก็บไฟล์ที่ดาวน์โหลดไว้
  • music: ไดเรกทอรีที่มักจะเก็บไฟล์เสียงไว้
  • pictures: ไดเรกทอรีที่โดยปกติแล้วจะจัดเก็บรูปภาพและภาพนิ่งอื่นๆ
  • videos: ไดเรกทอรีที่มักจะใช้เก็บวิดีโอ/ภาพยนตร์

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

// Assume `directoryHandle` is a handle to a previously opened directory.
const fileHandle = await self.showOpenFilePicker({
  startIn: directoryHandle
});

การระบุวัตถุประสงค์ของเครื่องมือเลือกไฟล์ต่างๆ

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

const fileHandle1 = await self.showSaveFilePicker({
  id: 'openText',
});

const fileHandle2 = await self.showSaveFilePicker({
  id: 'importImage',
});

การจัดเก็บแฮนเดิลไฟล์หรือแฮนเดิลไดเรกทอรีใน IndexedDB

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

การบันทึกแฮนเดิลไฟล์หรือไดเรกทอรีไปยัง IndexedDB จะช่วยให้คุณจัดเก็บสถานะหรือจดจำไฟล์หรือไดเรกทอรีที่ผู้ใช้ดำเนินการอยู่ได้ ซึ่งจะช่วยให้สามารถเก็บรายการไฟล์ที่เพิ่งเปิดหรือมีการแก้ไข เสนอให้เปิดไฟล์ล่าสุดเมื่อเปิดแอปอีกครั้ง คืนค่าไดเรกทอรีการทำงานก่อนหน้า และอื่นๆ ในเครื่องมือแก้ไขข้อความ ฉันจะจัดเก็บรายการไฟล์ 5 ไฟล์ล่าสุดที่ผู้ใช้เปิดไว้ เพื่อให้คุณเข้าถึงไฟล์เหล่านั้นอีกครั้งได้โดยง่าย

ตัวอย่างโค้ดด้านล่างแสดงการจัดเก็บและการดึงข้อมูลแฮนเดิลไฟล์ รวมถึงแฮนเดิลไดเรกทอรี คุณดูการใช้งานจริงได้ใน Glitch (ฉันใช้ไลบรารี idb-keyval เพื่อความสั้นกระชับ)

import { get, set } from 'https://unpkg.com/idb-keyval@5.0.2/dist/esm/index.js';

const pre1 = document.querySelector('pre.file');
const pre2 = document.querySelector('pre.directory');
const button1 = document.querySelector('button.file');
const button2 = document.querySelector('button.directory');

// File handle
button1.addEventListener('click', async () => {
  try {
    const fileHandleOrUndefined = await get('file');
    if (fileHandleOrUndefined) {
      pre1.textContent = `Retrieved file handle "${fileHandleOrUndefined.name}" from IndexedDB.`;
      return;
    }
    const [fileHandle] = await window.showOpenFilePicker();
    await set('file', fileHandle);
    pre1.textContent = `Stored file handle for "${fileHandle.name}" in IndexedDB.`;
  } catch (error) {
    alert(error.name, error.message);
  }
});

// Directory handle
button2.addEventListener('click', async () => {
  try {
    const directoryHandleOrUndefined = await get('directory');
    if (directoryHandleOrUndefined) {
      pre2.textContent = `Retrieved directroy handle "${directoryHandleOrUndefined.name}" from IndexedDB.`;
      return;
    }
    const directoryHandle = await window.showDirectoryPicker();
    await set('directory', directoryHandle);
    pre2.textContent = `Stored directory handle for "${directoryHandle.name}" in IndexedDB.`;
  } catch (error) {
    alert(error.name, error.message);
  }
});

แฮนเดิลของไฟล์หรือไดเรกทอรีที่เก็บมีสิทธิ์

เนื่องจากสิทธิ์ในปัจจุบันยังไม่เกิดขึ้นระหว่างเซสชัน คุณควรยืนยันว่าผู้ใช้ได้ให้สิทธิ์แก่ไฟล์หรือไดเรกทอรีโดยใช้ queryPermission() แล้วหรือไม่ หากยังไม่ได้ดำเนินการ ให้โทรหา requestPermission() เพื่อส่งคำขอ (อีกครั้ง) โดยจะทำงานในลักษณะเดียวกันสำหรับแฮนเดิลไฟล์และไดเรกทอรี คุณต้องเรียกใช้ fileOrDirectoryHandle.requestPermission(descriptor) หรือ fileOrDirectoryHandle.queryPermission(descriptor) ตามลำดับ

ในเครื่องมือแก้ไขข้อความ ฉันสร้างเมธอด verifyPermission() ที่จะตรวจสอบว่าผู้ใช้ให้สิทธิ์แล้วหรือยัง และจะส่งคำขอหากจำเป็น

async function verifyPermission(fileHandle, readWrite) {
  const options = {};
  if (readWrite) {
    options.mode = 'readwrite';
  }
  // Check if permission was already granted. If so, return true.
  if ((await fileHandle.queryPermission(options)) === 'granted') {
    return true;
  }
  // Request permission. If the user grants permission, return true.
  if ((await fileHandle.requestPermission(options)) === 'granted') {
    return true;
  }
  // The user didn't grant permission, so return false.
  return false;
}

การขอสิทธิ์เขียนด้วยคำขออ่านทำให้ฉันลดจำนวนพรอมต์สิทธิ์ ผู้ใช้เห็นข้อความแจ้ง 1 รายการเมื่อเปิดไฟล์ และให้สิทธิ์ทั้งการอ่านและเขียนในไฟล์

การเปิดไดเรกทอรีและแจกแจงเนื้อหาในไดเรกทอรี

หากต้องการแจกแจงไฟล์ทั้งหมดในไดเรกทอรี ให้เรียก showDirectoryPicker() ผู้ใช้เลือกไดเรกทอรีในเครื่องมือเลือก จากนั้นระบบจะแสดงผล FileSystemDirectoryHandle ซึ่งจะช่วยให้คุณแจกแจงและเข้าถึงไฟล์ของไดเรกทอรีได้ โดยค่าเริ่มต้น คุณจะมีสิทธิ์อ่านไฟล์ในไดเรกทอรี แต่หากต้องการสิทธิ์การเขียน คุณจะส่ง { mode: 'readwrite' } ไปยังเมธอดนั้นได้

const butDir = document.getElementById('butDirectory');
butDir.addEventListener('click', async () => {
  const dirHandle = await window.showDirectoryPicker();
  for await (const entry of dirHandle.values()) {
    console.log(entry.kind, entry.name);
  }
});

นอกจากนี้ หากคุณต้องการเข้าถึงไฟล์แต่ละไฟล์ผ่าน getFile() เพื่อรับขนาดไฟล์แต่ละรายการ อย่าใช้ await กับผลการค้นหาแต่ละรายการตามลำดับ แต่ให้ประมวลผลไฟล์ทั้งหมดพร้อมกัน เช่น ผ่าน Promise.all()

const butDir = document.getElementById('butDirectory');
butDir.addEventListener('click', async () => {
  const dirHandle = await window.showDirectoryPicker();
  const promises = [];
  for await (const entry of dirHandle.values()) {
    if (entry.kind !== 'file') {
      continue;
    }
    promises.push(entry.getFile().then((file) => `${file.name} (${file.size})`));
  }
  console.log(await Promise.all(promises));
});

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

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

// In an existing directory, create a new directory named "My Documents".
const newDirectoryHandle = await existingDirectoryHandle.getDirectoryHandle('My Documents', {
  create: true,
});
// In this new directory, create a file named "My Notes.txt".
const newFileHandle = await newDirectoryHandle.getFileHandle('My Notes.txt', { create: true });

การแก้ปัญหาเส้นทางของรายการในไดเรกทอรี

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

// Resolve the path of the previously created file called "My Notes.txt".
const path = await newDirectoryHandle.resolve(newFileHandle);
// `path` is now ["My Documents", "My Notes.txt"]

การลบไฟล์และโฟลเดอร์ในไดเรกทอรี

หากมีสิทธิ์เข้าถึงไดเรกทอรีแล้ว คุณจะลบไฟล์และโฟลเดอร์ที่มีอยู่ได้ด้วยเมธอด removeEntry() สำหรับโฟลเดอร์ การลบอาจเกิดซ้ำได้ และจะรวมโฟลเดอร์ย่อยทั้งหมดและไฟล์ที่อยู่ในนั้นด้วย

// Delete a file.
await directoryHandle.removeEntry('Abandoned Projects.txt');
// Recursively delete a folder.
await directoryHandle.removeEntry('Old Stuff', { recursive: true });

การลบไฟล์หรือโฟลเดอร์โดยตรง

หากคุณมีสิทธิ์เข้าถึงไฟล์หรือแฮนเดิลไดเรกทอรี ให้เรียกใช้ remove() ใน FileSystemFileHandle หรือ FileSystemDirectoryHandle เพื่อนําแฮนเดิลออก

// Delete a file.
await fileHandle.remove();
// Delete a directory.
await directoryHandle.remove();

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

คุณเปลี่ยนชื่อหรือย้ายไฟล์และโฟลเดอร์ไปยังตำแหน่งใหม่ได้โดยเรียกใช้ move() ในอินเทอร์เฟซ FileSystemHandle FileSystemHandle มีอินเทอร์เฟซย่อย FileSystemFileHandle และ FileSystemDirectoryHandle เมธอด move() จะใช้พารามิเตอร์ 1 หรือ 2 รายการ รายการแรกอาจเป็นสตริงที่มีชื่อใหม่ หรือ FileSystemDirectoryHandle ไปยังโฟลเดอร์ปลายทางก็ได้ ในกรณีหลัง พารามิเตอร์เสริมที่ 2 เป็นสตริงที่มีชื่อใหม่ ดังนั้นการย้ายและการเปลี่ยนชื่ออาจเกิดขึ้นในขั้นตอนเดียว

// Rename the file.
await file.move('new_name');
// Move the file to a new directory.
await file.move(directory);
// Move the file to a new directory and rename it.
await file.move(directory, 'newer_name');

การผสานรวมแบบลากและวาง

อินเทอร์เฟซการลากและวาง HTML ช่วยให้เว็บแอปพลิเคชันยอมรับไฟล์ที่ลากและวางบนหน้าเว็บได้ ในระหว่างดำเนินการลากและวาง ไฟล์และรายการไดเรกทอรีที่ลากจะเชื่อมโยงกับรายการไฟล์และรายการไดเรกทอรีตามลำดับ เมธอด DataTransferItem.getAsFileSystemHandle() จะแสดงคำสัญญาที่มีออบเจ็กต์ FileSystemFileHandle หากรายการที่ลากเป็นไฟล์ และสัญญาว่าจะให้มีออบเจ็กต์ FileSystemDirectoryHandle หากรายการที่ลากเป็นไดเรกทอรี รายการด้านล่างแสดง การใช้งานจริง โปรดทราบว่า DataTransferItem.kind ของอินเทอร์เฟซการลากและวาง DataTransferItem.kind คือ "file" สำหรับทั้งไฟล์และไดเรกทอรี ในขณะที่ File System Access API FileSystemHandle.kind "file" มีไว้สำหรับไฟล์และ "directory" สำหรับไดเรกทอรี

elem.addEventListener('dragover', (e) => {
  // Prevent navigation.
  e.preventDefault();
});

elem.addEventListener('drop', async (e) => {
  e.preventDefault();

  const fileHandlesPromises = [...e.dataTransfer.items]
    .filter((item) => item.kind === 'file')
    .map((item) => item.getAsFileSystemHandle());

  for await (const handle of fileHandlesPromises) {
    if (handle.kind === 'directory') {
      console.log(`Directory: ${handle.name}`);
    } else {
      console.log(`File: ${handle.name}`);
    }
  }
});

การเข้าถึงระบบไฟล์ส่วนตัวต้นทาง

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

const root = await navigator.storage.getDirectory();
// Create a new file handle.
const fileHandle = await root.getFileHandle('Untitled.txt', { create: true });
// Create a new directory handle.
const dirHandle = await root.getDirectoryHandle('New Folder', { create: true });
// Recursively remove a directory.
await root.removeEntry('Old Stuff', { recursive: true });

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

  • 86
  • 86
  • 111
  • 15.2

แหล่งที่มา

การเข้าถึงไฟล์ที่เพิ่มประสิทธิภาพเพื่อประสิทธิภาพจากระบบไฟล์ส่วนตัวต้นทาง

ระบบไฟล์ส่วนตัวของต้นทางมอบการเข้าถึงทางเลือกสำหรับไฟล์ชนิดพิเศษที่ได้รับการปรับปรุงให้มีประสิทธิภาพสูงสุด เช่น ด้วยการให้สิทธิ์เขียนเนื้อหาไฟล์แบบที่ทำด้วยตัวเองและเฉพาะตัว ใน Chromium 102 ขึ้นไป จะมีวิธีการเพิ่มเติมในระบบไฟล์ส่วนตัวต้นทางสำหรับการเข้าถึงไฟล์แบบง่ายๆ คือ createSyncAccessHandle() (สำหรับการดำเนินการอ่านและเขียนพร้อมกัน) เผยแพร่ใน FileSystemFileHandle แต่เฉพาะใน Web Workers

// (Read and write operations are synchronous,
// but obtaining the handle is asynchronous.)
// Synchronous access exclusively in Worker contexts.
const accessHandle = await fileHandle.createSyncAccessHandle();
const writtenBytes = accessHandle.write(buffer);
const readBytes = accessHandle.read(buffer, { at: 1 });

การเคลือบโพลีฟิล

ระบบไม่สามารถสร้างเมธอด File System Access API ให้สมบูรณ์ได้

  • เมธอด showOpenFilePicker() จะประมาณได้ด้วยองค์ประกอบ <input type="file">
  • คุณจำลองเมธอด showSaveFilePicker() ได้ด้วยองค์ประกอบ <a download="file_name"> แม้ว่าวิธีนี้จะทริกเกอร์การดาวน์โหลดแบบเป็นโปรแกรมและไม่อนุญาตให้เขียนทับไฟล์ที่มีอยู่
  • เมธอด showDirectoryPicker() อาจจำลองได้ด้วยองค์ประกอบ <input type="file" webkitdirectory> ที่ไม่ใช่แบบมาตรฐาน

เราได้พัฒนาไลบรารีชื่อ browser-fs-access ที่ใช้ File System Access API ทุกครั้งที่ทำได้ และจะกลับไปใช้ตัวเลือกที่ดีที่สุดในลำดับถัดไปในกรณีอื่นๆ ทั้งหมด

ความปลอดภัยและสิทธิ์

ทีม Chrome ได้ออกแบบและนำ File System Access API มาใช้โดยใช้หลักการสำคัญที่ระบุไว้ในการควบคุมการเข้าถึงฟีเจอร์แพลตฟอร์มเว็บที่มีประสิทธิภาพ ซึ่งรวมถึงการควบคุมผู้ใช้และความโปร่งใส ตลอดจนหลักการยศาสตร์ของผู้ใช้

การเปิดไฟล์หรือบันทึกไฟล์ใหม่

เครื่องมือเลือกไฟล์ที่จะเปิดไฟล์สำหรับการอ่าน
เครื่องมือเลือกไฟล์ที่ใช้เปิดไฟล์ที่มีอยู่เพื่ออ่าน

เมื่อเปิดไฟล์ ผู้ใช้จะให้สิทธิ์ในการอ่านไฟล์หรือไดเรกทอรีผ่านเครื่องมือเลือกไฟล์ เครื่องมือเลือกไฟล์ที่เปิดอยู่จะแสดงผ่านท่าทางสัมผัสของผู้ใช้เมื่อแสดงจากบริบทที่ปลอดภัยเท่านั้น หากผู้ใช้เปลี่ยนใจ ก็สามารถยกเลิกการเลือกในเครื่องมือเลือกไฟล์และเว็บไซต์จะไม่มีสิทธิ์เข้าถึงสิ่งใด ซึ่งเป็นลักษณะการทำงานเดียวกันกับขององค์ประกอบ <input type="file">

เครื่องมือเลือกไฟล์ที่จะบันทึกไฟล์ลงในดิสก์
เครื่องมือเลือกไฟล์เพื่อบันทึกไฟล์ลงในดิสก์

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

โฟลเดอร์ที่ถูกจำกัด

เบราว์เซอร์อาจจำกัดความสามารถของผู้ใช้ในการบันทึกลงในโฟลเดอร์บางโฟลเดอร์เพื่อช่วยปกป้องผู้ใช้และข้อมูลของผู้ใช้ เช่น โฟลเดอร์หลักของระบบปฏิบัติการอย่าง Windows, โฟลเดอร์ไลบรารีของ macOS ฯลฯ เมื่อเกิดเหตุการณ์เช่นนี้ เบราว์เซอร์จะแสดงข้อความแจ้งและขอให้ผู้ใช้เลือกโฟลเดอร์อื่น

การแก้ไขไฟล์หรือไดเรกทอรีที่มีอยู่

เว็บแอปไม่สามารถแก้ไขไฟล์ในดิสก์โดยไม่ได้รับการอนุญาตอย่างชัดแจ้งจากผู้ใช้

ข้อความแจ้งเกี่ยวกับสิทธิ์

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

ข้อความแจ้งสิทธิ์ที่แสดงก่อนบันทึกไฟล์
ข้อความแจ้งที่แสดงต่อผู้ใช้ก่อนที่เบราว์เซอร์จะได้รับสิทธิ์ในการเขียนในไฟล์ที่มีอยู่

นอกจากนี้ เว็บแอปที่แก้ไขไฟล์หลายไฟล์ เช่น IDE จะขอสิทธิ์ในการบันทึกการเปลี่ยนแปลง ณ เวลาที่เปิดได้ด้วย

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

ความโปร่งใส

ไอคอนแถบอเนกประสงค์
ไอคอนแถบอเนกประสงค์ที่บ่งชี้ว่าผู้ใช้ได้ให้สิทธิ์เว็บไซต์ในการ บันทึกลงในไฟล์ในเครื่อง

เมื่อผู้ใช้ให้สิทธิ์เว็บแอปในการบันทึกไฟล์ในเครื่องแล้ว เบราว์เซอร์จะแสดงไอคอนในแถบ URL การคลิกที่ไอคอนจะเป็นการเปิดป๊อปอัปที่แสดงรายการไฟล์ที่ผู้ใช้ได้ให้สิทธิ์เข้าถึงไว้ ผู้ใช้สามารถเพิกถอนสิทธิ์ดังกล่าวได้อย่างง่ายดายหากต้องการ

ความต่อเนื่องของสิทธิ์

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

ความคิดเห็น

เราอยากทราบเกี่ยวกับประสบการณ์ที่คุณได้รับจากการใช้งาน File System Access API

บอกให้เราทราบเกี่ยวกับการออกแบบ API

มีบางอย่างเกี่ยวกับ API ที่ไม่เป็นไปตามที่คุณคาดหวังไหม หรือมีวิธีการหรือพร็อพเพอร์ตี้ ที่ขาดหายไปที่คุณจำเป็นต้องใช้ในการนำแนวคิดของคุณไปปฏิบัติหรือไม่ หากมีข้อสงสัยหรือความคิดเห็น เกี่ยวกับโมเดลความปลอดภัย

พบปัญหาในการติดตั้งใช้งานใช่ไหม

คุณพบข้อบกพร่องในการใช้งาน Chrome หรือไม่ หรือการใช้งานแตกต่างจากข้อกำหนดหรือไม่

  • รายงานข้อบกพร่องที่ https://new.crbug.com อย่าลืมใส่รายละเอียดให้มากที่สุดเท่าที่จะทำได้ วิธีการง่ายๆ ในการทำซ้ำ และตั้งค่าคอมโพเนนต์เป็น Blink>Storage>FileSystem Glitch เหมาะสำหรับการแชร์การดำเนินการซ้ำที่ง่ายและรวดเร็ว

หากวางแผนจะใช้ API

หากกำลังวางแผนใช้ File System Access API ในเว็บไซต์ การสนับสนุนแบบสาธารณะของคุณช่วยให้เราสามารถจัดลำดับความสำคัญของฟีเจอร์ และแสดงให้ผู้ให้บริการเบราว์เซอร์รายอื่นๆ เห็นว่าการสนับสนุนฟีเจอร์เหล่านั้นมีความสำคัญเพียงใด

ลิงก์ที่มีประโยชน์

ข้อความแสดงการยอมรับ

ข้อกำหนดของ File System Access API เขียนโดย Marijn Kruisselbrink