API دسترسی به سیستم فایل: دسترسی به فایل های محلی را ساده می کند

رابط برنامه‌نویسی کاربردی دسترسی به سیستم فایل (File System Access API) به برنامه‌های وب اجازه می‌دهد تا تغییرات را مستقیماً در فایل‌ها و پوشه‌های دستگاه کاربر بخوانند یا ذخیره کنند.

منتشر شده: ۱۹ آگوست ۲۰۲۴

API دسترسی به سیستم فایل، توسعه‌دهندگان را قادر می‌سازد تا برنامه‌های وب قدرتمندی بسازند که با فایل‌های موجود در دستگاه محلی کاربر، مانند IDEها، ویرایشگرهای عکس و فیلم، ویرایشگرهای متن و موارد دیگر، تعامل داشته باشند. پس از اینکه کاربر به یک برنامه وب دسترسی می‌دهد، این API به آنها اجازه می‌دهد تا تغییرات را مستقیماً در فایل‌ها و پوشه‌های دستگاه کاربر بخوانند یا ذخیره کنند. فراتر از خواندن و نوشتن فایل‌ها، API دسترسی به سیستم فایل، امکان باز کردن یک دایرکتوری و شمارش محتویات آن را فراهم می‌کند.

اگر قبلاً با خواندن و نوشتن فایل‌ها کار کرده‌اید، بخش زیادی از آنچه که قرار است به اشتراک بگذارم برای شما آشنا خواهد بود. در هر صورت شما را تشویق می‌کنم که آن را بخوانید، زیرا همه سیستم‌ها یکسان نیستند.

رابط برنامه‌نویسی کاربردی دسترسی به سیستم فایل (File System Access API) در اکثر مرورگرهای کرومیوم (Chromium) در ویندوز، macOS، ChromeOS، لینوکس و اندروید پشتیبانی می‌شود. یک استثنای قابل توجه Brave است که در حال حاضر فقط با یک پرچم (flag) در دسترس است.

استفاده از API دسترسی به سیستم فایل

برای نشان دادن قدرت و مفید بودن رابط برنامه‌نویسی کاربردی دسترسی به سیستم فایل (File System Access API)، یک ویرایشگر متن تک‌فایلی نوشتم. این ویرایشگر به شما امکان می‌دهد یک فایل متنی را باز کنید، آن را ویرایش کنید، تغییرات را دوباره روی دیسک ذخیره کنید، یا یک فایل جدید ایجاد کنید و تغییرات را روی دیسک ذخیره کنید. چیز خاصی نیست، اما به اندازه کافی برای کمک به شما در درک مفاهیم ارائه می‌دهد.

پشتیبانی مرورگر

Browser Support

  • کروم: ۸۶.
  • لبه: ۸۶.
  • فایرفاکس: پشتیبانی نمی‌شود.
  • سافاری: پشتیبانی نمی‌شود.

Source

تشخیص ویژگی

برای اینکه بفهمید آیا API دسترسی به سیستم فایل پشتیبانی می‌شود یا خیر، بررسی کنید که آیا متد انتخابگر مورد نظر شما وجود دارد یا خیر.

if ('showOpenFilePicker' in self) {
  // The `showOpenFilePicker()` method of the File System Access API is supported.
}

امتحانش کن

API دسترسی به سیستم فایل را در نسخه آزمایشی ویرایشگر متن در عمل مشاهده کنید.

خواندن یک فایل از سیستم فایل محلی

اولین موردی که می‌خواهم بررسی کنم این است که از کاربر بخواهم یک فایل را انتخاب کند، سپس آن فایل را از روی دیسک باز کرده و بخواند.

از کاربر بخواهید فایلی را برای خواندن انتخاب کند

نقطه ورود به 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) برای یک فایل دارید، می‌توانید ویژگی‌های فایل را دریافت کنید یا به خود فایل دسترسی پیدا کنید. فعلاً، محتویات آن را می‌خوانم. فراخوانی 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 ) و ذخیره به عنوان (Save As) . ذخیره، تغییرات را با استفاده از شناسه فایل بازیابی شده قبلی، در فایل اصلی می‌نویسد. اما ذخیره به عنوان (Save As) یک فایل جدید ایجاد می‌کند و بنابراین به شناسه فایل جدیدی نیاز دارد.

ایجاد یک فایل جدید

برای ذخیره یک فایل، تابع showSaveFilePicker() را فراخوانی کنید، که انتخابگر فایل را در حالت "ذخیره" نشان می‌دهد و به کاربر اجازه می‌دهد فایل جدیدی را که می‌خواهد برای ذخیره استفاده کند، انتخاب کند. برای ویرایشگر متن، همچنین می‌خواستم که به طور خودکار پسوند .txt را اضافه کند، بنابراین پارامترهای اضافی را ارائه دادم.

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

ذخیره تغییرات روی دیسک

می‌توانید تمام کدهای مربوط به ذخیره تغییرات در یک فایل را در نسخه آزمایشی ویرایشگر متن من در گیت‌هاب پیدا کنید. تعاملات اصلی سیستم فایل در 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.
}

همچنین می‌توانید در داخل جریان (stream) 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);
  }
});

مدیریت و مجوزهای فایل یا دایرکتوری ذخیره شده

از آنجایی که مجوزها همیشه بین جلسات (session) ثابت نمی‌مانند ، باید با استفاده 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' } را به متد ارسال کنید.

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() .

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() اگر آیتم کشیده شده یک فایل باشد، یک promise با شیء FileSystemFileHandle و اگر آیتم کشیده شده یک دایرکتوری باشد، یک promise با شیء 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}`);
    }
  }
});

دسترسی به سیستم فایل خصوصی مبدا

سیستم فایل خصوصی origin یک نقطه پایانی ذخیره‌سازی است که همانطور که از نامش پیداست، برای مبدا صفحه خصوصی است. اگرچه مرورگرها معمولاً این کار را با ذخیره محتویات این سیستم فایل خصوصی origin در جایی از دیسک انجام می‌دهند، اما قرار نیست که این محتویات توسط کاربر قابل دسترسی باشند. به طور مشابه، انتظار نمی‌رود که فایل‌ها یا دایرکتوری‌هایی با نام‌هایی مطابق با نام‌های فرزندان سیستم فایل خصوصی origin وجود داشته باشند. در حالی که مرورگر ممکن است به نظر برسد که فایل‌هایی وجود دارند، از نظر داخلی - از آنجایی که این یک سیستم فایل خصوصی origin است - مرورگر ممکن است این "فایل‌ها" را در یک پایگاه داده یا هر ساختار داده دیگری ذخیره کند. اساساً، اگر از این API استفاده می‌کنید، انتظار نداشته باشید که فایل‌های ایجاد شده را به صورت یک به یک در جایی روی هارد دیسک پیدا کنید. پس از دسترسی به FileSystemDirectoryHandle ریشه، می‌توانید طبق معمول روی سیستم فایل خصوصی origin کار کنید.

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

Browser Support

  • کروم: ۸۶.
  • لبه: ۸۶.
  • فایرفاکس: ۱۱۱.
  • سافاری: ۱۵.۲.

Source

دسترسی به فایل‌های بهینه‌سازی‌شده برای عملکرد از سیستم فایل خصوصی مبدا

سیستم فایل خصوصی origin دسترسی اختیاری به نوع خاصی از فایل را فراهم می‌کند که برای عملکرد بسیار بهینه شده است، به عنوان مثال، با ارائه دسترسی نوشتن درجا و انحصاری به محتوای یک فایل. در Chromium 102 و بالاتر، یک روش اضافی در سیستم فایل خصوصی origin برای ساده‌سازی دسترسی به فایل وجود دارد: 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 توسعه داده‌ایم که در هر کجا که امکان داشته باشد از رابط برنامه‌نویسی کاربردی (API) دسترسی به سیستم فایل استفاده می‌کند و در سایر موارد به این گزینه‌های برتر بعدی رجوع می‌کند.

امنیت و مجوزها

تیم کروم، رابط برنامه‌نویسی کاربردی دسترسی به سیستم فایل (File System Access API) را با استفاده از اصول اصلی تعریف‌شده در «کنترل دسترسی به ویژگی‌های قدرتمند پلتفرم وب» ، از جمله کنترل و شفافیت کاربر و ارگونومی کاربر، طراحی و پیاده‌سازی کرده است.

باز کردن یک فایل یا ذخیره یک فایل جدید

انتخابگر فایل برای باز کردن فایل جهت خواندن
یک انتخابگر فایل که برای باز کردن یک فایل موجود جهت خواندن استفاده می‌شود.

هنگام باز کردن یک فایل، کاربر با استفاده از انتخابگر فایل، مجوز خواندن فایل یا دایرکتوری را می‌دهد. انتخابگر فایل باز فقط با استفاده از یک حرکت کاربر و زمانی که از یک زمینه امن ارائه شود، قابل نمایش است. اگر کاربران نظر خود را تغییر دهند، می‌توانند انتخاب را در انتخابگر فایل لغو کنند و سایت به هیچ چیز دسترسی پیدا نمی‌کند. این همان رفتاری است که در مورد عنصر <input type="file"> وجود دارد.

انتخابگر فایل برای ذخیره فایل روی دیسک.
یک انتخابگر فایل که برای ذخیره فایل روی دیسک استفاده می‌شود.

به طور مشابه، وقتی یک برنامه وب می‌خواهد یک فایل جدید ذخیره کند، مرورگر انتخابگر فایل ذخیره را نشان می‌دهد و به کاربر اجازه می‌دهد نام و مکان فایل جدید را مشخص کند. از آنجایی که آنها یک فایل جدید را در دستگاه ذخیره می‌کنند (به جای بازنویسی یک فایل موجود)، انتخابگر فایل به برنامه اجازه نوشتن در فایل را می‌دهد.

پوشه‌های محدود شده

برای کمک به محافظت از کاربران و داده‌های آنها، مرورگر ممکن است توانایی کاربر را برای ذخیره در پوشه‌های خاص، به عنوان مثال، پوشه‌های اصلی سیستم عامل مانند ویندوز، پوشه‌های کتابخانه macOS محدود کند. وقتی این اتفاق می‌افتد، مرورگر اعلانی را نشان می‌دهد و از کاربر می‌خواهد پوشه دیگری را انتخاب کند.

تغییر یک فایل یا دایرکتوری موجود

یک برنامه وب نمی‌تواند بدون دریافت اجازه صریح از کاربر، فایلی را روی دیسک تغییر دهد.

درخواست اجازه

اگر شخصی بخواهد تغییرات را در فایلی که قبلاً به آن دسترسی خواندن داده است ذخیره کند، مرورگر یک اعلان مجوز نشان می‌دهد که از سایت درخواست اجازه برای نوشتن تغییرات روی دیسک را می‌کند. درخواست مجوز فقط با یک حرکت کاربر، مثلاً با کلیک روی دکمه ذخیره، قابل اجرا است.

قبل از ذخیره فایل، درخواست مجوز نمایش داده می‌شود.
پیامی که قبل از اعطای مجوز نوشتن روی یک فایل موجود به مرورگر، به کاربران نشان داده می‌شود.

از طرف دیگر، یک برنامه وب که چندین فایل را ویرایش می‌کند، مانند یک IDE، می‌تواند در زمان باز شدن، برای ذخیره تغییرات درخواست مجوز کند.

اگر کاربر گزینه لغو را انتخاب کند و دسترسی نوشتن را اعطا نکند، برنامه وب نمی‌تواند تغییرات را در فایل محلی ذخیره کند. باید یک روش جایگزین برای کاربر فراهم کند تا داده‌های خود را ذخیره کند، به عنوان مثال با ارائه راهی برای "دانلود" فایل یا ذخیره داده‌ها در فضای ابری.

شفافیت

آیکون Omnibox
آیکون نوار آدرس که نشان می‌دهد کاربر به وب‌سایت اجازه ذخیره در یک فایل محلی را داده است.

وقتی کاربر به یک برنامه وب اجازه ذخیره یک فایل محلی را می‌دهد، مرورگر یک آیکون در نوار آدرس نشان می‌دهد. با کلیک بر روی آیکون، یک پنجره بازشو باز می‌شود که لیست فایل‌هایی را که کاربر به آنها دسترسی داده است، نشان می‌دهد. کاربر همیشه می‌تواند در صورت تمایل، آن دسترسی را لغو کند.

تداوم مجوز

برنامه وب می‌تواند بدون هیچ پیغامی، تغییرات را در فایل ذخیره کند تا زمانی که همه تب‌های مبدا آن بسته شوند. به محض بسته شدن یک تب، سایت تمام دسترسی‌ها را از دست می‌دهد. دفعه بعد که کاربر از برنامه وب استفاده کند، دوباره برای دسترسی به فایل‌ها از او سوال پرسیده می‌شود.

بازخورد

ما می‌خواهیم درباره تجربیات شما با API دسترسی به سیستم فایل بشنویم.

در مورد طراحی API به ما بگویید

آیا چیزی در مورد API وجود دارد که آنطور که انتظار داشتید کار نمی‌کند؟ یا متدها یا ویژگی‌هایی که برای پیاده‌سازی ایده‌تان به آنها نیاز دارید، وجود ندارند؟ در مورد مدل امنیتی سؤال یا نظری دارید؟

مشکل در اجرا؟

آیا در پیاده‌سازی کروم اشکالی پیدا کردید؟ یا پیاده‌سازی با مشخصات متفاوت است؟

  • یک اشکال را در https://new.crbug.com ثبت کنید. حتماً تا حد امکان جزئیات، دستورالعمل‌های بازتولید و تنظیمات کامپوننت‌ها را روی Blink>Storage>FileSystem قرار دهید.

قصد استفاده از API را دارید؟

آیا قصد دارید از رابط برنامه‌نویسی کاربردی دسترسی به سیستم فایل (File System Access API) در سایت خود استفاده کنید؟ پشتیبانی عمومی شما به ما کمک می‌کند تا ویژگی‌ها را اولویت‌بندی کنیم و به سایر فروشندگان مرورگر نشان می‌دهد که پشتیبانی از آنها چقدر حیاتی است.

  • نحوه‌ی استفاده از آن را در تاپیک گفتمان WICG به اشتراک بگذارید.
  • با استفاده از هشتگ #FileSystemAccess یک توییت به @ChromiumDev ارسال کنید و به ما اطلاع دهید که کجا و چگونه از آن استفاده می‌کنید.

لینک‌های مفید

تقدیرنامه‌ها

مشخصات API دسترسی به فایل سیستم توسط Marijn Kruisselbrink نوشته شده است.