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

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

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

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

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

تتوفّر واجهة File System Access API حاليًا على معظم متصفّحات Chromium على أنظمة التشغيل Windows وmacOS وChromeOS وLinux. وأبرز استثناءات هي لعبة Brave التي تكون متاحة حاليًا خلف العلم فقط. يتوافق Android مع جزء نظام الملفات الخاصة للمصدر من واجهة برمجة التطبيقات اعتبارًا من الإصدار 109 من Chromium. لا تتوفّر حاليًا أي خطط لاستخدام طُرق المنتقي ، ولكن يمكنك تتبُّع مدى التقدّم المحتمل من خلال تمييز 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(). عند استدعائه، يظهر مربع حوار منتقي الملفات، ويطلب من المستخدم تحديد ملف. بعد اختيار ملف، تعرض واجهة برمجة التطبيقات مجموعة من الأسماء المعرِّفة للملفات. تتيح لك مَعلمة 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 يحتوي على كائن ثنائي كبير. للحصول على البيانات من الكائن الثنائي الكبير (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 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() سلسلة، وهي مطلوبة لمحرِّر النصوص. ولكن قد يأخذ أيضًا 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 في 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
  • 86
  • 111
  • 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 });

الملء التلقائي

لا يمكن تعويض طرق واجهة برمجة التطبيقات 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.

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

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

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

هل واجهت خطأً في تنفيذ Chrome؟ أم أنّ التنفيذ مختلف عن المواصفات؟

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

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

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

روابط مفيدة

شكر وتقدير

تمت كتابة مواصفات File System Access API من قِبل Marijn Kruisselbrink.