File System Access API: דרך פשוטה יותר לגשת לקבצים מקומיים

File System Access API מאפשר לאפליקציות אינטרנט לקרוא או לשמור שינויים ישירות בקבצים ובתיקיות במכשיר של המשתמש.

מה זה File System Access API?

File System Access API (לשעבר Native File System API) (לשעבר Native File System API) מאפשר למפתחים ליצור אפליקציות אינטרנט מתקדמות שמקיימות אינטראקציה עם קבצים במכשיר המקומי של המשתמש, כמו סביבות פיתוח משולבות (IDE), עורכי תמונות וסרטונים, עורכי טקסט ועוד. אחרי שמשתמש מעניק גישה לאפליקציית אינטרנט, ה-API הזה מאפשר לקרוא או לשמור את השינויים ישירות בקבצים ובתיקיות במכשיר של המשתמש. מעבר לקריאה ולכתיבה של קבצים, File System Access API מאפשר לפתוח ספרייה ולספור את התוכן שלה.

אם כבר עבדתם בעבר עם קריאה וכתיבה של קבצים, הרבה מה שאני עומד לשתף יהיה מוכר לכם. אני ממליץ לך לקרוא אותו בכל מקרה, מפני שלא כל המערכות דומות.

File System Access API נתמך כרגע ברוב דפדפני Chromium ב-Windows, ב-macOS, ב-ChromeOS וב-Linux. יוצא מן הכלל הוא Brave, כי כרגע, האפשרות הזו זמינה רק מאחורי דגל. החל מ-Chromium 109, Android תומך בחלק המקורי של מערכת הקבצים הפרטית של ה-API. כרגע אין תוכניות לשיטות בורר, אבל אפשר לסמן את crbug.com/1011535 כדי לעקוב אחר ההתקדמות הפוטנציאלית.

שימוש ב-File System Access API

כדי להדגים את העוצמה והתועלת של File System Access API, כתבתי עורך טקסט לקובץ יחיד. היא מאפשרת לפתוח קובץ טקסט, לערוך אותו, לשמור את השינויים בדיסק או להתחיל קובץ חדש ולשמור את השינויים בדיסק. זה לא יוקרתי, אבל הוא מספק מספיק כדי לעזור לכם להבין את המושגים.

תמיכת דפדפן

תמיכה בדפדפן

  • 86
  • 86
  • x
  • x

מקור

אני רוצה לנסות

ראו את File System Access API בפעולה בהדגמה של עורך הטקסט.

קריאת קובץ ממערכת הקבצים המקומית

התרחיש לדוגמה הראשון שאני רוצה להתמודד איתו הוא לבקש מהמשתמש לבחור קובץ, ואז לפתוח ולקרוא אותו מהדיסק.

בקשה מהמשתמש לבחור קובץ לקריאה

נקודת הכניסה ל-File System Access API היא window.showOpenFilePicker(). כשהיא נקראת, מוצגת תיבת דו-שיח לבחירת קבצים, ומבקשים מהמשתמש לבחור קובץ. אחרי שהם בוחרים קובץ, ה-API מחזיר מגוון של כינויים לקבצים. פרמטר אופציונלי של options מאפשר לכם להשפיע על ההתנהגות של בוחר הקבצים, למשל, בכך שהוא מאפשר למשתמש לבחור כמה קבצים, ספריות או סוגי קבצים שונים. אם לא צוינו אפשרויות כלשהן, בוחר הקבצים מאפשר למשתמש לבחור קובץ יחיד. זה מושלם לעורך טקסט.

כמו ממשקי API רבים אחרים, הקריאה ל-showOpenFilePicker() חייבת להתבצע בהקשר מאובטח, וצריך להפעיל אותה מתוך תנועת משתמש.

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

אחרי שהמשתמש בוחר קובץ, showOpenFilePicker() מחזיר מערך של כינויים, במקרה הזה מערך של רכיב אחד עם FileSystemFileHandle אחד שמכיל את המאפיינים והשיטות שנדרשים לאינטראקציה עם הקובץ.

כדאי לשמור הפניה לכינוי של הקובץ כדי שתוכלו להשתמש בו מאוחר יותר. צריך לשמור את השינויים בקובץ או לבצע פעולות אחרות.

קריאת קובץ ממערכת הקבצים

עכשיו יש לכם כינוי לקובץ, כך שתוכלו לקבל את מאפייני הקובץ או לגשת לקובץ עצמו. בינתיים, אקרא את התוכן. קריאה ל-handle.getFile() מחזירה אובייקט File שמכיל 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;
});

כתיבת הקובץ למערכת הקבצים המקומית

בעורך הטקסט יש שתי דרכים לשמור קובץ: שמירה ושמירה בשם. האפשרות Save כותבת את השינויים בחזרה לקובץ המקורי באמצעות כינוי הקובץ שאוחזר קודם לכן. עם זאת, האפשרות שמירה בשם יוצרת קובץ חדש, ולכן יש צורך בכינוי חדש לקובץ.

יצירת קובץ חדש

כדי לשמור קובץ צריך להפעיל את הפקודה 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 מטופלים ב-method 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
});

ציון המטרה של בוררי קבצים שונים

לפעמים לאפליקציות יש בוררים שונים למטרות שונות. לדוגמה, עורך טקסט עשיר עשוי לאפשר למשתמש לפתוח קובצי טקסט, אבל גם לייבא תמונות. כברירת מחדל, כל בוחר קבצים ייפתח במיקום האחרון שזכור. אפשר לעקוף את הבעיה על ידי אחסון ערכי id לכל סוג של בורר. אם צוין id, ההטמעה של בוחר הקבצים זוכרת ספרייה נפרדת (נפרדת בשימוש האחרון) לאותו id.

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

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

אחסון כינויים של קבצים או כינויים של ספריות ב-IndexedDB

אפשר לסדר את הכינויים של הקבצים והספריות עם כינויים. כלומר, אפשר לשמור כינוי של קובץ או ספרייה ב-IndexedDB, או לקרוא ל-postMessage() כדי לשלוח אותם בין אותו מקור ברמה עליונה.

המשמעות של שמירת קובץ או ספרייה ב-IndexedDB היא שאפשר לאחסן מצבים, או לזכור על אילו קבצים או ספריות המשתמש עבד. כך אפשר לשמור רשימה של קבצים שנפתחו או נערכו לאחרונה, להציע לפתוח מחדש את הקובץ האחרון כשפותחים את האפליקציה, לשחזר את ספריית העבודה הקודמת ועוד. בכלי לעריכת טקסט, אני שומר רשימה של חמשת הקבצים האחרונים שהמשתמש פתח, כדי שיהיה קל לגשת שוב לקבצים האלה.

דוגמת הקוד הבאה מציגה אחסון ואחזור של כינוי לקובץ וכינוי לספרייה. תוכלו לראות את זה בפעולה ב-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;
}

כשביקשתי הרשאת כתיבה עם בקשת הקריאה, הפחתתי את מספר הבקשות להרשאות. המשתמש רואה הנחיה אחת בפתיחת הקובץ ומעניק הרשאה גם לקרוא וגם לכתוב בו.

פתיחת ספרייה וספירת התוכן שלה

כדי לספור את כל הקבצים בספרייה, מתקשרים אל 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() משתמשת בפרמטר אחד או שניים. הערך הראשון יכול להיות מחרוזת עם השם החדש או FileSystemDirectoryHandle לתיקיית היעד. במקרה מאוחר יותר, הפרמטר השני האופציונלי הוא מחרוזת עם השם החדש, ולכן העברה ושינוי השם יכולים להתבצע בשלב אחד.

// 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 של ממשק הגרירה והשחרור הוא "file" גם לקבצים וגם לספריות, ואילו FileSystemHandle.kind של ממשק ה-API של גישה למערכת קבצים הוא "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. גליץ' הוא כלי מעולה לשיתוף גיבויים מהירים וקלים.

מתכנן להשתמש ב-API?

תכננת להשתמש ב-File System Access API באתר שלך? התמיכה הציבורית שלכם עוזרת לנו לתעדף תכונות, ומראה לספקי דפדפנים אחרים עד כמה חיוני לספק להם תמיכה.

קישורים שימושיים

אישורים

המפרט של File System Access API נכתב על ידי Marijn Kruisselbrink.