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

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

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

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

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

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

با استفاده از File System Access API

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

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

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

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

منبع

تشخیص ویژگی

برای اطلاع از پشتیبانی از File System Access API، بررسی کنید که آیا روش انتخابگر مورد علاقه شما وجود دارد یا خیر.

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

آن را امتحان کنید

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، یکی از متدهای آن ( slice() ، stream() ، text() یا arrayBuffer() ) را فراخوانی کنید.

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

شی File که توسط FileSystemFileHandle.getFile() برگردانده می شود تنها تا زمانی قابل خواندن است که فایل زیرین روی دیسک تغییر نکرده باشد. اگر فایل روی دیسک اصلاح شود، شی File غیرقابل خواندن می شود و شما باید دوباره getFile() را فراخوانی کنید تا یک شی File جدید برای خواندن داده های تغییر یافته دریافت کنید.

همه را کنار هم گذاشتن

هنگامی که کاربران روی دکمه Open کلیک می کنند، مرورگر یک انتخابگر فایل را نشان می دهد. هنگامی که آنها یک فایل را انتخاب کردند، برنامه محتویات را می خواند و آنها را در <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 یک فایل جدید ایجاد می کند و بنابراین به یک دسته فایل جدید نیاز دارد.

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

برای ذخیره یک فایل، 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() یک رشته می گیرد که برای یک ویرایشگر متن مورد نیاز است. اما می تواند یک منبع بافر یا یک 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' } به متد ارسال کنید.

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() یک وعده را با یک شی FileSystemFileHandle برمی گرداند اگر آیتم کشیده شده یک فایل باشد، و یک وعده با یک شی FileSystemDirectoryHandle اگر مورد کشیده شده یک دایرکتوری باشد. فهرست زیر این را در عمل نشان می دهد. توجه داشته باشید که DataTransferItem.kind واسط Drag and Drop برای فایل ها و دایرکتوری ها "file" است، در حالی که FileSystemHandle.kind از File System Access 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> شبیه سازی کرد.

ما کتابخانه‌ای به نام مرورگر-fs-access ایجاد کرده‌ایم که هر جا ممکن است از API دسترسی به فایل سیستم استفاده می‌کند و در سایر موارد به بهترین گزینه‌های بعدی بازمی‌گردد.

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

تیم Chrome با استفاده از اصول اصلی تعریف شده در کنترل دسترسی به ویژگی‌های قدرتمند پلتفرم وب ، از جمله کنترل کاربر و شفافیت، و ارگونومی کاربر، API دسترسی به سیستم فایل را طراحی و پیاده‌سازی کرده است.

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

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

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

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

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

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

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

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

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

درخواست مجوز

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

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

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

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

شفافیت

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

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

تداوم مجوز

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

بازخورد

ما می‌خواهیم در مورد تجربیات شما با File System Access API بشنویم.

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

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

مشکل در اجرا؟

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

  • یک اشکال را در https://new.crbug.com ثبت کنید. اطمینان حاصل کنید که تا جایی که می توانید جزئیات، دستورالعمل هایی برای بازتولید، و Components را روی Blink>Storage>FileSystem قرار دهید. Glitch برای به اشتراک گذاری مجدد سریع کار می کند.

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

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

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

لینک های مفید

قدردانی ها

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