واجهة برمجة التطبيقات File System Access API: تبسيط الوصول إلى الملفات على الجهاز

تسمح واجهة File System Access API لتطبيقات الويب بقراءة التغييرات أو حفظها مباشرةً في الملفات والمجلدات على جهاز المستخدم.

ما هي واجهة برمجة التطبيقات File System Access API؟

تتيح واجهة برمجة التطبيقات File System Access API للمطوّرين إنشاء تطبيقات ويب فعّالة تتفاعل مع الملفات على الجهاز المحلي للمستخدم، مثل حِزم تطوير البرامج المتكاملة ومحرّري الصور والفيديوهات ومحرّري النصوص وغير ذلك. بعد أن يمنح أحد المستخدمين تطبيق ويب إذن الوصول، تسمح له واجهة برمجة التطبيقات هذه بقراءة التغييرات أو حفظها مباشرةً في الملفات والمجلدات على جهاز المستخدم. بالإضافة إلى قراءة الملفات وكتابتها، توفّر واجهة برمجة التطبيقات File System Access API إمكانية فتح دليل وتعداد محتوياته.

إذا سبق لك قراءة الملفات وكتابتها، ستكون معظم المعلومات التي سأشاركها مألوفة لك. ننصحك بقراءتها على أي حال، لأنّ بعض الأنظمة تختلف عن غيرها.

تتوفّر واجهة File System Access API على معظم متصفّحات Chromium على أنظمة التشغيل Windows وmacOS وChromeOS وLinux. يُستثنى من ذلك متصفّح Brave الذي يتوفر فيه الإصدار حاليًا فقط من خلال علامة. يجري العمل على توفير الميزة على أجهزة Android في سياق crbug.com/1011535.

استخدام واجهة برمجة التطبيقات File System Access API

لإظهار مدى فعالية واجهة برمجة التطبيقات File System Access API وفائدتها، كتبتُ ملفًا واحدًا محرر نص. يتيح لك فتح ملف نصي، أو تحريره، أو حفظ التغييرات مرة أخرى على القرص، أو بدء ملف جديد وحفظ التغييرات على القرص. إنه لا شيء فاخر، ولكنه يوفر ما يكفي لمساعدتك على فهم المفاهيم.

دعم المتصفح

توافق المتصفّح

  • الإصدار 86 من متصفّح Chrome
  • ‫Edge: 86
  • Firefox: غير متوافق
  • Safari: غير متوافق

المصدر

رصد الميزات

لمعرفة ما إذا كانت واجهة برمجة التطبيقات File System Access API متوافقة، تحقّق من توفّر طريقة الاختيار التي تريدها.

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

تجربة الميزة

يمكنك الاطّلاع على واجهة برمجة التطبيقات File System Access API في أثناء عملها في عرض توضيحي ل محرِّر النصوص.

قراءة ملف من نظام الملفات على الجهاز

حالة الاستخدام الأولى التي أريد معالجتها هي مطالبة المستخدم باختيار ملف، ثم فتح هذا الملف وقراءته من القرص.

طلب اختيار ملف من المستخدم لقراءته

نقطة الدخول إلى File System Access API هي window.showOpenFilePicker(). عند استدعائه، يعرض مربّع حوار أداة اختيار الملفات، ويطلب من المستخدم اختيار ملف. بعد اختيار ملف، تعرض واجهة برمجة التطبيقات صفيفًا من عناوين الملفات. تتيح لك مَعلمة options الاختيارية إمكانية التأثير في سلوك أداة اختيار الملفات مثلاً من خلال السماح للمستخدم باختيار عدّة ملفات أو أدلة أو أنواع مختلفة من الملفات. بدون تحديد أي خيارات، يسمح أداة اختيار الملفات للمستخدم باختيار ملف واحد. هذا مثالي لمحرر النصوص.

مثل العديد من واجهات برمجة التطبيقات القوية الأخرى، يجب تنفيذ طلب البيانات من 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 يحتوي على ملف نصي. للحصول على البيانات من العنصر المصغّر، يمكنك استدعاء إحدى بُنيته الأساسية، (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;
});

كتابة الملف في نظام الملفات على الجهاز

في محرِّر النصوص، هناك طريقتان لحفظ الملف: حفظ وحفظ باسم. تؤدي عملية الحفظ إلى إعادة كتابة التغييرات في الملف الأصلي باستخدام معرّف الملف الذي تم استرجاعه سابقًا. ولكن الحفظ باسم ينشئ ملفًا جديدًا، وبالتالي يتطلب معرّف ملف جديدًا.

إنشاء ملف جديد

لحفظ ملف، يمكنك استدعاء 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
});

تحديد الغرض من أدوات اختيار الملفات المختلفة

في بعض الأحيان، يكون للتطبيقات منتقي مختلف لأغراض مختلفة. على سبيل المثال، قد يسمح محرِّر النصوص المنسّقة للمستخدم بفتح ملفات نصية، ولكن أيضًا باستيراد الصور. سيتم تلقائيًا فتح كل أداة اختيارملف في آخر موقع تم حفظه. يمكنك تجنُّب ذلك من خلال تخزين قيم id لكل نوع من أدوات الاختيار. في حال تحديد id، سيتذكر تنفيذ أداة اختيار الملفات directory منفصلاً تم استخدامه مؤخرًا لهذا 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()، على سبيل المثال، للحصول على أحجام ملف individual ، لا تستخدِم 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 هي "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}`);
    }
  }
});

الوصول إلى نظام الملفات الخاص الأصلي

إنّ نظام الملفات الخاص المصدر هو نقطة نهاية تخزين، كما يوحي اسمها، هي خاصة بالنسبة إلى مصدر الصفحة. على الرغم من أنّ المتصفّحات عادةً ما تُنفّذ ذلك من خلال الاحتفاظ بمحتوى نظام الملفات الخاص هذا المصدر على القرص في مكان ما، لا يُقصد أن يتمكّن المستخدم من الوصول إلى المحتوى. وبالمثل، لا نتوقع أن تتوفّر ملفات أو أدلة بأسماء تتطابق مع أسماء العناصر الفرعية لنظام الملفات الخاص الأصلي. قد يبدو المتصفّح أنّ هناك ملفات، داخليًا، بما أنّ هذا نظام ملفات خاص ذات مصدر خاص، قد يخزِّن المتصفّح هذه "الملفات" في قاعدة بيانات أو أي بنية بيانات أخرى. بشكل أساسي، إذا كنت تستخدم واجهة برمجة التطبيقات هذه، لا تتوقّع العثور على الملفات التي تم إنشاؤها متطابقة بشكلٍ فردي في مكان ما على القرص الصلب. يمكنك العمل كالمعتاد على نظام الملفات الخاص الأصلي بعد أن تتمكّن من الوصول إلى الجذر 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 من متصفّح Chrome
  • الحافة: 86.
  • Firefox: 111.
  • ‫Safari: 15.2

المصدر

الوصول إلى الملفات المحسّنة لتحقيق أداء أفضل من نظام الملفات الخاص الأصلي

يوفر نظام الملفات الخاص الأصلي إمكانية وصول اختيارية إلى نوع خاص من الملفات التي تم تحسينها بشكلٍ كبير لتحسين الأداء، على سبيل المثال، من خلال توفير إمكانية الوصول إلى محتوى الملف بدلاً من نسخه وإمكانية الوصول الحصري للكتابة إلى محتوى الملف. في الإصدار 102 من Chromium والإصدارات الأحدث، تتوفّر طريقة إضافية في نظام الملفات الخاص الأصلي ل تبسيط الوصول إلى الملفات: 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 });

polyfill

لا يمكن تعويض طرق واجهة برمجة التطبيقات 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 ونفّذها باستخدام المبادئ الأساسية المحدّدة في مقالة التحكّم في الوصول إلى ميزات Web Platform القوية، بما في ذلك التحكّم والشفافية للمستخدمين وملاءمة الاستخدام.

فتح ملف أو حفظ ملف جديد

أداة اختيار الملفات لفتح ملف وقراءته
أداة اختيار ملفات تُستخدم لفتح ملف حالي للقراءة.

عند فتح ملف، يوفر المستخدم إذنًا لقراءة ملف أو دليل باستخدام منتقي الملفات. لا يمكن عرض أداة اختيار الملفات المفتوحة إلا باستخدام إيماءة المستخدم عند عرضها من سياق آمن. إذا غير المستخدمون رأيهم، فيمكنهم إلغاء التحديد في منتقي الملفات ولن يتمكن الموقع من الوصول إلى أي شيء. هذا هو سلوك العنصر <input type="file"> نفسه.

أداة اختيار الملفات لحفظ ملف على القرص
أداة اختيار ملفات تُستخدَم لحفظ ملف على القرص.

وبالمثل، عندما يريد تطبيق ويب حفظ ملف جديد، يعرض المتصفّح أداة اختيار حفظ الملف، مما يتيح للمستخدم تحديد اسم الملف الجديد وموقعه. بما أنّه يتم حفظ ملف جديد على الجهاز (بدلاً من استبدال ملف حالي)، يمنح أداة اختيار الملفات التطبيق إذنًا للقيام بكتابة البيانات في الملف.

المجلدات المحظورة

للمساعدة في حماية المستخدمين وبياناتهم، قد يحدّ المتصفّح من قدرة المستخدم على الحفظ في مجلدات معيّنة، على سبيل المثال، مجلدات نظام التشغيل الأساسية مثل مجلدات مكتبة macOS وWindows. وعندما يحدث ذلك، يعرض المتصفّح رسالة مطالبة ويطلب من المستخدم اختيار مجلد مختلف.

تعديل ملف أو دليل حالي

لا يمكن لتطبيق ويب تعديل ملف على القرص بدون الحصول على إذن صريح من المستخدم.

طلب الإذن

إذا أراد مستخدم حفظ تغييرات على ملف سبق له منحه إذن الوصول للقراءة، يعرض المتصفّح طلبًا للحصول على إذن يطلب من الموقع الإلكتروني إذنًا بإدخال تغييرات على القرص. لا يمكن بدء طلب الإذن إلا من خلال إيماءة المستخدم، على سبيل المثال، بالنقر على زر "حفظ" .

يتم عرض طلب الإذن قبل حفظ ملف.
يظهر هذا الطلب للمستخدمين قبل منح المتصفّح إذن تعديل لملف حالي.

بدلاً من ذلك، يمكن لتطبيق ويب يعدّل ملفات متعددة، مثل IDE، أن يطلب أيضًا إذنًا لحفظ التغييرات في وقت الفتح.

إذا اختار المستخدم "إلغاء"، ولم يمنح إذن الوصول للكتابة، لن يتمكّن تطبيق الويب من حفظ التغييرات في الملف المحلي. يجب أن يوفّر التطبيق طريقة بديلة للمستخدم لحفظ بياناته، مثلاً، من خلال توفير طريقة "لتنزيل" الملف أو حفظ البيانات في السحابة الإلكترونية.

الشفافية

رمز المربّع المتعدد الاستخدامات
رمز شريط العناوين يشير إلى أنّ المستخدم منح الموقع الإلكتروني الإذن للحفظ في ملف على الجهاز

بعد أن يمنح المستخدم إذنًا لتطبيق ويب لحفظ ملف محلي، يعرض المتصفح رمزًا في شريط العناوين. يؤدي النقر على الرمز إلى فتح نافذة منبثقة تعرض قائمة الملفات التي منح المستخدم إذنًا بالوصول إليها. ويمكن للمستخدم في أي وقت إلغاء هذا الإذن إذا اختار ذلك.

الاحتفاظ بالأذونات

يمكن لتطبيق الويب مواصلة حفظ التغييرات في الملف بدون طلب موافقتك إلى أن يتم إغلاق جميع علامات التبويب التي تشير إلى مصدره. بعد إغلاق علامة تبويب، يفقد الموقع الإلكتروني إمكانية الوصول إلى جميع البيانات. وفي المرة التالية التي يستخدم فيها المستخدم تطبيق الويب، سيُطلب منه مرة أخرى الوصول إلى الملفات.

ملاحظات

نريد معرفة تجاربك مع واجهة برمجة التطبيقات File System Access API.

أخبِرنا عن تصميم واجهة برمجة التطبيقات

هل هناك مشكلة في واجهة برمجة التطبيقات لا تعمل على النحو المتوقّع؟ هل هناك طُرق أو سمات مفقودة تحتاجها لتنفيذ فكرتك؟ هل لديك سؤال أو تعليق حول ملف أمان الحساب؟

هل هناك مشكلة في التنفيذ؟

هل رصدت خطأ في عملية تنفيذ Chrome؟ أم أن التنفيذ يختلف عن المواصفات؟

  • يمكنك إرسال بلاغ عن خطأ على الرابط https://new.crbug.com. احرص على تضمين أكبر قدر ممكن من التفاصيل، وتعليمات لإعادة إنتاج الخطأ، وضبط المكوّنات على Blink>Storage>FileSystem. يعمل تطبيق Glitch بشكل رائع لمشاركة عمليات إعادة التقديم السريعة.

هل تخطّط لاستخدام واجهة برمجة التطبيقات؟

هل تخطّط لاستخدام واجهة برمجة التطبيقات File System Access API على موقعك الإلكتروني؟ ويساعدنا الدعم العام الذي تقدّمه لنا في تحديد أولويات الميزات، كما يُظهر لموردي المتصفحات الآخرين مدى أهمية دعمهم لها.

روابط مفيدة

الشكر والتقدير

كتب مارين كروسيلبرينك مواصفات File System Access API.