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 การเข้าถึงระบบไฟล์ ทำให้คุณสามารถเปิดไฟล์ข้อความ แก้ไข บันทึกการเปลี่ยนแปลงกลับไปยังดิสก์ หรือเริ่มต้นไฟล์ใหม่และบันทึกการเปลี่ยนแปลงลงในดิสก์ ไม่สวยแต่ก็เพียงพอต่อการทำความเข้าใจแนวคิด
การสนับสนุนเบราว์เซอร์
ลองเลย
ดูการทำงานของ 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 });
การเข้าถึงไฟล์ที่เพิ่มประสิทธิภาพเพื่อประสิทธิภาพจากระบบไฟล์ส่วนตัวต้นทาง
ระบบไฟล์ส่วนตัวของต้นทางมอบการเข้าถึงทางเลือกสำหรับไฟล์ชนิดพิเศษที่ได้รับการปรับปรุงให้มีประสิทธิภาพสูงสุด เช่น ด้วยการให้สิทธิ์เขียนเนื้อหาไฟล์แบบที่ทำด้วยตัวเองและเฉพาะตัว ใน 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 มาใช้โดยใช้หลักการสำคัญที่ระบุไว้ในการควบคุมการเข้าถึงฟีเจอร์แพลตฟอร์มเว็บที่มีประสิทธิภาพ ซึ่งรวมถึงการควบคุมผู้ใช้และความโปร่งใส ตลอดจนหลักการยศาสตร์ของผู้ใช้
การเปิดไฟล์หรือบันทึกไฟล์ใหม่
![เครื่องมือเลือกไฟล์ที่จะเปิดไฟล์สำหรับการอ่าน](https://developer.chrome.com/static/docs/capabilities/web-apis/file-system-access/image/file-picker-open-file-8cb5f6783ee89.jpg?authuser=5&hl=th)
เมื่อเปิดไฟล์ ผู้ใช้จะให้สิทธิ์ในการอ่านไฟล์หรือไดเรกทอรีผ่านเครื่องมือเลือกไฟล์
เครื่องมือเลือกไฟล์ที่เปิดอยู่จะแสดงผ่านท่าทางสัมผัสของผู้ใช้เมื่อแสดงจากบริบทที่ปลอดภัยเท่านั้น หากผู้ใช้เปลี่ยนใจ ก็สามารถยกเลิกการเลือกในเครื่องมือเลือกไฟล์และเว็บไซต์จะไม่มีสิทธิ์เข้าถึงสิ่งใด ซึ่งเป็นลักษณะการทำงานเดียวกันกับขององค์ประกอบ <input type="file">
![เครื่องมือเลือกไฟล์ที่จะบันทึกไฟล์ลงในดิสก์](https://developer.chrome.com/static/docs/capabilities/web-apis/file-system-access/image/file-picker-save-file-bd54ae001799a.jpg?authuser=5&hl=th)
ในทำนองเดียวกัน เมื่อเว็บแอปต้องการบันทึกไฟล์ใหม่ เบราว์เซอร์จะแสดงเครื่องมือเลือกไฟล์ในการบันทึกไฟล์ ทำให้ผู้ใช้ระบุชื่อและตำแหน่งของไฟล์ใหม่ได้ เนื่องจากโปรแกรมจะบันทึกไฟล์ใหม่ลงในอุปกรณ์ (แทนการเขียนทับไฟล์ที่มีอยู่) เครื่องมือเลือกไฟล์จะให้สิทธิ์แอปในการเขียนไฟล์
โฟลเดอร์ที่ถูกจำกัด
เบราว์เซอร์อาจจำกัดความสามารถของผู้ใช้ในการบันทึกลงในโฟลเดอร์บางโฟลเดอร์เพื่อช่วยปกป้องผู้ใช้และข้อมูลของผู้ใช้ เช่น โฟลเดอร์หลักของระบบปฏิบัติการอย่าง Windows, โฟลเดอร์ไลบรารีของ macOS ฯลฯ เมื่อเกิดเหตุการณ์เช่นนี้ เบราว์เซอร์จะแสดงข้อความแจ้งและขอให้ผู้ใช้เลือกโฟลเดอร์อื่น
การแก้ไขไฟล์หรือไดเรกทอรีที่มีอยู่
เว็บแอปไม่สามารถแก้ไขไฟล์ในดิสก์โดยไม่ได้รับการอนุญาตอย่างชัดแจ้งจากผู้ใช้
ข้อความแจ้งเกี่ยวกับสิทธิ์
หากมีผู้ต้องการบันทึกการเปลี่ยนแปลงไปยังไฟล์ที่ตนได้ให้สิทธิ์อ่านไว้ก่อนหน้านี้ เบราว์เซอร์จะแสดงข้อความแจ้งสิทธิ์เพื่อขอสิทธิ์ให้เว็บไซต์เขียนการเปลี่ยนแปลงลงในดิสก์ สามารถทริกเกอร์คำขอสิทธิ์ได้ด้วยท่าทางสัมผัสของผู้ใช้เท่านั้น เช่น โดยคลิกปุ่มบันทึก
![ข้อความแจ้งสิทธิ์ที่แสดงก่อนบันทึกไฟล์](https://developer.chrome.com/static/docs/capabilities/web-apis/file-system-access/image/permission-prompt-shown-p-823d180f97ad5.png?authuser=5&hl=th)
นอกจากนี้ เว็บแอปที่แก้ไขไฟล์หลายไฟล์ เช่น IDE จะขอสิทธิ์ในการบันทึกการเปลี่ยนแปลง ณ เวลาที่เปิดได้ด้วย
หากผู้ใช้เลือก "ยกเลิก" และไม่ได้ให้สิทธิ์เขียน เว็บแอปจะบันทึกการเปลี่ยนแปลงไปยังไฟล์ในเครื่องไม่ได้ โดยควรมีวิธีอื่นให้ผู้ใช้บันทึกข้อมูลของตนเอง เช่น การระบุวิธี "ดาวน์โหลด" ไฟล์ การบันทึกข้อมูลไปยังระบบคลาวด์ เป็นต้น
ความโปร่งใส
![ไอคอนแถบอเนกประสงค์](https://developer.chrome.com/static/docs/capabilities/web-apis/file-system-access/image/omnibox-icon-5797e9a831603.jpg?authuser=5&hl=th)
เมื่อผู้ใช้ให้สิทธิ์เว็บแอปในการบันทึกไฟล์ในเครื่องแล้ว เบราว์เซอร์จะแสดงไอคอนในแถบ URL การคลิกที่ไอคอนจะเป็นการเปิดป๊อปอัปที่แสดงรายการไฟล์ที่ผู้ใช้ได้ให้สิทธิ์เข้าถึงไว้ ผู้ใช้สามารถเพิกถอนสิทธิ์ดังกล่าวได้อย่างง่ายดายหากต้องการ
ความต่อเนื่องของสิทธิ์
เว็บแอปจะบันทึกการเปลี่ยนแปลงไปยังไฟล์ต่อไปได้โดยไม่มีข้อความแจ้งจนกว่าแท็บทั้งหมดของต้นทางจะปิดลง เมื่อปิดแท็บแล้ว เว็บไซต์จะสูญเสียสิทธิ์เข้าถึงทั้งหมด ครั้งต่อไปที่ผู้ใช้ใช้เว็บแอป ผู้ใช้จะได้รับข้อความแจ้งอีกครั้งเพื่อเข้าถึงไฟล์
ความคิดเห็น
เราอยากทราบเกี่ยวกับประสบการณ์ที่คุณได้รับจากการใช้งาน File System Access API
บอกให้เราทราบเกี่ยวกับการออกแบบ API
มีบางอย่างเกี่ยวกับ API ที่ไม่เป็นไปตามที่คุณคาดหวังไหม หรือมีวิธีการหรือพร็อพเพอร์ตี้ ที่ขาดหายไปที่คุณจำเป็นต้องใช้ในการนำแนวคิดของคุณไปปฏิบัติหรือไม่ หากมีข้อสงสัยหรือความคิดเห็น เกี่ยวกับโมเดลความปลอดภัย
- ให้ส่งปัญหาข้อมูลจำเพาะในที่เก็บ GitHub สำหรับการเข้าถึงระบบไฟล์ WICG หรือแสดงความคิดเห็นเกี่ยวกับปัญหาที่มีอยู่
พบปัญหาในการติดตั้งใช้งานใช่ไหม
คุณพบข้อบกพร่องในการใช้งาน Chrome หรือไม่ หรือการใช้งานแตกต่างจากข้อกำหนดหรือไม่
- รายงานข้อบกพร่องที่ https://new.crbug.com อย่าลืมใส่รายละเอียดให้มากที่สุดเท่าที่จะทำได้ วิธีการง่ายๆ ในการทำซ้ำ และตั้งค่าคอมโพเนนต์เป็น
Blink>Storage>FileSystem
Glitch เหมาะสำหรับการแชร์การดำเนินการซ้ำที่ง่ายและรวดเร็ว
หากวางแผนจะใช้ API
หากกำลังวางแผนใช้ File System Access API ในเว็บไซต์ การสนับสนุนแบบสาธารณะของคุณช่วยให้เราสามารถจัดลำดับความสำคัญของฟีเจอร์ และแสดงให้ผู้ให้บริการเบราว์เซอร์รายอื่นๆ เห็นว่าการสนับสนุนฟีเจอร์เหล่านั้นมีความสำคัญเพียงใด
- แชร์แผนการใช้งานในชุดข้อความของ WICG Discourse
- ส่งทวีตไปที่ @ChromiumDev โดยใช้แฮชแท็ก
#FileSystemAccess
และแจ้งให้เราทราบว่าคุณใช้แฮชแท็กนี้ที่ไหนและอย่างไร
ลิงก์ที่มีประโยชน์
- คำอธิบายแบบสาธารณะ
- ข้อกำหนดการเข้าถึงระบบไฟล์และข้อกำหนดเกี่ยวกับไฟล์
- ข้อบกพร่องในการติดตาม
- รายการ ChromeStatus.com
- คำจำกัดความของ TypeScript
- File System Access API - โมเดลความปลอดภัยของ Chromium
- คอมโพเนนต์การกะพริบ:
Blink>Storage>FileSystem
ข้อความแสดงการยอมรับ
ข้อกำหนดของ File System Access API เขียนโดย Marijn Kruisselbrink