Kiwix PWA की मदद से उपयोगकर्ता, गीगाबाइट (जीबी) डेटा को ऑफ़लाइन इस्तेमाल के लिए कैसे सेव कर सकते हैं

एक टेबल के आस-पास इकट्ठा हुए लोग, जिनके पास लैपटॉप है. टेबल के बाईं ओर एक प्लास्टिक की कुर्सी है. बैकग्राउंड में, किसी विकासशील देश का स्कूल दिख रहा हो.

इस केस स्टडी में बताया गया है कि गैर-लाभकारी संस्था Kiwix, प्रोग्रेसिव वेब ऐप्लिकेशन टेक्नोलॉजी और फ़ाइल सिस्टम ऐक्सेस एपीआई का इस्तेमाल कैसे करती है. इससे उपयोगकर्ता, इंटरनेट पर मौजूद बड़े संग्रह को ऑफ़लाइन इस्तेमाल करने के लिए डाउनलोड और सेव कर पाते हैं. Origin Private File System (OPFS) से जुड़े कोड को तकनीकी तौर पर लागू करने के बारे में जानें. यह Kiwix PWA में एक नई ब्राउज़र सुविधा है. इससे फ़ाइल मैनेजमेंट बेहतर होता है और अनुमति के अनुरोधों के बिना संग्रह को बेहतर तरीके से ऐक्सेस किया जा सकता है. इस लेख में, इस नए फ़ाइल सिस्टम से जुड़ी चुनौतियों के बारे में बताया गया है. साथ ही, आने वाले समय में होने वाले संभावित बदलावों के बारे में भी बताया गया है.

Kiwix के बारे में जानकारी

इंटरनेट के जन्म के 30 साल बाद भी, इंटरनेट का इस्तेमाल करने वाले लोगों की संख्या काफ़ी कम है. इंटरनेशनल टेलीकम्यूनिकेशन यूनियन के मुताबिक, दुनिया की एक तिहाई आबादी अब भी इंटरनेट का भरोसेमंद ऐक्सेस पाने का इंतज़ार कर रही है. क्या कहानी यहीं खत्म होती है? बेशक आ जाओ. स्विट्ज़रलैंड में मौजूद Kiwix, एक ग़ैर-लाभकारी संस्था है. इसने ओपन सोर्स ऐप्लिकेशन और कॉन्टेंट का एक ऐसा नेटवर्क बनाया है जिसका मकसद, सीमित या बिना इंटरनेट वाले लोगों को जानकारी उपलब्ध कराना है. उनका कहना है कि अगर आप इंटरनेट को आसानी से ऐक्सेस नहीं कर पा रहे हैं, तो कोई व्यक्ति आपके लिए ज़रूरी संसाधन डाउनलोड कर सकता है. ऐसा तब किया जा सकता है, जब इंटरनेट कनेक्शन उपलब्ध हो. साथ ही, उन्हें ऑफ़लाइन इस्तेमाल करने के लिए, डिवाइस में सेव किया जा सकता है. Wikipedia, Project Gutenberg, Stack Exchange या TED Talks जैसी कई अहम साइटों को अब ज़्यादा कंप्रेस किए गए संग्रह में बदला जा सकता है. इन्हें ZIM फ़ाइलें कहा जाता है. साथ ही, Kiwix ब्राउज़र की मदद से इन्हें कभी भी पढ़ा जा सकता है.

ZIM संग्रह, ज़्यादातर एचटीएमएल, JavaScript, और सीएसएस को स्टोर करने के लिए, Zstandard (ZSTD) कंप्रेशन का इस्तेमाल करते हैं. यह कंप्रेशन बहुत असरदार होता है. पुराने वर्शन में XZ का इस्तेमाल किया जाता था. वहीं, इमेज को आम तौर पर कंप्रेस किए गए WebP फ़ॉर्मैट में बदल दिया जाता है. हर ZIM में एक यूआरएल और टाइटल इंडेक्स भी शामिल होता है. यहां कंप्रेस करने की सुविधा अहम है, क्योंकि अंग्रेज़ी में Wikipedia का पूरा कॉन्टेंट (6.4 करोड़ लेख और इमेज) ZIM फ़ॉर्मैट में बदलने के बाद, 97 जीबी तक कंप्रेस हो जाता है. यह तब तक काफ़ी लगता है, जब तक आपको यह पता नहीं चल जाता कि अब इंसान के सभी ज्ञान को एक मिड-रेंज Android फ़ोन में सेव किया जा सकता है. इसमें कई छोटे संसाधन भी उपलब्ध हैं. जैसे, Wikipedia के थीम वाले वर्शन, जैसे कि गणित, चिकित्सा वगैरह.

Kiwix, डेस्कटॉप (Windows/Linux/macOS) और मोबाइल (iOS/Android) के लिए, अलग-अलग नेटिव ऐप्लिकेशन उपलब्ध कराता है. हालांकि, इस केस स्टडी में प्रोग्रेसिव वेब ऐप्लिकेशन (PWA) पर फ़ोकस किया जाएगा. इसका मकसद, ऐसे किसी भी डिवाइस के लिए एक आसान और यूनिवर्सल समाधान उपलब्ध कराना है जिसमें आधुनिक ब्राउज़र हो.

हम ऐसे यूनिवर्सल वेब ऐप्लिकेशन को डेवलप करने से जुड़ी चुनौतियों पर ध्यान देंगे जिसे बड़े कॉन्टेंट संग्रह को पूरी तरह से ऑफ़लाइन ऐक्सेस करने की सुविधा देनी है. साथ ही, हम कुछ आधुनिक JavaScript एपीआई, खास तौर पर फ़ाइल सिस्टम ऐक्सेस एपीआई और ऑरिजिन प्राइवेट फ़ाइल सिस्टम पर भी ध्यान देंगे. ये एपीआई, इन चुनौतियों को हल करने के लिए नए और बेहतर समाधान उपलब्ध कराते हैं.

क्या यह ऑफ़लाइन इस्तेमाल के लिए उपलब्ध वेब ऐप्लिकेशन है?

Kiwix के उपयोगकर्ताओं की अलग-अलग ज़रूरतें होती हैं. साथ ही, Kiwix के पास उन डिवाइसों और ऑपरेटिंग सिस्टम पर काफ़ी कम या कोई कंट्रोल नहीं होता जिन पर उपयोगकर्ता अपना कॉन्टेंट ऐक्सेस करेंगे. इनमें से कुछ डिवाइस धीमे या पुराने हो सकते हैं. ऐसा खास तौर पर, दुनिया के कम आय वाले इलाकों में होता है. Kiwix, इस्तेमाल के जितने ज़्यादा उदाहरणों को कवर करने की कोशिश करता है उतना ही ज़्यादा लोगों तक पहुंच सकता है. साथ ही, संगठन को यह भी पता चला है कि वेब ब्राउज़र, किसी भी डिवाइस पर मौजूद सबसे यूनिवर्सल सॉफ़्टवेयर है. इसका इस्तेमाल करके, ज़्यादा से ज़्यादा लोगों तक पहुंचा जा सकता है. इसलिए, Atwood's Law से प्रेरित होकर, कुछ Kiwix डेवलपर ने करीब 10 साल पहले, Kiwix सॉफ़्टवेयर को C++ से JavaScript में पोर्ट करने की कोशिश शुरू की. इस कानून के मुताबिक, कोई भी ऐसा ऐप्लिकेशन जो JavaScript में लिखा जा सकता है, आखिर में JavaScript में ही लिखा जाएगा.

इस पोर्ट का पहला वर्शन, Kiwix HTML5 कहलाता था. यह वर्शन, अब बंद हो चुके Firefox OS और ब्राउज़र एक्सटेंशन के लिए था. इसके कोर में, C++ डिकंप्रेसन इंजन (XZ और ZSTD) था (और है), जिसे ASM.js की इंटरमीडियरी JavaScript भाषा में और बाद में Wasm या WebAssembly में इकट्ठा किया गया था. इसके लिए, Emscripten कंपाइलर का इस्तेमाल किया गया था. बाद में, इसका नाम बदलकर Kiwix JS कर दिया गया. ब्राउज़र एक्सटेंशन पर अब भी लगातार काम किया जा रहा है.

Kiwix JS Offline Browser

प्रोग्रेसिव वेब ऐप्लिकेशन (PWA) डालें. इस टेक्नोलॉजी की संभावनाओं को समझते हुए, Kiwix के डेवलपर ने Kiwix JS का एक खास पीडब्ल्यूए वर्शन बनाया. साथ ही, ओएस इंटिग्रेशन जोड़ने की कोशिश की, ताकि ऐप्लिकेशन में नेटिव ऐप्लिकेशन जैसी सुविधाएं मिल सकें. इनमें ऑफ़लाइन इस्तेमाल, इंस्टॉलेशन, फ़ाइल मैनेजमेंट, और फ़ाइल सिस्टम ऐक्सेस करना शामिल है.

ऑफ़लाइन-फ़र्स्ट PWA बहुत हल्के होते हैं. इसलिए, वे उन स्थितियों के लिए सबसे सही होते हैं जहां मोबाइल इंटरनेट की स्पीड कम होती है या इंटरनेट की कीमत ज़्यादा होती है. इसके पीछे की टेक्नोलॉजी, Service Worker API और उससे जुड़ा Cache API है. इसका इस्तेमाल, Kiwix JS पर आधारित सभी ऐप्लिकेशन करते हैं. इन एपीआई की मदद से, ऐप्लिकेशन सर्वर के तौर पर काम करते हैं. ये ऐप्लिकेशन, देखे जा रहे मुख्य दस्तावेज़ या लेख से फ़ेच करने के अनुरोध को इंटरसेप्ट करते हैं. साथ ही, उन्हें ZIM संग्रह से जवाब निकालने और बनाने के लिए, (JS) बैकएंड पर रीडायरेक्ट करते हैं.

हर जगह स्टोरेज

ZIM संग्रह का साइज़ बहुत बड़ा होता है. इसलिए, Kiwix के डेवलपर के लिए, इसे स्टोर करना और खास तौर पर मोबाइल डिवाइसों पर इसका ऐक्सेस देना सबसे बड़ी समस्या है. Kiwix के कई उपयोगकर्ता, इंटरनेट उपलब्ध होने पर ऐप्लिकेशन में कॉन्टेंट डाउनलोड करते हैं, ताकि बाद में उसे ऑफ़लाइन इस्तेमाल किया जा सके. कुछ लोग टोरेंट का इस्तेमाल करके, पीसी पर कॉन्टेंट डाउनलोड करते हैं और फिर उसे मोबाइल या टैबलेट डिवाइस पर ट्रांसफ़र करते हैं. कुछ लोग, मोबाइल इंटरनेट की खराब या महंगी सेवा वाले इलाकों में, यूएसबी स्टिक या पोर्टेबल हार्ड ड्राइव पर कॉन्टेंट शेयर करते हैं. उपयोगकर्ता के पास, कॉन्टेंट को ऐक्सेस करने के कई तरीके होते हैं. इन सभी तरीकों से कॉन्टेंट को ऐक्सेस करने के लिए, Kiwix JS और Kiwix PWA का इस्तेमाल किया जा सकता है.

फ़ाइल एपीआई की मदद से, Kiwix JS ने कम मेमोरी वाले डिवाइसों पर भी, सैकड़ों जीबी के बड़े संग्रह पढ़ने की सुविधा शुरू की. हमारे एक ZIM संग्रह का साइज़ 166 जीबी है! यह एपीआई, किसी भी ब्राउज़र में काम करता है. यहां तक कि बहुत पुराने ब्राउज़र में भी यह काम करता है. इसलिए, जब नए एपीआई काम नहीं करते, तब यह यूनिवर्सल फ़ॉलबैक के तौर पर काम करता है. Kiwix के मामले में, यह एलिमेंट तय करना उतना ही आसान है जितना एचटीएमएल में input एलिमेंट तय करना:

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

चुनने के बाद, इनपुट एलिमेंट में फ़ाइल ऑब्जेक्ट होते हैं. ये ऑब्जेक्ट, स्टोरेज में मौजूद डेटा का रेफ़रंस देने वाले मेटाडेटा होते हैं. तकनीकी तौर पर, Kiwix का ऑब्जेक्ट-ओरिएंटेड बैकएंड, क्लाइंट-साइड JavaScript में लिखा गया है. यह ज़रूरत के हिसाब से, बड़े संग्रह के छोटे-छोटे हिस्से पढ़ता है. अगर उन स्लाइस को डिकंप्रेस करना ज़रूरी है, तो बैकएंड उन्हें Wasm डिकंप्रेसर को पास करता है. साथ ही, अनुरोध किए जाने पर, तब तक और स्लाइस पाता है, जब तक कि पूरा ब्लॉब (आम तौर पर कोई लेख या एसेट) डिकंप्रेस नहीं हो जाता. इसका मतलब है कि बड़े संग्रह को कभी भी पूरी तरह से मेमोरी में पढ़ने की ज़रूरत नहीं होती.

File API एक यूनिवर्सल API है. हालांकि, इसमें एक समस्या है, जिसकी वजह से Kiwix JS ऐप्लिकेशन, नेटिव ऐप्लिकेशन की तुलना में पुराने और खराब लगते हैं: इसके लिए, उपयोगकर्ता को फ़ाइल पिकर का इस्तेमाल करके संग्रह चुनने होंगे या हर बार ऐप्लिकेशन लॉन्च करने पर, ऐप्लिकेशन में फ़ाइल को खींचकर छोड़ना होगा. इसकी वजह यह है कि इस API की मदद से, एक सेशन से दूसरे सेशन में ऐक्सेस की अनुमतियां बरकरार रखने का कोई तरीका नहीं है.

कई डेवलपर की तरह, Kiwix JS डेवलपर ने भी शुरुआत में यूज़र एक्सपीरियंस को बेहतर बनाने के लिए, Electron का इस्तेमाल किया. ElectronJS एक बेहतरीन फ़्रेमवर्क है. इसमें कई बेहतरीन सुविधाएं मिलती हैं. जैसे, Node API का इस्तेमाल करके फ़ाइल सिस्टम का पूरा ऐक्सेस. हालांकि, इसकी कुछ समस्याएं हैं:

  • यह सिर्फ़ डेस्कटॉप ऑपरेटिंग सिस्टम पर चलता है.
  • यह बड़ी और भारी (70 एमबी से 100 एमबी) हो.

हर ऐप्लिकेशन में Chromium की पूरी कॉपी शामिल होने की वजह से, Electron ऐप्लिकेशन का साइज़ बहुत ज़्यादा होता है. यह साइज़, कम किए गए और बंडल किए गए PWA के सिर्फ़ 5.1 एमबी के साइज़ के मुकाबले काफ़ी ज़्यादा है!

क्या Kiwix, PWA के उपयोगकर्ताओं के लिए इस स्थिति को बेहतर बना सकता था?

फ़ाइल सिस्टम को ऐक्सेस करने का एपीआई

साल 2019 के आस-पास, Kiwix को एक नए एपीआई के बारे में पता चला. यह एपीआई, Chrome 78 में ऑरिजिन ट्रायल के तौर पर इस्तेमाल किया जा रहा था. तब इसे नेटिव फ़ाइल सिस्टम एपीआई कहा जाता था. इसमें, किसी फ़ाइल या फ़ोल्डर के लिए फ़ाइल हैंडल पाने और उसे IndexedDB डेटाबेस में सेव करने की सुविधा का वादा किया गया था. अहम बात यह है कि यह हैंडल, ऐप्लिकेशन के सेशन के बीच बना रहता है. इसलिए, उपयोगकर्ता को ऐप्लिकेशन को फिर से लॉन्च करते समय, फ़ाइल या फ़ोल्डर को फिर से चुनने की ज़रूरत नहीं पड़ती. हालांकि, उन्हें अनुमति के लिए एक छोटे प्रॉम्प्ट का जवाब देना पड़ता है. प्रोडक्शन में आने से पहले, इसका नाम बदलकर फ़ाइल सिस्टम ऐक्सेस एपीआई कर दिया गया था. साथ ही, मुख्य हिस्सों को WHATWG ने फ़ाइल सिस्टम एपीआई (एफ़एसए) के तौर पर स्टैंडर्ड कर दिया था.

तो, एपीआई का फ़ाइल सिस्टम ऐक्सेस हिस्सा कैसे काम करता है? ध्यान देने के लिए कुछ ज़रूरी बातें:

  • यह एक असाइनोक्रोनस एपीआई है (वेब वर्कर्स में खास फ़ंक्शन को छोड़कर).
  • फ़ाइल या डायरेक्ट्री पिकर को प्रोग्राम के हिसाब से लॉन्च किया जाना चाहिए. इसके लिए, उपयोगकर्ता के जेस्चर (यूज़र इंटरफ़ेस (यूआई) एलिमेंट पर क्लिक या टैप) को कैप्चर करना होगा.
  • अगर उपयोगकर्ता को किसी नई सेशन में, पहले से चुनी गई फ़ाइल को फिर से ऐक्सेस करने की अनुमति देनी है, तो इसके लिए भी उपयोगकर्ता के जेस्चर की ज़रूरत होगी. असल में, अगर उपयोगकर्ता के जेस्चर से अनुमति नहीं ली जाती है, तो ब्राउज़र अनुमति का अनुरोध नहीं दिखाएगा.

फ़ाइल और डायरेक्ट्री हैंडल को सेव करने के लिए, IndexedDB API का इस्तेमाल करना पड़ता है. हालांकि, यह कोड अपेक्षाकृत आसान है. अच्छी बात यह है कि कुछ लाइब्रेरी आपके लिए ज़्यादातर काम कर सकती हैं. जैसे, browser-fs-access. Kiwix JS में, हमने सीधे तौर पर उन एपीआई के साथ काम करने का फ़ैसला लिया है जिनके बारे में काफ़ी जानकारी उपलब्ध है.

फ़ाइल और डायरेक्ट्री पिकर खोलना

फ़ाइल पिकर खोलने पर, यह कुछ ऐसा दिखता है. यहां 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 } की मदद से कई फ़ाइलें चुनने की अनुमति देनी है, तो हर हैंडल को प्रोसेस करने वाले सभी 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 में देखा जा सकता है. हालांकि, अगर इसका इस्तेमाल सिर्फ़ किसी फ़ाइल या फ़ोल्डर के हैंडल को सेव और वापस पाने के लिए किया जाता है, तो इसे काफ़ी आसान बनाया जा सकता है.

डायरेक्ट्री प्रोसेस करना थोड़ा मुश्किल है, क्योंकि आपको अपनी पसंद की फ़ाइलें या फ़ाइल टाइप ढूंढने के लिए, चुने गए डायरेक्ट्री में मौजूद एंट्री को असिंक्रोनस 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) { … }) के साथ फ़ाइल पाने की ज़रूरत होगी. इसके अलावा, async function में const file = await entry.getFile() का इस्तेमाल करके भी फ़ाइल को ऐक्सेस किया जा सकता है.

क्या हम आगे बढ़ सकते हैं?

ऐप्लिकेशन को फिर से खोलने पर, उपयोगकर्ता को इस्तेमालकर्ता के जेस्चर से अनुमति देने की ज़रूरत होती है. इससे फ़ाइल और फ़ोल्डर को (फिर से) खोलने में थोड़ी परेशानी होती है. हालांकि, यह किसी फ़ाइल को फिर से चुनने के मुकाबले काफ़ी आसान है. फ़िलहाल, Chromium के डेवलपर कोड को फ़ाइनल कर रहे हैं. इससे, इंस्टॉल किए गए PWA के लिए परमानेंट अनुमतियां मिल सकेंगी. PWA के कई डेवलपर, इस सुविधा का इंतज़ार कर रहे थे.

लेकिन, अगर हमें इंतज़ार न करना पड़े, तो क्या होगा?! Kiwix के डेवलपर को हाल ही में पता चला है कि फ़ाइल ऐक्सेस एपीआई की एक नई सुविधा का इस्तेमाल करके, अनुमति के लिए पूछे जाने वाले सभी सवालों को हटाया जा सकता है. यह सुविधा, Chromium और Firefox, दोनों ब्राउज़र पर काम करती है. साथ ही, Safari पर भी कुछ हद तक काम करती है, लेकिन अब भी FileSystemWritableFileStream मौजूद नहीं है. यह नई सुविधा, Origin का निजी फ़ाइल सिस्टम है.

पूरी तरह नेटिव सिस्टम: Origin का निजी फ़ाइल सिस्टम

Origin Private File System (OPFS), Kiwix के PWA में अब भी एक्सपेरिमेंट के तौर पर उपलब्ध है. हालांकि, टीम इसे आज़माने के लिए उपयोगकर्ताओं को बढ़ावा देने को लेकर काफ़ी उत्साहित है. इसकी वजह यह है कि यह नेटिव ऐप्लिकेशन और वेब ऐप्लिकेशन के बीच का फ़ासला कम करता है. इसके मुख्य फ़ायदे यहां दिए गए हैं:

  • OPFS में मौजूद संग्रहों को बिना अनुमति के ऐक्सेस किया जा सकता है. उपयोगकर्ता किसी लेख को पढ़ना और संग्रह को ब्राउज़ करना, ठीक उसी जगह से फिर से शुरू कर सकते हैं जहां उन्होंने पिछले सेशन में छोड़ा था.
  • इससे, इसमें सेव की गई फ़ाइलों को ऐक्सेस करने की सुविधा को बेहतर बनाया जाता है: Android पर, हमें फ़ाइलों को ऐक्सेस करने की स्पीड में पांच से दस गुना तक की बढ़ोतरी दिखी है.

File API का इस्तेमाल करके, Android में फ़ाइल को स्टैंडर्ड तरीके से ऐक्सेस करने में काफ़ी समय लगता है. खास तौर पर, अगर बड़े संग्रह को डिवाइस के स्टोरेज के बजाय, माइक्रोएसडी कार्ड में सेव किया जाता है, तो ऐसा होना आम बात है. आम तौर पर, Kiwix के उपयोगकर्ताओं को ऐसा करना पड़ता है. इस नए एपीआई के साथ, यह सब बदल जाता है. ज़्यादातर उपयोगकर्ता, OPFS में 97 जीबी की फ़ाइल सेव नहीं कर पाएंगे. यह फ़ाइल, डिवाइस के स्टोरेज में सेव होती है, न कि माइक्रोएसडी कार्ड के स्टोरेज में. हालांकि, यह छोटे से लेकर मध्यम साइज़ के संग्रह को सेव करने के लिए बेहतरीन है. क्या आपको WikiProject Medicine से सबसे ज़्यादा जानकारी वाला मेडिकल एन्साइक्लोपीडिया चाहिए? कोई समस्या नहीं है, 1.7 जीबी का यह डेटा आसानी से OPFS में फ़िट हो जाता है! (सलाह: ऐप्लिकेशन में मौजूद लाइब्रेरी में, अन्यmdwiki_en_all_maxi खोजें.)

ओपीएफ़एस कैसे काम करता है

ओपीएफ़एस, ब्राउज़र से मिलने वाला एक फ़ाइल सिस्टम है. यह हर ऑरिजिन के लिए अलग होता है. इसे Android पर ऐप्लिकेशन के स्कोप वाले स्टोरेज के तौर पर देखा जा सकता है. फ़ाइलों को उपयोगकर्ता के लिए दिखने वाले फ़ाइल सिस्टम से, OPFS में इंपोर्ट किया जा सकता है. इसके अलावा, उन्हें सीधे तौर पर OPFS में डाउनलोड भी किया जा सकता है. एपीआई की मदद से, OPFS में फ़ाइलें बनाई भी जा सकती हैं. ओपीएफ़एस में जाने के बाद, उन्हें डिवाइस के बाकी हिस्सों से अलग कर दिया जाता है. डेस्कटॉप पर, Chromium पर आधारित ब्राउज़र में, फ़ाइलों को 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() से मिलता है. इसका मतलब है कि उस कोड का फिर से इस्तेमाल किया जा सकता है जो उस FileSystemDirectoryHandle को हैंडल करता है. साथ ही, इसे 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);
    });
});

ध्यान रखें कि आपको archiveList कलेक्शन में मौजूद किसी भी एंट्री पर getFile() का इस्तेमाल करना होगा.

ओपीएफ़एस में फ़ाइलें इंपोर्ट करना

तो, ओपीएफ़एस में फ़ाइलें कैसे डालें? जल्दबाज़ी न करें! सबसे पहले, आपको यह तय करना होगा कि आपको कितना स्टोरेज चाहिए. साथ ही, यह पक्का करना होगा कि उपयोगकर्ता 97 जीबी की कोई ऐसी फ़ाइल अपलोड न कर पाएं जो स्टोरेज में फ़िट न हो.

अनुमानित कोटा देखना आसान है: navigator.storage.estimate().then(function (estimate) { … });. उपयोगकर्ता को यह जानकारी दिखाने का तरीका ढूंढना थोड़ा मुश्किल है. Kiwix ऐप्लिकेशन में, हमने चेकबॉक्स के ठीक बगल में दिखने वाला एक छोटा इन-ऐप्लिकेशन पैनल चुना है. इससे उपयोगकर्ताओं को ओपीएफ़एस आज़माने में मदद मिलती है:

इस्तेमाल किए गए स्टोरेज का प्रतिशत और बचे हुए स्टोरेज को गीगाबाइट में दिखाने वाला पैनल.

पैनल में जानकारी, 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() का इस्तेमाल नहीं करना ज़रूरी है, क्योंकि यह तरीका 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!)
      });
  }
});

उपयोगकर्ता से पूछने वाला डायलॉग बॉक्स कि क्या उसे ओरिजनल निजी फ़ाइल सिस्टम में .zim फ़ाइलों की सूची जोड़नी है.

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 का यूज़र इंटरफ़ेस, जिसमें सबसे नीचे एक बार है. इसमें उपयोगकर्ता को ऐप्लिकेशन बंद न करने की चेतावनी दी गई है. साथ ही, .zim संग्रह के डाउनलोड की प्रोग्रेस भी दिखाई गई है.

डाउनलोड करने में काफ़ी समय लग सकता है. इसलिए, Kiwix ऐप्लिकेशन में डाउनलोड करने के दौरान, उपयोगकर्ताओं को ऐप्लिकेशन का इस्तेमाल करने की अनुमति दी जाती है. हालांकि, बैनर हमेशा दिखता रहता है, ताकि उपयोगकर्ताओं को यह याद रहे कि डाउनलोड पूरा होने तक ऐप्लिकेशन बंद न करें.

ऐप्लिकेशन में मिनी फ़ाइल मैनेजर लागू करना

इस दौरान, Kiwix PWA के डेवलपर को पता चला कि OPFS में फ़ाइलें जोड़ना ही काफ़ी नहीं है. साथ ही, ऐप्लिकेशन में उपयोगकर्ताओं को ऐसी फ़ाइलें मिटाने का तरीका भी देना ज़रूरी था जिनकी अब उन्हें ज़रूरत नहीं है. साथ ही, OPFS में लॉक की गई फ़ाइलों को, उपयोगकर्ता के लिए उपलब्ध फ़ाइल सिस्टम में वापस एक्सपोर्ट करने का तरीका भी देना ज़रूरी था. इसलिए, ऐप्लिकेशन में एक छोटा फ़ाइल मैनेजमेंट सिस्टम लागू करना ज़रूरी हो गया.

यहां हम Chrome के लिए उपलब्ध शानदार OPFS Explorer एक्सटेंशन के बारे में बताना चाहते हैं. यह एक्सटेंशन, Edge में भी काम करता है. यह Developer टूल में एक टैब जोड़ता है. इससे आपको यह पता चलता है कि OPFS में क्या है. साथ ही, गड़बड़ी वाली या काम न करने वाली फ़ाइलों को भी मिटाया जा सकता है. इससे यह पता लगाने में मदद मिली कि कोड काम कर रहा है या नहीं, डाउनलोड के व्यवहार को मॉनिटर करने में, और आम तौर पर हमारे डेवलपमेंट एक्सपेरिमेंट को ठीक करने में.

फ़ाइल एक्सपोर्ट करने की सुविधा, चुनी गई फ़ाइल या डायरेक्ट्री पर फ़ाइल हैंडल पाने की सुविधा पर निर्भर करती है. इसमें Kiwix, एक्सपोर्ट की गई फ़ाइल को सेव करेगा. इसलिए, यह सुविधा सिर्फ़ उन कॉन्टेक्स्ट में काम करती है जहां window.showSaveFilePicker() तरीके का इस्तेमाल किया जा सकता है. अगर Kiwix की फ़ाइलें कई जीबी से छोटी होतीं, तो हम मेमोरी में ब्लॉब बना पाते, उसे यूआरएल देते, और फिर उसे उपयोगकर्ता को दिखने वाले फ़ाइल सिस्टम में डाउनलोड कर पाते. माफ़ करें, इतने बड़े संग्रह के लिए ऐसा नहीं किया जा सकता. अगर यह सुविधा काम करती है, तो फ़ाइल को एक्सपोर्ट करना काफ़ी आसान है: यह ठीक वैसा ही है, जैसे किसी फ़ाइल को OPFS में सेव करना. इसके लिए, सेव की जाने वाली फ़ाइल का हैंडल पाएं और उपयोगकर्ता से window.showSaveFilePicker() का इस्तेमाल करके, उसे सेव करने के लिए कोई जगह चुनने के लिए कहें. इसके बाद, saveHandle पर createWriteable() का इस्तेमाल करें. कोड को देखने के लिए, रिपॉज़िटरी में जाएं.

फ़ाइल को मिटाने की सुविधा सभी ब्राउज़र पर काम करती है. इसे dirHandle.removeEntry('filename') का इस्तेमाल करके मिटाया जा सकता है. Kiwix के मामले में, हमने ऊपर की तरह ही OPFS एंट्री को दोहराने का विकल्प चुना, ताकि हम यह देख सकें कि चुनी गई फ़ाइल पहले से मौजूद है या नहीं और पुष्टि करने के लिए कह सकें. हालांकि, ऐसा सभी के लिए ज़रूरी नहीं है. अगर आपको इस बारे में ज़्यादा जानकारी चाहिए, तो हमारे कोड की जांच करें.

हमने यह फ़ैसला लिया है कि Kiwix के यूज़र इंटरफ़ेस (यूआई) को इन विकल्पों के बटन से भरा न जाए. इसके बजाय, हमने संग्रह की सूची के नीचे छोटे आइकॉन डाले हैं. इनमें से किसी आइकॉन पर टैप करने से, संग्रह की सूची का रंग बदल जाएगा. इससे उपयोगकर्ता को यह पता चलेगा कि वह क्या करने जा रहा है. इसके बाद, उपयोगकर्ता किसी संग्रह पर क्लिक या टैप करता है और पुष्टि करने के बाद, उस पर काम किया जाता है (एक्सपोर्ट या मिटाना).

उपयोगकर्ता से पूछने वाला डायलॉग कि क्या उसे .zim फ़ाइल मिटानी है.

आखिर में, यहां फ़ाइल मैनेजमेंट की उन सभी सुविधाओं का स्क्रीनकास्ट डेमो दिया गया है जिनके बारे में ऊपर बताया गया है. इनमें, OPFS में फ़ाइल जोड़ना, उसमें सीधे डाउनलोड करना, मिटाना, और उपयोगकर्ता को दिखने वाले फ़ाइल सिस्टम में एक्सपोर्ट करना शामिल है.

डेवलपर का काम कभी खत्म नहीं होता

ओपीएफ़एस, पीडब्ल्यूए के डेवलपर के लिए एक बेहतरीन इनोवेशन है. यह फ़ाइल मैनेजमेंट की बेहतरीन सुविधाएं उपलब्ध कराता है. इनकी मदद से, नेटिव ऐप्लिकेशन और वेब ऐप्लिकेशन के बीच का अंतर कम किया जा सकता है. हालांकि, डेवलपर कभी भी संतुष्ट नहीं होते! OPFS काफ़ी हद तक बेहतर है, लेकिन पूरी तरह से नहीं… यह बहुत अच्छी बात है कि मुख्य सुविधाएं, Chromium और Firefox, दोनों ब्राउज़र में काम करती हैं. साथ ही, इन्हें Android के साथ-साथ डेस्कटॉप पर भी लागू किया गया है. हमें उम्मीद है कि Safari और iOS पर भी जल्द ही, सभी सुविधाएं उपलब्ध होंगी. ये समस्याएं अब भी मौजूद हैं:

  • फ़िलहाल, Firefox में OPFS के लिए 10 जीबी का कोटा तय है. भले ही, डिस्क में कितना भी स्टोरेज हो. हालांकि, ज़्यादातर PWA के लेखकों के लिए यह कोटा काफ़ी हो सकता है, लेकिन Kiwix के लिए यह काफ़ी कम है. फ़िलहाल, Chromium ब्राउज़र में ऐसा करने की सुविधा उपलब्ध है.
  • फ़िलहाल, मोबाइल ब्राउज़र या डेस्कटॉप Firefox पर, बड़ी फ़ाइलों को ओपीएफ़एस से, उपयोगकर्ता को दिखने वाले फ़ाइल सिस्टम में एक्सपोर्ट नहीं किया जा सकता. ऐसा इसलिए, क्योंकि window.showSaveFilePicker() लागू नहीं किया गया है. इन ब्राउज़र में, बड़ी फ़ाइलें OPFS में ट्रैप हो जाती हैं. यह Kiwix के कॉन्टेंट को सभी के लिए उपलब्ध कराने के सिद्धांत के मुताबिक नहीं है. साथ ही, इससे उपयोगकर्ताओं के बीच संग्रह शेयर करने की सुविधा भी खत्म हो जाएगी. ऐसा खास तौर पर उन इलाकों में होगा जहां इंटरनेट की सुविधा कम या महंगी है.
  • उपयोगकर्ता के पास यह कंट्रोल करने का विकल्प नहीं होता कि OPFS वर्चुअल फ़ाइल सिस्टम किस स्टोरेज का इस्तेमाल करेगा. यह समस्या खास तौर पर मोबाइल डिवाइसों पर होती है. इनमें उपयोगकर्ताओं के पास, डिवाइस के स्टोरेज के मुकाबले, माइक्रोएसडी कार्ड में ज़्यादा स्टोरेज हो सकता है.

हालांकि, कुल मिलाकर, ये समस्याएं मामूली हैं. इनके बावजूद, PWA में फ़ाइल ऐक्सेस करने की सुविधा काफ़ी बेहतर है. Kiwix PWA की टीम, Chromium के उन डेवलपर और समर्थकों का बहुत आभारी है जिन्होंने सबसे पहले फ़ाइल सिस्टम ऐक्सेस एपीआई का सुझाव दिया और उसे डिज़ाइन किया. साथ ही, ब्राउज़र वेंडर के बीच Origin निजी फ़ाइल सिस्टम की अहमियत पर सहमति बनाने के लिए, कड़ी मेहनत की. Kiwix JS PWA के लिए, इसने यूज़र एक्सपीरियंस से जुड़ी कई समस्याओं को हल किया है. इन समस्याओं की वजह से, ऐप्लिकेशन को पहले इस्तेमाल करना मुश्किल था. साथ ही, इससे हमें सभी के लिए Kiwix कॉन्टेंट को ऐक्सेस करने की सुविधा को बेहतर बनाने में मदद मिली है. कृपया Kiwix के PWA वर्शन को आज़माएं और डेवलपर को बताएं कि आपको यह कैसा लगा!

पीडब्ल्यूए की सुविधाओं के बारे में कुछ बेहतरीन संसाधनों के लिए, इन साइटों पर जाएं:

  • Project Fugu API का शोकेस: यह वेब ऐप्लिकेशन का एक कलेक्शन है. इसमें उन सुविधाओं को दिखाया गया है जो नेटिव ऐप्लिकेशन और PWA के बीच के अंतर को कम करती हैं.
  • PWA आज क्या कर सकता है: इस सेक्शन में, PWA की मदद से आज क्या-क्या किया जा सकता है, इस बारे में जानकारी दी गई है.