چگونه Kiwix PWA به کاربران اجازه می دهد تا گیگابایت داده از اینترنت را برای استفاده آفلاین ذخیره کنند

مردم دور یک لپ‌تاپ که روی یک میز ساده با یک صندلی پلاستیکی در سمت چپ ایستاده‌اند، جمع شده‌اند. پس زمینه شبیه مدرسه ای در یک کشور در حال توسعه است.

این مطالعه موردی به بررسی این موضوع می‌پردازد که چگونه Kiwix، یک سازمان غیرانتفاعی، از فناوری Progressive Web App و File System Access API استفاده می‌کند تا کاربران را قادر به دانلود و ذخیره آرشیوهای اینترنتی بزرگ برای استفاده آفلاین کند. در مورد پیاده سازی فنی کد مربوط به سیستم فایل خصوصی مبدا (OPFS)، یک ویژگی مرورگر جدید در Kiwix PWA که مدیریت فایل را بهبود می بخشد، دسترسی بهتر به بایگانی ها را بدون درخواست اجازه می دهد، بیاموزید. این مقاله چالش ها را مورد بحث قرار می دهد و پیشرفت های بالقوه آینده در این سیستم فایل جدید را برجسته می کند.

درباره کیویکس

با گذشت بیش از 30 سال از تولد وب، طبق اعلام اتحادیه بین المللی مخابرات، یک سوم جمعیت جهان همچنان منتظر دسترسی مطمئن به اینترنت هستند . داستان اینجا به پایان می رسد؟ البته نه. مردم در Kiwix ، یک سازمان غیرانتفاعی مستقر در سوئیس، اکوسیستمی از برنامه‌ها و محتوای منبع باز ایجاد کرده‌اند که هدف آن در دسترس قرار دادن دانش برای افرادی با دسترسی محدود یا بدون دسترسی به اینترنت است. ایده آنها این است که اگر نمی توانید به راحتی به اینترنت دسترسی داشته باشید، آنگاه شخصی می تواند منابع کلیدی را برای شما دانلود کند، از کجا و چه زمانی اتصال در دسترس است، و آنها را به صورت محلی برای استفاده آفلاین بعدی ذخیره کند. بسیاری از سایت‌های حیاتی، برای مثال ویکی‌پدیا، پروژه گوتنبرگ، Stack Exchange، یا حتی گفتگوهای TED، اکنون می‌توانند به آرشیوهای بسیار فشرده، به نام فایل‌های ZIM تبدیل شوند و به سرعت توسط مرورگر Kiwix خوانده شوند.

بایگانی‌های ZIM از فشرده‌سازی بسیار کارآمد Zstandard (ZSTD) استفاده می‌کنند (نسخه‌های قدیمی‌تر از XZ استفاده می‌کردند)، بیشتر برای ذخیره‌سازی HTML، جاوا اسکریپت و CSS، در حالی که تصاویر معمولاً به فرمت WebP فشرده تبدیل می‌شوند. هر ZIM همچنین شامل یک URL و یک فهرست عنوان است. فشرده‌سازی در اینجا کلیدی است، زیرا کل ویکی‌پدیا به زبان انگلیسی (6.4 میلیون مقاله، به اضافه تصاویر) پس از تبدیل به فرمت ZIM به 97 گیگابایت فشرده می‌شود، که تا زمانی که متوجه شوید که مجموع همه دانش بشر اکنون می‌تواند جا بیفتد، بسیار زیاد است. در یک گوشی اندرویدی میان رده بسیاری از منابع کوچکتر نیز ارائه شده است، از جمله نسخه های موضوعی ویکی پدیا، مانند ریاضیات، پزشکی و غیره.

Kiwix طیف وسیعی از برنامه‌های بومی را برای استفاده از دسکتاپ (ویندوز / لینوکس / macOS) و همچنین استفاده از تلفن همراه (iOS/Android) ارائه می‌کند. با این حال، این مطالعه موردی بر روی برنامه وب پیشرو (PWA) متمرکز خواهد شد که هدف آن این است که یک راه حل جهانی و ساده برای هر دستگاهی باشد که یک مرورگر مدرن دارد.

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

یک برنامه وب برای استفاده آفلاین؟

کاربران Kiwix گروهی متشکل با نیازهای مختلف هستند و Kiwix کنترل کمی بر دستگاه‌ها و سیستم‌عامل‌هایی که از طریق آنها به محتوای خود دسترسی خواهند داشت، دارد. برخی از این دستگاه ها ممکن است کند یا قدیمی باشند، به خصوص در مناطق کم درآمد جهان. در حالی که Kiwix سعی می‌کند تا حد ممکن موارد استفاده را پوشش دهد، سازمان همچنین متوجه شد که می‌تواند با استفاده از جهانی‌ترین نرم‌افزار در هر دستگاهی: مرورگر وب، به کاربران بیشتری دسترسی پیدا کند. بنابراین، با الهام از قانون اتوود ، که بیان می‌کند هر برنامه‌ای که می‌تواند با جاوا اسکریپت نوشته شود، در نهایت با جاوا اسکریپت نوشته می‌شود ، برخی از توسعه دهندگان Kiwix، حدود 10 سال پیش، اقدام به انتقال نرم‌افزار Kiwix از C++ به جاوا اسکریپت کردند.

اولین نسخه این پورت که Kiwix HTML5 نام داشت، برای سیستم عامل فایرفاکس از بین رفته و برای برنامه های افزودنی مرورگر بود. در هسته آن یک موتور فشرده سازی C++ (XZ و ZSTD) بود (و هست) که با استفاده از کامپایلر Emscripten به زبان جاوا اسکریپت میانی ASM.js و بعداً Wasm یا WebAssembly کامپایل شده بود. بعدها به Kiwix JS تغییر نام داد، افزونه های مرورگر هنوز به طور فعال توسعه می یابند.

مرورگر آفلاین Kiwix JS

وارد برنامه وب پیشرو (PWA) شوید. با درک پتانسیل این فناوری، توسعه‌دهندگان Kiwix یک نسخه اختصاصی PWA از Kiwix JS ساختند و به افزودن یکپارچه‌سازی‌های سیستم‌عاملی پرداختند که به برنامه اجازه می‌دهد قابلیت‌های مشابه بومی، به ویژه در زمینه‌های استفاده آفلاین، نصب، مدیریت فایل و دسترسی به سیستم فایل

PWA های آفلاین اول بسیار سبک وزن هستند، و بنابراین برای زمینه هایی که اینترنت موبایل متناوب یا گران قیمت وجود دارد، عالی هستند. فناوری پشت این سرویس Worker API و Cache API مربوطه است که توسط همه برنامه‌های مبتنی بر Kiwix JS استفاده می‌شود. این APIها به برنامه‌ها اجازه می‌دهند تا به‌عنوان یک سرور عمل کنند، درخواست‌های واکشی را از سند یا مقاله اصلی در حال مشاهده رهگیری کنند، و آنها را به باطن (JS) هدایت کنند تا یک پاسخ از بایگانی ZIM استخراج و بسازند.

ذخیره سازی، ذخیره سازی در همه جا

با توجه به حجم زیاد آرشیوهای ZIM، ذخیره سازی و دسترسی به آن، به ویژه در دستگاه های تلفن همراه، احتمالاً بزرگترین دردسر برای توسعه دهندگان Kiwix است. بسیاری از کاربران نهایی Kiwix در صورت در دسترس بودن اینترنت، محتوای درون برنامه ای را برای استفاده آفلاین بعدی دانلود می کنند. سایر کاربران با استفاده از تورنت در رایانه شخصی بارگیری می کنند و سپس به دستگاه تلفن همراه یا رایانه لوحی منتقل می کنند، و برخی از آنها محتوا را بر روی درایوهای USB یا هارد دیسک های قابل حمل در مناطقی که اینترنت موبایل ناقص یا گران قیمت دارند تبادل می کنند. همه این راه‌های دسترسی به محتوا از مکان‌های قابل دسترسی دلخواه کاربر باید توسط Kiwix JS و Kiwix PWA پشتیبانی شوند.

چیزی که در ابتدا امکان خواندن آرشیوهای عظیم از صدها گیگابایت را برای Kiwix JS فراهم کرد ( یکی از بایگانی های ZIM ما 166 گیگابایت است!) حتی در دستگاه های با حافظه کم، API فایل است. این API به طور جهانی در هر مرورگری، حتی مرورگرهای بسیار قدیمی ، پشتیبانی می‌شود، و بنابراین برای مواقعی که APIهای جدیدتر پشتیبانی نمی‌شوند، به‌عنوان بازگشتی جهانی عمل می‌کند. در مورد Kiwix، به آسانی تعریف یک عنصر input در HTML است:

<input
  type="file"
  accept="application/octet-stream,.zim,.zimaa,.zimab,.zimac, ..."
  value="Select folder with ZIM files"
  id="archiveFilesLegacy"
  multiple
/>

پس از انتخاب، عنصر ورودی اشیاء File را نگه می‌دارد که اساساً ابرداده‌ای هستند که به داده‌های زیرین در ذخیره‌سازی ارجاع می‌دهند. از نظر فنی، باطن شی گرا Kiwix که با جاوا اسکریپت سمت کلاینت خالص نوشته شده است، برش های کوچکی از آرشیو بزرگ را در صورت نیاز می خواند. اگر این برش ها نیاز به فشرده سازی داشته باشند، backend آنها را به کمپرسور Wasm ارسال می کند و در صورت درخواست، برش های بیشتری را دریافت می کند تا زمانی که یک لکه کامل (معمولاً یک مقاله یا یک دارایی) از حالت فشرده خارج شود. این بدان معنی است که آرشیو بزرگ هرگز نباید به طور کامل در حافظه خوانده شود.

همانطور که جهانی است، File API دارای یک اشکال است که باعث می‌شود برنامه‌های Kiwix JS در مقایسه با برنامه‌های بومی قدیمی و قدیمی به نظر برسند: از کاربر می‌خواهد بایگانی‌ها را با استفاده از یک انتخابگر فایل انتخاب کند، یا یک فایل را به داخل برنامه بکشد و رها کند. ، هر بار که برنامه راه اندازی می شود ، زیرا با این API، هیچ راهی برای تداوم مجوزهای دسترسی از یک جلسه به جلسه دیگر وجود ندارد.

برای کاهش این UX ضعیف، مانند بسیاری از توسعه دهندگان، توسعه دهندگان Kiwix JS در ابتدا مسیر Electron را طی کردند. ElectronJS یک چارچوب شگفت انگیز است که ویژگی های قدرتمندی از جمله دسترسی کامل به سیستم فایل با استفاده از Node API را ارائه می دهد. با این حال، برخی از معایب شناخته شده دارد:

  • فقط روی سیستم عامل های دسکتاپ اجرا می شود.
  • این بزرگ و سنگین است (70MB-100MB).

اندازه برنامه های Electron، به دلیل این واقعیت که یک کپی کامل از Chromium با هر برنامه گنجانده شده است، بسیار نامطلوب با تنها 5.1 مگابایت برای PWA کوچک شده و همراه است!

بنابراین، آیا راهی وجود داشت که Kiwix بتواند وضعیت را برای کاربران PWA بهبود بخشد؟

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

در حدود سال ۲۰۱۹، Kiwix از یک API اضطراری آگاه شد که در حال آزمایش اولیه در Chrome 78 بود که در آن زمان به نام Native File System API شناخته می‌شد. این امکان را برای یک فایل یا یک پوشه و ذخیره آن در پایگاه داده IndexedDB داده بود. مهمتر از همه، این دسته در بین جلسات برنامه باقی می ماند، بنابراین کاربر مجبور نیست هنگام راه اندازی مجدد برنامه، فایل یا پوشه را دوباره انتخاب کند (اگرچه باید به یک درخواست مجوز سریع پاسخ دهد). زمانی که به تولید رسید، به API دسترسی به سیستم فایل تغییر نام داد و قطعات اصلی توسط WHATWG به عنوان API سیستم فایل (FSA) استاندارد شده بود.

بنابراین، بخش دسترسی به فایل سیستم از API چگونه کار می کند؟ چند نکته مهم قابل ذکر است:

  • این یک API ناهمزمان است (به جز توابع تخصصی در Web Workers).
  • انتخاب‌کننده‌های فایل یا دایرکتوری باید به‌صورت برنامه‌نویسی و با گرفتن یک حرکت کاربر راه‌اندازی شوند (روی یک عنصر UI کلیک یا ضربه بزنید).
  • برای اینکه کاربر مجدداً اجازه دسترسی به فایلی که قبلاً انتخاب شده است (در یک جلسه جدید) را بدهد، یک اشاره کاربر نیز مورد نیاز است - در واقع مرورگر از نشان دادن درخواست مجوز در صورتی که توسط یک حرکت کاربر شروع نشود، خودداری می کند.

این کد نسبتاً ساده است، جدای از اینکه باید از IndexedDB API بی‌حساب برای ذخیره‌سازی دسته‌های فایل و دایرکتوری استفاده کنید. خبر خوب این است که چند کتابخانه وجود دارند که کارهای سنگینی مانند مرورگر-fs-access را برای شما انجام می دهند. در Kiwix JS، تصمیم گرفتیم مستقیماً با APIها کار کنیم که بسیار مستند هستند.

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

باز کردن یک انتخابگر فایل چیزی شبیه به این است (در اینجا با استفاده از Promises استفاده می‌شود، اما اگر async/await شکر را ترجیح می‌دهید، به آموزش Chrome for Developers مراجعه کنید):

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

پردازش دایرکتوری‌ها کمی پیچیده‌تر است، زیرا برای یافتن فایل‌ها یا انواع فایل‌های مورد نظر، باید از طریق ورودی‌های فهرست انتخابی با async 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() دریافت کنید. const file = await entry.getFile() در یک async function .

آیا می توانیم جلوتر برویم؟

الزام کاربر به اعطای مجوز که با اشاره کاربر در راه‌اندازی بعدی برنامه آغاز می‌شود، مقدار کمی اصطکاک به باز کردن فایل و پوشه (دوباره) اضافه می‌کند، اما باز هم بسیار روان‌تر از انتخاب مجدد یک فایل است. . توسعه دهندگان Chromium در حال حاضر در حال نهایی کردن کدی هستند که اجازه می دهد برای PWA های نصب شده مجوزهای دائمی داشته باشند. این چیزی است که بسیاری از توسعه دهندگان PWA خواستار آن بوده اند و به شدت پیش بینی می شود.

اما اگر منتظر نباشیم چی؟! توسعه‌دهندگان Kiwix اخیراً دریافته‌اند که می‌توان در حال حاضر با استفاده از یک ویژگی جدید و درخشان از File Access API که توسط هر دو مرورگر Chromium و Firefox پشتیبانی می‌شود (و تا حدی توسط Safari پشتیبانی می‌شود، اما هنوز FileSystemWritableFileStream وجود ندارد ) همه درخواست‌های مجوز را حذف کرد. این ویژگی جدید Origin Private File System است.

به طور کامل بومی شدن: سیستم فایل خصوصی Origin

Origin Private File System (OPFS) هنوز یک ویژگی آزمایشی در Kiwix PWA است، اما تیم واقعاً هیجان‌زده است که کاربران را تشویق کند آن را امتحان کنند، زیرا تا حد زیادی شکاف بین برنامه‌های بومی و برنامه‌های وب را پر می‌کند. در اینجا مزایای کلیدی وجود دارد:

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

دسترسی استاندارد به فایل در اندروید با استفاده از File API به طرز دردناکی کند است، به خصوص (همانطور که اغلب برای کاربران Kiwix اتفاق می افتد) اگر آرشیوهای بزرگ به جای حافظه دستگاه، روی کارت microSD ذخیره شوند. این همه با این API جدید تغییر می کند. در حالی که اکثر کاربران نمی‌توانند یک فایل 97 گیگابایتی را در OPFS (که فضای ذخیره‌سازی دستگاه را مصرف می‌کند، نه حافظه کارت microSD) ذخیره کنند، این برای ذخیره‌سازی آرشیوهای کوچک تا متوسط ​​عالی است. شما کامل ترین دایره المعارف پزشکی از ویکی پروژه پزشکی را می خواهید؟ مشکلی نیست، با 1.7 گیگابایت به راحتی در OPFS جا می شود! (نکته: به دنبال موارد دیگرmdwiki_en_all_maxi در کتابخانه درون برنامه باشید.)

نحوه عملکرد OPFS

OPFS یک سیستم فایل ارائه شده توسط مرورگر است که برای هر مبدأ مجزا است و می‌توان آن را شبیه به فضای ذخیره‌سازی با محدوده برنامه در اندروید در نظر گرفت. فایل‌ها را می‌توان از سیستم فایل قابل مشاهده توسط کاربر به OPFS وارد کرد، یا می‌توان آن‌ها را مستقیماً در آن دانلود کرد (API همچنین امکان ایجاد فایل‌ها در OPFS را فراهم می‌کند). هنگامی که در OPFS قرار می گیرند، از بقیه دستگاه جدا می شوند. در مرورگرهای مبتنی بر Chromium دسکتاپ، امکان صادر کردن فایل‌ها از OPFS به سیستم فایل قابل مشاهده توسط کاربر نیز وجود دارد.

برای استفاده از OPFS، اولین قدم درخواست دسترسی به آن است، با استفاده از navigator.storage.getDirectory() (دوباره، اگر ترجیح می دهید کد را با استفاده از await ببینید، سیستم فایل خصوصی Origin را بخوانید):

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

بنابراین، چگونه می‌توان فایل‌ها را در وهله اول وارد 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:&nbsp;<b>' +
    percent +
    '%</b>; ' +
    'Remaining:&nbsp;<b>' +
    (OPFSQuota / 1024 / 1024 / 1024).toFixed(2) +
    '&nbsp;GB</b>';
});

همانطور که می بینید، دکمه ای نیز وجود دارد که به کاربران امکان می دهد فایل ها را از سیستم فایل قابل مشاهده توسط کاربر به OPFS اضافه کنند. خبر خوب در اینجا این است که شما به سادگی می توانید از File API برای دریافت فایل مورد نیاز شی (یا اشیاء) که قرار است وارد شوند استفاده کنید. در واقع، مهم است که از window.showOpenFilePicker() استفاده نکنید زیرا این روش توسط فایرفاکس پشتیبانی نمی شود، در حالی که 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!)
      });
  }
});

گفتگو از کاربر می پرسد که آیا می خواهد لیستی از فایل های .zim را به سیستم فایل خصوصی مبدا اضافه کند.

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

رابط کاربری Kiwix با نواری در پایین که به کاربر هشدار می دهد از برنامه خارج نشود و پیشرفت دانلود آرشیو zim را نشان می دهد.

از آنجایی که دانلود می‌تواند یک عملیات بسیار طولانی باشد، Kiwix به کاربران اجازه می‌دهد تا در طول عملیات آزادانه از برنامه استفاده کنند، اما اطمینان حاصل می‌کند که بنر همیشه نمایش داده می‌شود، به طوری که به کاربران یادآوری می‌شود که تا زمانی که عملیات دانلود کامل نشده است، برنامه را نبندند.

پیاده سازی مینی فایل منیجر درون برنامه ای

در این مرحله، توسعه دهندگان Kiwix PWA متوجه شدند که برای اضافه کردن فایل ها به OPFS کافی نیست. این برنامه همچنین باید راهی را در اختیار کاربران قرار دهد تا فایل‌هایی را که دیگر به آن‌ها نیاز ندارند را از این قسمت ذخیره‌سازی حذف کنند و در حالت ایده‌آل، همچنین، فایل‌های قفل‌شده در OPFS را به سیستم فایل قابل مشاهده کاربر صادر کند. به طور موثر، پیاده سازی یک سیستم مدیریت فایل کوچک در داخل برنامه ضروری شد.

یک فریاد سریع در اینجا به برنامه افزودنی افسانه‌ای OPFS Explorer برای Chrome (این برنامه در Edge نیز کار می‌کند). یک برگه در ابزارهای توسعه‌دهنده اضافه می‌کند که به شما امکان می‌دهد دقیقاً آنچه را در OPFS وجود دارد ببینید و همچنین فایل‌های سرکش یا ناموفق را حذف کنید. برای بررسی کارکرد کد، نظارت بر رفتار دانلودها، و به طور کلی پاکسازی آزمایشات توسعه ما بسیار ارزشمند بود.

صادرات فایل به توانایی دریافت دسته فایل در یک فایل یا دایرکتوری انتخاب شده بستگی دارد که Kiwix قرار است فایل صادر شده را در آن ذخیره کند، بنابراین این فقط در زمینه هایی کار می کند که می تواند از متد window.showSaveFilePicker() استفاده کند. اگر فایل‌های Kiwix کوچک‌تر از چند گیگابایت بودند، می‌توانیم یک حباب در حافظه بسازیم، به آن URL بدهیم و سپس آن را در سیستم فایل قابل مشاهده برای کاربر دانلود کنیم. متأسفانه، با چنین آرشیوهای بزرگی این امکان وجود ندارد. در صورت پشتیبانی، صادرات نسبتاً ساده است: تقریباً مشابه ذخیره یک فایل در OPFS (دریافت یک دسته روی فایلی که باید ذخیره شود، از کاربر بخواهید با window.showSaveFilePicker() ، سپس از createWriteable() در saveHandle استفاده کنید. می توانید کد را در مخزن مشاهده کنید .

حذف فایل توسط همه مرورگرها پشتیبانی می شود و می توان با یک dirHandle.removeEntry('filename') ساده به آن دست یافت. در مورد Kiwix، ما ترجیح دادیم که ورودی‌های OPFS را مانند بالا تکرار کنیم، تا بتوانیم ابتدا بررسی کنیم که فایل انتخابی وجود دارد و درخواست تأیید کنیم، اما ممکن است برای همه لازم نباشد. در صورت علاقه مجدد می توانید کد ما را بررسی کنید .

تصمیم گرفته شد که رابط کاربری Kiwix را با دکمه‌هایی که این گزینه‌ها را ارائه می‌دهند شلوغ نکنیم و در عوض آیکون‌های کوچک را مستقیماً زیر فهرست بایگانی قرار دهیم. با ضربه زدن بر روی یکی از این نمادها، رنگ فهرست بایگانی تغییر می‌کند، تا به عنوان یک سرنخ بصری برای کاربر در مورد کاری که قرار است انجام دهد. سپس کاربر روی یکی از بایگانی ها کلیک یا ضربه می زند و عملیات مربوطه (صادرات یا حذف) (پس از تایید) انجام می شود.

گفتگو از کاربر می پرسد که آیا می خواهد یک فایل .zim را حذف کند.

در نهایت، در اینجا یک نسخه نمایشی از تمام ویژگی‌های مدیریت فایل که در بالا مورد بحث قرار گرفت، ارائه می‌شود - افزودن یک فایل به OPFS، دانلود مستقیم یک فایل در آن، حذف یک فایل، و صادرات به سیستم فایل قابل مشاهده برای کاربر.

کار یک توسعه دهنده هرگز انجام نمی شود

OPFS یک نوآوری عالی برای توسعه دهندگان PWA است که ویژگی های مدیریت فایل واقعا قدرتمندی را ارائه می دهد که فاصله زیادی بین برنامه های بومی و برنامه های وب را از بین می برد. اما توسعه دهندگان گروه بدبختی هستند - آنها هرگز کاملاً راضی نیستند! OPFS تقریباً کامل است، اما نه کاملاً... خیلی خوب است که ویژگی‌های اصلی در هر دو مرورگر کرومیوم و فایرفاکس کار می‌کنند، و در اندروید و همچنین دسکتاپ پیاده‌سازی می‌شوند. امیدواریم مجموعه کامل ویژگی ها نیز به زودی در سافاری و iOS پیاده سازی شود. مسائل زیر باقی می ماند:

  • فایرفاکس در حال حاضر سقف 10 گیگابایتی را در سهمیه OPFS قرار می دهد، صرف نظر از اینکه چقدر فضای دیسک زیرین وجود دارد. در حالی که برای اکثر نویسندگان PWA این ممکن است کافی باشد، برای Kiwix، این بسیار محدود کننده است. خوشبختانه مرورگرهای Chromium بسیار سخاوتمندتر هستند.
  • در حال حاضر امکان صادر کردن فایل‌های بزرگ از OPFS به سیستم فایل قابل مشاهده توسط کاربر در مرورگرهای تلفن همراه یا فایرفاکس دسکتاپ وجود ندارد، زیرا window.showSaveFilePicker() پیاده‌سازی نشده است. در این مرورگرها، فایل های بزرگ به طور موثر در OPFS به دام می افتند. این در تضاد با اخلاق Kiwix برای دسترسی آزاد به محتوا، و توانایی اشتراک‌گذاری آرشیو بین کاربران، به‌ویژه در زمینه‌های اتصال به اینترنت متناوب یا گران قیمت است.
  • هیچ توانایی کاربر برای کنترل ذخیره سازی که سیستم فایل مجازی OPFS مصرف می کند وجود ندارد. این به ویژه در دستگاه های تلفن همراه مشکل ساز است، جایی که کاربران ممکن است فضای زیادی روی کارت microSD داشته باشند، اما فضای بسیار کمی در حافظه دستگاه داشته باشند.

اما در مجموع، اینها در مواردی که در غیر این صورت یک گام بزرگ رو به جلو برای دسترسی به فایل در PWA ها به شمار می رود، اشتباهات جزئی هستند. تیم Kiwix PWA از توسعه دهندگان و حامیان Chromium که برای اولین بار API دسترسی به فایل سیستم را پیشنهاد و طراحی کردند و برای کار سخت برای دستیابی به اجماع بین فروشندگان مرورگر در مورد اهمیت Origin Private File System بسیار سپاسگزار است. برای Kiwix JS PWA، بسیاری از مشکلات UX را که در گذشته برنامه را با مشکل مواجه کرده بودند، حل کرده است و به ما در تلاش برای افزایش دسترسی به محتوای Kiwix برای همه کمک می‌کند. لطفاً Kiwix PWA را بررسی کنید و به توسعه دهندگان بگویید که چه فکر می کنید!

برای برخی از منابع عالی در مورد قابلیت های PWA، به این سایت ها نگاهی بیندازید: