تستكشف هذه الدراسة الحالة كيفية استخدام Kiwix، وهي مؤسسة غير ربحية، لتكنولوجيا التطبيقات الويب المتطورة وFile System Access API لتمكين المستخدمين من تنزيل وحفظ أرشيفات الإنترنت الكبيرة لاستخدامها بلا إنترنت. تعرَّف على التنفيذ الفني لرمز المصدر الخاص بنظام الملفات (OPFS)، وهو ميزة جديدة للمتصفّح في تطبيق Kiwix المتوافق مع الأجهزة الجوّالة (PWA) تعمل على تحسين إدارة الملفات، وتوفير إمكانية وصول محسّنة إلى المحفوظات بدون طلبات الحصول على الأذونات. تتناول المقالة التحديات وتسلّط الضوء على التطورات المستقبلية المحتملة في نظام الملفات الجديد.
لمحة عن Kiwix
بعد مرور أكثر من 30 عامًا على ولادة الويب، لا يزال ثلث سكان العالم في انتظار إمكانية الوصول إلى الإنترنت بشكل موثوق وفقًا لاتحاد الاتصالات الدولي. هل هذا هو مكان انتهاء القصة؟ بالطبع لا. طوّر فريق Kiwix، وهي منظمة غير ربحية مقرّها سويسرا، منظومة متكاملة من التطبيقات والمحتوى المفتوحَين المصدر بهدف إتاحة المعرفة للأشخاص الذين لا يمكنهم الوصول إلى الإنترنت أو يمكنهم الوصول إليه بشكل محدود. وتعتمد هذه الفكرة على أنّه إذا لم يكن بإمكان أنت الوصول إلى الإنترنت بسهولة، يمكن لشخص ما تنزيل الموارد الرئيسية نيابةً عنك في المكان والوقت اللذين يتوفّر فيهما الاتصال بالإنترنت، ثم تخزينها على الجهاز لاستخدامها لاحقًا بلا إنترنت. يمكن الآن تحويل العديد من المواقع الإلكترونية المهمة، مثل Wikipedia أو Project Gutenberg أو Stack Exchange أو حتى محادثات TED، إلى أرشيفات مضغوطة للغاية تُعرف باسم ملفات ZIM، ويمكن قراءتها على الفور باستخدام متصفّح Kiwix.
تستخدم أرشيفات ZIM ضغط Zstandard (ZSTD) الفعّال للغاية (كانت الإصدارات القديمة تستخدم XZ)، ويُستخدَم هذا الضغط في الغالب لتخزين HTML وJavaScript وCSS، في حين يتم عادةً تحويل الصور إلى تنسيق WebP المضغوط. يتضمّن كل ملف ZIM أيضًا عنوان URL وفهرسة العناوين. فالضغط هو المفتاح هنا، حيث يتم ضغط كل محتوى ويكيبيديا باللغة الإنجليزية (6.4 مليون مقالة بالإضافة إلى صورة) إلى 97 غيغابايت بعد التحويل إلى تنسيق ZIM، الذي يبدو كثيرًا إلى أن تدرك أن مجموع كل المعارف البشرية يمكن أن يتناسب الآن مع هاتف Android متوسط المواصفات. تتوفّر أيضًا العديد من المراجع الأصغر حجمًا، بما في ذلك نُسخ مخصّصة من Wikipedia، مثل الرياضيات والطب وما إلى ذلك.
تقدّم Kiwix مجموعة من التطبيقات المتوافقة مع الأجهزة فقط التي تستهدف استخدام أجهزة الكمبيوتر المكتبي (Windows/Linux/macOS) والأجهزة الجوّالة (iOS/Android). ومع ذلك، ستتمحور دراسة الحالة هذه عن تطبيق الويب التقدّمي (PWA) الذي يهدف إلى أن يكون حلاً شاملاً وبسيطًا لأي جهاز مزوّد بمتصفّح حديث.
سنلقي نظرة على التحديات التي تواجه تطوير تطبيق ويب شامل يحتاج إلى توفير إمكانية الوصول السريع إلى أرشيفات المحتوى الكبيرة بلا إنترنت، وبعض واجهات برمجة تطبيقات JavaScript الحديثة، ولا سيما واجهتَي برمجة التطبيقات File System Access API وOrigin Private File System، التي توفّر حلولاً مبتكرة ومثيرة لهذه التحديات.
هل هو تطبيق ويب للاستخدام بلا إنترنت؟
يمثّل مستخدمو Kiwix مجموعة متنوعة لديهم العديد من الاحتياجات المختلفة، ولا تملك Kiwix سوى تحكّم بسيط أو لا تحكّم على الإطلاق في الأجهزة وأنظمة التشغيل التي سيتم من خلالها الوصول إلى المحتوى. قد تكون بعض هذه الأجهزة بطيئة أو قديمة، خاصةً في المناطق ذات الدخل المنخفض في العالم. في حين أنّ Kiwix تحاول تغطية أكبر عدد ممكن من حالات الاستخدام، لاحظت المؤسسة أيضًا أنّه يمكنها الوصول إلى عددٍ أكبر من المستخدمين باستخدام أوسع برنامج متاح على أي جهاز: متصفح الويب. لذلك، استنادًا إلى قانون أتوودف الذي ينص على أنّ أي تطبيق يمكن كتابته بلغة JavaScript سيتم كتابته باستخدامها في نهاية المطاف، بدأ بعض مطوّري Kiwix قبل 10 سنوات تقريبًا في نقل برنامج Kiwix من C++ إلى JavaScript.
كان الإصدار الأول من هذا المتوافق، المُسمى Kiwix HTML5، مخصّصًا لنظام التشغيل Firefox OS الذي تم إيقافه نهائيًا الآن ولإضافات المتصفّح. كان (ولا يزال) في الأساس محرك فك ضغط برمجيًا (XZ وZSTD) مكتوبًا بلغة C++ وتم تجميعه باستخدام لغة JavaScript المُعدّة للاستخدام على الويب (ASM.js) ثم Wasm أو WebAssembly باستخدام مجمّع Emscripten. تم تغيير اسم إضافات المتصفّح لاحقًا إلى Kiwix JS، ولا تزال تتم برمجة هذه الإضافات بشكلٍ نشط.
أدخِل عنوان URL لملف APK لتطبيق الويب التقدّمي (PWA). وإدراكًا منهم لإمكانات هذه التكنولوجيا، أنشأ مطوّرو Kiwix إصدارًا مخصّصًا من التطبيقات المصمّمة للتشغيل على الويب من Kiwix JS، وبدأوا في إضافة عمليات دمج مع أنظمة التشغيل تتيح للتطبيق توفير إمكانات مشابهة للتطبيقات الأصلية، لا سيما في ما يتعلّق باستخدامه بدون اتصال بالإنترنت، وتثبيته، ومعالجة الملفات، والوصول إلى نظام الملفات.
إنّ تطبيقات الويب التقدّمية التي تعمل بلا إنترنت هي تطبيقات خفيفة للغاية، لذا فهي مثالية للسياقات التي يكون فيها الإنترنت الجوّال متقطّعًا أو باهظ التكلفة. التكنولوجيا المستخدَمة في هذا الإجراء هي Service Worker API وCache API ذات الصلة، والتي تستخدمها كل التطبيقات المستندة إلى مكتبة Kiwix JS. تسمح واجهات برمجة التطبيقات هذه للتطبيقات بالعمل كخادم، من خلال اعتراض طلبات الجلب من المستند أو المقالة الرئيسيَين المعروضَين، وإعادة توجيهها إلى الخلفيّة (JS) لاستخراج استجابة وإنشاءها من أرشيف ZIM.
مساحة تخزين في كل مكان
نظرًا للحجم الكبير لأرشيفات ZIM، يشكّل التخزين والوصول إليها، خاصةً على الأجهزة الجوّالة، أكبر مشكلة تواجه مطوّري Kiwix. ينزِّل العديد من مستخدمي Kiwix النهائيين المحتوى داخل التطبيق، عندما يكون الإنترنت متاحًا، لاستخدامه لاحقًا بلا إنترنت. ينزِّل مستخدمون آخرون المحتوى على جهاز كمبيوتر باستخدام برنامج تورنت، ثم ينقلونه إلى جهاز جوّال أو جهاز لوحي، ويتبادل بعض المستخدمين المحتوى على فلاشات USB أو أقراص صلبة محمولة في المناطق التي تتوفر فيها خدمة إنترنت جوّال متقطّعة أو باهظة الثمن. يجب أن تتيح كل طرق الوصول إلى المحتوى من مواقع جغرافية عشوائية يمكن للمستخدم الوصول إليها بواسطة Kiwix JS وKiwix PWA.
إنّ واجهة برمجة التطبيقات File API هي ما سمح لتطبيق Kiwix JS في البداية بقراءة أرشيفات ضخمة تبلغ مساحتها
مئات غيغابايت
(يبلغ حجم
أحد أرشيفات ZIM 166 غيغابايت)، حتى على الأجهزة ذات الذاكرة المنخفضة. تتوفّر واجهة برمجة التطبيقات هذه
بشكل عام في أي متصفّح، حتى في
المتصفّحات القديمة جدًا، وبالتالي، تُستخدَم كخيار احتياطي عام في حال عدم توفّر واجهات برمجة تطبيقات أحدث. إنّ عملية تحديد عنصر input
في HTML سهلة جدًا، كما هو الحال في Kiwix:
<input
type="file"
accept="application/octet-stream,.zim,.zimaa,.zimab,.zimac, ..."
value="Select folder with ZIM files"
id="archiveFilesLegacy"
multiple
/>
بعد الاختيار، يحتوي عنصر الإدخال على عناصر File التي هي في الأساس بيانات وصفية تشير إلى البيانات الأساسية في مساحة التخزين. من الناحية الفنية، تقرأ البنية الأساسية الموجَّهة للكائنات في Kiwix، والتي تمت كتابتها بلغة JavaScript من جهة العميل، قطاعات صغيرة من الأرشيف الكبير حسب الحاجة. إذا كان من الضروري إزالة ضغط هذه الشرائح، تُرسِلها الخلفية إلى أداة إزالة الضغط Wasm، وتحصل على شرائح أخرى إذا طُلب ذلك، إلى أن تتم إزالة ضغط ملف blob كامل (عادةً ما يكون مقالة أو مادة عرض). وهذا يعني أنّه لا يجب أبدًا قراءة الأرشيف الكبير بالكامل في الذاكرة.
على الرغم من أنّ واجهة برمجة التطبيقات File API عالمية، إلا أنّها تتضمن عيوبًا جعلت تطبيقات Kiwix JS تبدو معقدة وقديمية مقارنةً بالتطبيقات الأصلية: فهي تتطلب من المستخدم اختيار الأرشيفات باستخدام أداة اختيار الملفات، أو السحب والإفلات ملف في التطبيق، في كل مرة يتم فيها تشغيل التطبيق، لأنّه لا تتوفّر من خلال واجهة برمجة التطبيقات هذه طريقة للحفاظ على أذونات الوصول من جلسة إلى أخرى.
للحدّ من هذه التجربة السيئة للمستخدم، اتّبع مطوّرو JavaScript في Kiwix في البداية مسار Electron، مثل العديد من المطوّرين. ElectronJS هو إطار عمل رائع يقدّم ميزات فعّالة، بما في ذلك الوصول الكامل إلى نظام الملفات باستخدام واجهات برمجة تطبيقات Node. ومع ذلك، لهذه الطريقة بعض السلبيات المعروفة:
- ولا يمكن تشغيله إلا على أنظمة التشغيل المخصّصة للكمبيوتر المكتبي.
- حجمه كبير (من 70 إلى 100 ميغابايت).
يُرجى العِلم أنّ حجم تطبيقات Electron يتجاوز 5.1 ميغابايت بكثير، وذلك لأنّ كل تطبيق يتضمّن نسخة كاملة من Chromium.
هل كانت هناك طريقة يمكن أن تحسّن بها Kiwix من تجربة مستخدمي التطبيق المتوافق مع الأجهزة الجوّالة؟
واجهة برمجة التطبيقات File System Access API لإنقاذ الموقف
في عام 2019 تقريبًا، تعرّف فريق Kiwix على واجهة برمجة تطبيقات جديدة كانت تخضع لاختبار أولي في Chrome 78، وكان اسمها آنذاك Native File System API. ووعدنا بأنّه سيكون بإمكاننا الحصول على معرّف ملف لملف أو مجلد وتخزينه في قاعدة بيانات IndexedDB. من المهم أن يظل الاسم المعرِّف هذا محفوظًا بين جلسات التطبيق، حتى لا يضطر المستخدم إلى اختيار الملف أو المجلد مرة أخرى عند إعادة تشغيل التطبيق (مع أنّه عليهم الإجابة عن طلب إذن سريع). وبحلول مرحلة الطرح، تمت إعادة تسميتها باسم File System Access API، وتمت توحيد الأجزاء الأساسية من قِبل WHATWG باسم File System API (FSA).
إذن، كيف يعمل جزء File System Access من واجهة برمجة التطبيقات؟ في ما يلي بعض النقاط المهمة التي يجب أخذها في الاعتبار:
- وهي واجهة برمجة تطبيقات غير متزامنة (باستثناء الوظائف المخصّصة في Web Workers).
- يجب تشغيل أدات اختيار الملفات أو الأدلة آليًا من خلال تسجيل إيماءة المستخدم (النقر على عنصر واجهة مستخدم).
- لكي يمنح المستخدم الإذن مرة أخرى للوصول إلى ملف تم اختياره سابقًا (في جلسة جديدة)، يجب أيضًا إجراء إيماءة من المستخدم، وفي الواقع سيرفض المتصفّح عرض طلب الإذن إذا لم يتم بدؤه من خلال إيماءة من المستخدم.
إنّ الرمز البرمجي بسيط نسبيًا، باستثناء الحاجة إلى استخدام واجهة برمجة التطبيقات المعقدة IndexedDB API لتخزين أسماء المعرّفات الخاصة بالملف والدليل. والخبر السار هو أنّ هناك بضع مكتبات تُنجز الكثير من المهام الصعبة نيابةً عنك، مثل مكتبة browser-fs-access. في Kiwix JS، قرّرنا العمل مباشرةً مع واجهات برمجة التطبيقات التي تم documenting بشكل جيد للغاية.
فتح أدات اختيار الملفات والأدلة
يظهر فتح أداة اختيار الملفات على النحو التالي (يتم استخدام Promises هنا، ولكن إذا كان
يعجبك async/await
sugar، يمكنك الاطّلاع على الدليل التعليمي لبرنامج Chrome للمطوّرين):
return window
.showOpenFilePicker({ multiple: false })
.then(function (fileHandles) {
return processFileHandle(fileHandles[0]);
})
.catch(function (err) {
// This is normal if app is launching
console.warn(
'User cancelled, or cannot access fs without user gesture',
err,
);
});
يُرجى العِلم أنّ هذا الرمز لا يعالج سوى الملف الأول الذي تم اختياره (ويحظر اختيار أكثر من ملف واحد) وذلك بهدف التبسيط. إذا أردت السماح باختيار
ملفات متعددة باستخدام { multiple: true }
، ما عليك سوى تضمين كل التعهدات التي تتمثّل في
معالجة كل اسم معرِّف في عبارة Promise.all().then(...)
، على سبيل المثال:
let promisesForFiles = fileHandles.map(function (fileHandle) {
return processFileHandle(fileHandle);
});
return Promise.all(promisesForFiles).then(function (arrayOfFiles) {
// Do something with the files array
console.log(arrayOfFiles);
}).catch(function (err) {
// Handle any errors that occurred during processing
console.error('Error processing file handles!', err);
)};
ومع ذلك، يمكن اختيار ملفات متعددة بشكل أفضل من خلال مطالبة المستخدم بتحديد المجلد الذي يحتوي على هذه الملفات بدلاً من الملفات الفردية فيه، خاصةً أنّ مستخدمي Kiwix يميلون إلى تنظيم جميع ملفات ZIM في المجلد نفسه. رمز تشغيل أداة اختيار الدليل هو
نفسه تقريبًا كما هو موضّح أعلاه باستثناء استخدام
window.showDirectoryPicker.then(function (dirHandle) { … });
.
معالجة معرّف الملف أو الدليل
بعد الحصول على الاسم المعرِّف، عليك معالجته، لذا يمكن أن تبدو الدالة
processFileHandle
على النحو التالي:
function processFileHandle(fileHandle) {
// Serialize fileHandle to indexedDB
serializeFSHandletoIdxDB('pickedFSHandle', fileHandle, function (val) {
console.debug('IndexedDB responded with ' + val);
});
return fileHandle.getFile().then(function (file) {
// Do something with the file
return file;
});
}
يُرجى العلم أنّه عليك تقديم الدالة لتخزين معرّف الملف، ولا تتوفّر
طُرق مساعدة لإجراء ذلك، ما لم تستخدم مكتبة تجريدية. يمكن الاطّلاع على كيفية تنفيذ Kiwix لهذه الميزة في الملف
cache.js
،
ولكن يمكن تبسيطها بشكل كبير إذا تم استخدامها فقط لتخزين اسم ملف أو مجلد واسترداده.
إنّ معالجة الأدلة أكثر تعقيدًا بعض الشيء لأنّك يجب أن تكرر
التنقّل في الإدخالات في الدليل الذي تم اختياره باستخدام دالة entries.next()
غير المتزامنة للعثور على
الملفات أو أنواع الملفات التي تريدها. هناك طرق مختلفة لإجراء ذلك، ولكن
هذا هو الرمز البرمجي المستخدَم في تطبيق Kiwix PWA، بشكل مخطّط:
let iterableEntryList = dirHandle.entries();
return iterateAsyncDirEntries(iterableEntryList, []).then(function (entryList) {
// Do something with the entry list
return entryList;
});
/**
* Iterates FileSystemDirectoryHandle iterator and adds entries to an array
* @param {Iterator} entries An asynchronous iterator of entries
* @param {Array} archives An array to which to add the entries (may be empty)
* @return {Promise<Array>} A Promise for an array of entries in the directory
*/
function iterateAsyncDirEntries(entries, archives) {
return entries
.next()
.then(function (result) {
if (!result.done) {
let entry = result.value[1];
// Filter for the files you want
if (/\.zim(\w\w)?$/i.test(entry.name)) {
archives.push(entry);
}
return iterateAsyncDirEntryArray(entries, archives);
} else {
// We've processed all the entries
if (!archives.length) {
console.warn('No archives found in the picked directory!');
}
return archives;
}
})
.catch(function (err) {
console.error('There was an error processing the directory!', err);
});
}
يُرجى العلم أنّه لكل إدخال في entryList
، ستحتاج لاحقًا إلى الحصول على الملف
باستخدام entry.getFile().then(function (file) { … })
عندما تحتاج إلى استخدامه، أو
المكافئ باستخدام const file = await entry.getFile()
في
async function
.
هل يمكننا تحسين الأداء أكثر؟
إنّ طلب منح الإذن من المستخدم الذي يبدأ بإيماءة المستخدم عند عمليات التشغيل اللاحقة للتطبيق يضيف قدرًا بسيطًا من الصعوبة إلى (إعادة) فتح الملفات والمجلدات، ولكنّه لا يزال أكثر سلاسة من إجبار المستخدم على إعادة اختيار ملف. يعمل مطوّرو Chromium حاليًا على وضع اللمسات الأخيرة على الرمز البرمجي الذي سيسمح بمنح أذونات دائمة لتطبيقات الويب المتوافقة مع الأجهزة الجوّالة المثبَّتة. وهذا هو ما كان يطلبه الكثير من مطوّري التطبيقات المتوافقة مع الأجهزة الجوّالة (PWA)، وهو ما يُتوقّع أن يُحقّق نجاحًا كبيرًا.
ماذا لو لم يكن علينا الانتظار؟ اكتشف مطوّرو Kiwix مؤخرًا أنّه
يمكنهم إزالة جميع طلبات الأذونات في الوقت الحالي، وذلك باستخدام ميزة
جديدة رائعة لواجهة برمجة التطبيقات File Access API التي تتيحها كل من متصفحات Chromium وFirefox (تتيحها أيضًا Safari جزئيًا، ولكن لا تزال
غير متاحة FileSystemWritableFileStream
).
وهذه الميزة الجديدة هي Origin Private File System.
استخدام نظام ملفات أصلي بالكامل: نظام ملفات Origin Private File System
لا يزال نظام Origin Private File System (OPFS) ميزة تجريبية في تطبيق Kiwix PWA، ولكن الفريق متحمّس جدًا لتشجيع المستخدمين على تجربته لأنّه يسدّ الفجوة بين التطبيقات المتوافقة مع الأجهزة الجوّالة والتطبيقات المتوافقة مع الويب. في ما يلي المزايا الرئيسية:
- يمكن الوصول إلى الأرشيفات في "نظام إدارة الملفات في وضع الاستضافة" بدون طلب أذونات، حتى عند التشغيل. يمكن للمستخدمين استئناف قراءة مقالة وتصفّح أرشيف من حيث توقفوا في جلسة سابقة، بدون أي مشاكل على الإطلاق.
- ويوفر إمكانية وصول محسّنة للغاية إلى الملفات المخزّنة فيه: على أجهزة Android، نلاحظ تحسينات في السرعة تتراوح بين خمس و10 مرات أسرع.
إنّ الوصول العادي إلى الملفات في Android باستخدام واجهة برمجة التطبيقات File API يكون بطيئًا بشكلٍ مؤلم، خاصةً (كما هو الحال غالبًا لمستخدمي Kiwix) إذا تم تخزين أرشيفات كبيرة على بطاقة microSD بدلاً من مساحة تخزين الجهاز. تغيّر كل ذلك مع واجهة برمجة التطبيقات الجديدة هذه. على الرغم من أنّ معظم المستخدمين لن يتمكّنوا من تخزين ملف بحجم 97 غيغابايت في نظام OPFS (الذي يستهلك مساحة تخزين الجهاز، وليس مساحة تخزين بطاقة microSD)، إلا أنّه مثالي لتخزين أرشيفات صغيرة أو متوسطة الحجم. هل تريد قراءة الموسوعة الطبية الأكثر اكتمالاً من WikiProject Medicine؟ لا مشكلة، يمكن تخزين هذا الملف بسهولة في نظام OPFS بسعة 1.7 غيغابايت. (ملاحظة: ابحث عن other → mdwiki_en_all_maxi في المكتبة داخل التطبيق).
آلية عمل "الملف الشخصي للإعلانات"
OPFS هو نظام ملفات يوفّره المتصفّح، وهو منفصل لكل مصدر، ويشبه مساحة التخزين على مستوى التطبيق في Android. يمكن استيراد الملفات إلى OPFS من نظام الملفات المرئي للمستخدم، أو يمكن تنزيلها مباشرةً إليه (تسمح واجهة برمجة التطبيقات أيضًا بإنشاء ملفات في OPFS). وبعد نقلها إلى مساحة التخزين المؤقتة، يتم عزلها عن بقية الجهاز. على أجهزة الكمبيوتر المكتبي المتصفّحات المستندة إلى Chromium، من الممكن أيضًا تصدير الملفات من OPFS إلى نظام الملفات المرئي للمستخدم.
لاستخدام نظام OPFS، تكون الخطوة الأولى هي طلب الوصول إليه باستخدام navigator.storage.getDirectory()
(مرة أخرى، إذا كنت تفضّل الاطّلاع على الرمز باستخدام await
، يُرجى قراءة نظام Origin Private File System):
return navigator.storage
.getDirectory()
.then(function (handle) {
return processDirHandle(handle);
})
.catch(function (err) {
console.warn('Unable to get the OPFS directory entry', err);
});
الاسم المعرِّف الذي تحصل عليه من هذا الإجراء هو النوع نفسه من
FileSystemDirectoryHandle
الذي تحصل عليه من window.showDirectoryPicker()
المذكور أعلاه، ما يعني أنّه يمكنك إعادة استخدام الرمز الذي يعالج ذلك (وهناك
خبر سارّ وهو أنّه ما مِن حاجة إلى تخزين هذا الرمز في indexedDB
، ما عليك سوى الحصول عليه عند
احتياجك إليه). لنفترض أنّ لديك بعض الملفات في OPFS وتريد
استخدامها، باستخدام الدالة iterateAsyncDirEntries()
الموضّحة سابقًا،
يمكنك إجراء ما يلي:
return navigator.storage.getDirectory().then(function (dirHandle) {
let entries = dirHandle.entries();
return iterateAsyncDirEntries(entries, [])
.then(function (archiveList) {
return archiveList;
})
.catch(function (err) {
console.error('Unable to iterate OPFS entries', err);
});
});
تذكَّر أنّه لا يزال عليك استخدام getFile()
على أي إدخال تريد العمل
به من صفيف archiveList
.
استيراد الملفات إلى OPFS
إذًا، كيف تنقل الملفات إلى "نظام إدارة الملفات في السحابة الإلكترونية" في المقام الأول؟ ليس بهذه السرعة. أولاً، عليك تقدير مساحة التخزين المتاحة لك، والتأكّد مما إذا كان المستخدمون سيحاولون تحميل ملف بحجم 97 غيغابايت إذا كان لا يناسب المساحة.
يمكنك الحصول على الحصة المقدَّرة بسهولة:
navigator.storage.estimate().then(function (estimate) { … });
. أما المسألة الأصعب قليلاً،
فهي تحديد كيفية عرض هذه المعلومات للمستخدم. في تطبيق Kiwix، اخترنا استخدام
لوحة صغيرة داخل التطبيق تظهر بجانب مربّع الاختيار مباشرةً، ما يتيح للمستخدمين تجربة
نظام OPFS:
تتم تعبئة اللوحة باستخدام
estimate.quota
و
estimate.usage
،
على سبيل المثال:
let OPFSQuota; // Global variable, so we don't have to keep checking it
return navigator.storage.estimate().then(function (estimate) {
const percent = ((estimate.usage / estimate.quota) * 100).toFixed(2);
OPFSQuota = estimate.quota - estimate.usage;
document.getElementById('OPFSQuota').innerHTML =
'<b>OPFS storage quota:</b><br />Used: <b>' +
percent +
'%</b>; ' +
'Remaining: <b>' +
(OPFSQuota / 1024 / 1024 / 1024).toFixed(2) +
' GB</b>';
});
كما ترى، يتوفّر أيضًا زر يتيح للمستخدمين إضافة ملفات إلى OPFS من
نظام الملفات الظاهرة للمستخدم. الخبر السارّ هنا هو أنّه يمكنك ببساطة استخدام
File API للحصول على
كائن الملف (أو الكائنات) المطلوبة التي سيتم استيرادها. في الواقع، من
المهم عدم استخدام window.showOpenFilePicker()
لأنّ هذه الطريقة
غير متوافقة مع Firefox، في حين أنّ OPFS متوافق بالتأكيد.
الزرّ المرئي إضافة ملفات الذي يظهر في لقطة الشاشة أعلاه ليس أداة اختيار ملفات قديمة، ولكنه click()
يعرض أداة اختيار ملفات قديمة مخفية
(عنصر <input type="file" multiple … />
) عند النقر عليه. بعد ذلك، يُسجِّل التطبيق حدث change
لإدخال الملف المخفي، ويتحقّق من حجم الملفات ويرفضها إذا كانت كبيرة جدًا بالنسبة إلى الحصة. إذا كان كل شيء
على ما يرام، اسأل المستخدم عما إذا كان يريد إضافتها:
archiveFilesLegacy.addEventListener('change', function (files) {
const filesArray = Array.from(files.target.files);
// Abort if user didn't select any files
if (filesArray.length === 0) return;
// Calculate the size of the picked files
let filesSize = 0;
filesArray.forEach(function (file) {
filesSize += file.size;
});
// Check the size of the files does not exceed the quota
if (filesSize > OPFSQuota) {
// Oh no, files are too big! Tell user...
console.log('Files would exceed the OPFS quota!');
} else {
// Ask user if they're sure... if user said yes...
return importOPFSEntries(filesArray)
.then(function () {
// Tell user we successfully imported the archives
})
.catch(function (err) {
// Tell user there was an error (error catching is important!)
});
}
});
بما أنّ استيراد المحفوظات ليس هو العملية الأكثر سرعة على بعض أنظمة التشغيل، مثل Android، يعرض تطبيق Kiwix أيضًا بانرًا ومؤشرًا صغيرًا أثناء استيراد المحفوظات. لم يتمكّن الفريق من معرفة كيفية إضافة مؤشر لعملية التقدّم لهذه العملية: إذا تمكّنت من معرفة ذلك، يُرجى إرسال الإجابة على بطاقة بريدية.
كيف نفَّذ Kiwix دالة importOPFSEntries()
؟ ويشمل ذلك
استخدام طريقة fileHandle.createWriteable()
التي تسمح ببث كل
ملف بفعالية في نظام OPFS. ويتولّى المتصفّح تنفيذ كل الإجراءات اللازمة. (تستخدم Kiwix Promises هنا لأسباب تتعلّق بقاعدة بياناتنا القديمة، ولكن يجب القول إنّ await
تُنشئ في هذه الحالة بنية نحوية أبسط وتتجنّب تأثير هرم الهلاك).
function importOPFSEntries(files) {
// Get a handle on the OPFS directory
return navigator.storage
.getDirectory()
.then(function (dir) {
// Collect the promises for each file that we want to write
let promises = files.map(function (file) {
// Create the file and get a writeable handle on it
return dir
.getFileHandle(file.name, { create: true })
.then(function (fileHandle) {
// Get a writer for the file
return fileHandle.createWritable().then(function (writer) {
// Show a banner / spinner, then write the file
return writer
.write(file)
.then(function () {
// Finished with this writer
return writer.close();
})
.catch(function (err) {
console.error('There was an error writing to the OPFS!', err);
});
});
})
.catch(function (err) {
console.error('Unable to get file handle from OPFS!', err);
});
});
// Return a promise that resolves when all the files have been written
return Promise.all(promises);
})
.catch(function (err) {
console.error('Unable to get a handle on the OPFS directory!', err);
});
}
تنزيل بث ملفات مباشرةً في OPFS
هناك طريقة أخرى تتيح بث ملف من الإنترنت مباشرةً
إلى OPFS أو إلى أي دليل لديك معرّف دليل له (أي، الأدلة التي تم اختيارها باستخدام window.showDirectoryPicker()
). وتستخدم هذه الطريقة مبادئ
الرمز البرمجي أعلاه نفسها، ولكنها تُنشئ Response
يتألف من
ReadableStream
وعنصر تحكّم يضيف إلى "قائمة الانتظار" وحدات البايت التي يتم قراءتها من الملف العميق. بعد ذلك، تتم
توجيه Response.body
الناتج إلى كاتب الملف الجديد
داخل نظام OPFS.
في هذه الحالة، يمكن لتطبيق Kiwix احتساب عدد البايتات التي تمر عبر
ReadableStream
، وبالتالي توفير مؤشر للتقدّم للمستخدم، والتحذير منه
أيضًا بعدم إغلاق التطبيق أثناء التنزيل. إنّ الرمز البرمجي معقّد جدًا
ولا يمكن عرضه هنا، ولكن بما أنّ تطبيقنا هو تطبيق مفتوح المصدر، يمكنك
الاطّلاع على المصدر
إذا كنت مهتمًا بتنفيذ إجراء مشابه. في ما يلي شكل واجهة مستخدم Kiwix (تظهر قيم التقدّم المختلفة أدناه لأنّه يتم تعديل البانر فقط عند تغيُّر النسبة المئوية، ولكن يتم تعديل لوحة تقدّم التنزيل بشكلٍ أكثر انتظامًا):
بما أنّ عملية التنزيل قد تكون طويلة جدًا، يسمح تطبيق Kiwix للمستخدمين باستخدام التطبيق بحرية أثناء العملية، ولكنه يضمن عرض البانر دائمًا، لذكير المستخدمين بعدم إغلاق التطبيق إلى أن تكتمل عملية التنزيل.
تنفيذ تطبيق مصغّر لإدارة الملفات داخل التطبيق
في هذه المرحلة، أدرك مطوّرو تطبيق Kiwix PWA أنّه ليس كافيًا التمكّن من إضافة ملفات إلى OPFS. كان على التطبيق أيضًا أن يوفّر للمستخدمين طريقة لحذف الملفات التي لم تعد بحاجة إليها من مساحة التخزين هذه، ومن الأفضل أيضًا تصدير أي ملفات مقفلة في نظام OPFS مرة أخرى إلى نظام الملفات المرئي للمستخدم. وبالتالي، أصبح من الضروري تنفيذ نظام إدارة ملفات محدود داخل التطبيق.
نريد أن نشير هنا إلى إضافة OPFS Explorer الرائعة لتطبيق Chrome (تعمل أيضًا في Edge). وتضيف علامة تبويب في أدوات المطوّرين تتيح لك الاطّلاع على المحتوى الدقيق في نظام OPFS، وحذف الملفات غير الصالحة أو التي تعذّر تحميلها أيضًا. وقد كان ذلك مفعّلاً للغاية للتحقّق مما إذا كان الرمز يعمل، ومراقبة سلوك عمليات التنزيل، وتنظيف تجارب التطوير بشكل عام.
تعتمد عملية تصدير الملفات على إمكانية الحصول على معرّف ملف في ملف أو دليل تم اختيارهما وسيتم حفظ الملف الذي تم تصديره فيهما، لذا لا تعمل هذه العملية إلا في السياقات التي يمكن فيها استخدام طريقة window.showSaveFilePicker()
. إذا كانت ملفات
Kiwix أصغر حجمًا من عدة غيغابايت، سنتمكّن من إنشاء ملف تعريف متعدّد العناصر
في الذاكرة، ومنحه عنوان URL، ثم تنزيله إلى نظام الملفات المرئي للمستخدم.
لا يمكن إجراء ذلك مع أرشيفات كبيرة بهذا الشكل. إذا كان التصدير متاحًا، يكون الإجراء بسيطًا إلى حدٍ كبير: وهو يشبه إلى حدٍ كبير عملية حفظملف في OPFS (احصل على معرّف للملف المطلوب حفظه، واطلب من المستخدم اختيارموقع حفظه باستخدام window.showSaveFilePicker()
، ثم استخدِمcreateWriteable()
على saveHandle
). يمكنك
الاطّلاع على الرمز البرمجي
في المستودع.
تتيح جميع المتصفحات حذف الملفات، ويمكن إجراء ذلك من خلال
إجراء dirHandle.removeEntry('filename')
بسيط. في ما يتعلّق بتطبيق Kiwix، فضّلنا تكرار إدخالات OPFS كما فعلنا أعلاه، حتى نتمكّن من التحقّق من توفّر الملف المحدّد أولاً وطلب التأكيد، ولكن قد لا يكون ذلك ضروريًا للجميع. مرة أخرى، يمكنك
فحص الرمز البرمجي
إذا أردت ذلك.
تمّ اتخاذ قرار بعدم تشويش واجهة مستخدم Kiwix بأزرار تقدّم هذه الخيارات، وبدلاً من ذلك، تمّ وضع رموز صغيرة أسفل قائمة الأرشيف مباشرةً. سيؤدي النقر على أحد هذه الرموز إلى تغيير لون قائمة الأرشيف، وذلك كإشارة مرئية للمستخدم بشأن الإجراء الذي سينفّذه. بعد ذلك، ينقر المستخدم على أحد الأرشيفات، ويتم تنفيذ العملية المقابلة (التصدير أو الحذف) (بعد التأكيد).
أخيرًا، إليك فيديو تجريبي يعرض جميع ميزات إدارة الملفات التي تمت مناقشتها أعلاه، وهي إضافة ملف إلى نظام OPFS وتنزيل ملف مباشرةً إليه، حذف ملف وتصديره إلى نظام الملفات المرئي للمستخدم.
عمل المطوّر لا ينتهي أبدًا
إنّ نظام OPFS هو ابتكار رائع لمطوّري تطبيقات الويب التقدّمية، إذ يقدّم ميزات فعالة جدًا في إدارة الملفات تساهم بشكل كبير في سد الفجوة بين التطبيقات الأصلية وتطبيقات الويب. لكنّ المطوّرين هم مجموعة تعيسة، فهم لن يشعروا أبدًا بالسعادة. إنّ نظام OPFS مثالي تقريبًا، ولكن ليس تمامًا. من الرائع أن تعمل الميزات الرئيسية في كل من متصفّحَي Chromium وFirefox، وأن يتم تنفيذها على أجهزة Android والكمبيوتر المكتبي. نأمل أن يتم قريبًا أيضًا تطبيق مجموعة الميزات الكاملة في Safari وiOS. لا تزال المشاكل التالية قائمة:
- يفرض Firefox حاليًا حدًا أقصى يبلغ 10 غيغابايت على حصة OPFS، بغض النظر عن مقدار المساحة المتوفّرة على القرص. قد يكون هذا الحدّ مناسبًا لمعظم مؤلفي التطبيقات المتوافقة مع تقنية الويب التقدّمي، ولكنّه يشكّل قيودًا صارمة على تطبيق Kiwix. لحسن الحظ، تتيح متصفحات Chromium مساحة أكبر بكثير.
- لا يمكن حاليًا تصدير الملفات الكبيرة من OPFS إلى
نظام الملفات الظاهرة للمستخدمين على متصفّحات الأجهزة الجوّالة أو متصفّح Firefox لأجهزة الكمبيوتر المكتبي، لأنّه
لم يتم تنفيذ
window.showSaveFilePicker()
. في هذه المتصفحات، يتم حجز الملفات الكبيرة بشكل فعّال في نظام OPFS. وهذا الإجراء يتعارض مع فلسفة Kiwix التي تدعو إلى الوصول المفتوح إلى المحتوى، وإمكانية مشاركة المحفوظات بين المستخدمين، خاصة في المناطق التي تواجه فيها مشاكل متكررة في الاتصال بالإنترنت أو تكاليف مرتفعة. - لا يمكن للمستخدم التحكّم في مساحة التخزين التي سيستهلكها نظام الملفات الافتراضي OPFS. يشكّل ذلك مشكلة بشكل خاص على الأجهزة الجوّالة، حيث قد يتوفر لدى المستخدمين مساحات تخزين كبيرة على بطاقة microSD، ولكن بمساحة تخزين صغيرة جدًا في مساحة تخزين الجهاز.
بوجهٍ عام، هذه مشاكل بسيطة في ميزة رائعة تمثل خطوة كبيرة للأمام للاطّلاع على الملفات في التطبيقات المتوافقة مع الأجهزة الجوّالة. يُقدّر فريق Kiwix PWA بشدة مطوّري Chromium والمدافعين عنهم الذين اقترحوا واجهة برمجة التطبيقات File System Access API وصمّموا واجهة برمجة التطبيقات هذه لأول مرة، كما يُقدّرون العمل الجاد الذي أدّى إلى تحقيق توافق بين مورّدي المتصفّحات بشأن أهمية نظام الملفات الخاص بالمورّد. بالنسبة إلى تطبيق Kiwix JS PWA، تم حلّ العديد من مشاكل تجربة المستخدم التي كانت تؤثر في أداء التطبيق في السابق، ويساعدنا في سعينا لتحسين إمكانية وصول الجميع إلى محتوى Kiwix. يُرجى تجربة تطبيق Kiwix المتوافق مع الأجهزة الجوّالة (PWA) وإخبار المطوّرين برأيك.
للحصول على بعض المراجع الرائعة حول إمكانات التطبيقات المتوافقة مع الأجهزة الجوّالة، يمكنك الاطّلاع على المواقع الإلكترونية التالية:
- عرض Project Fugu API: مجموعة من تطبيقات الويب التي تعرض إمكانات تُغلق الفجوة بين التطبيقات المُنشأة من رمز أصلي وتطبيقات الويب المتقدّمة
- الميزات التي توفّرها التطبيقات المتوافقة مع الأجهزة الجوّالة: عرض للميزات التي يمكن أن يوفّرها هذا النوع من التطبيقات