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) के लिए, अलग-अलग नेटिव ऐप्लिकेशन उपलब्ध कराता है. हालांकि, इस केस स्टडी में प्रोग्रेसिव वेब ऐप्लिकेशन (पीडब्ल्यूए) पर फ़ोकस किया जाएगा. इसका मकसद, ऐसे किसी भी डिवाइस के लिए एक यूनिवर्सल और आसान समाधान है, जिसमें मॉडर्न ब्राउज़र है.

हम ऐसे यूनिवर्सल वेब ऐप्लिकेशन को डेवलप करने से जुड़ी चुनौतियों पर ध्यान देंगे जिसे बड़े कॉन्टेंट संग्रह को पूरी तरह से ऑफ़लाइन ऐक्सेस करने की सुविधा देनी है. साथ ही, हम कुछ आधुनिक 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 ऑफ़लाइन ब्राउज़र

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

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

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

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

File API की मदद से, Kiwix JS ने सैकड़ों जीबी स्टोरेज को आसानी से पढ़ा जा सकता था. वहीं, कम मेमोरी वाले डिवाइसों में भी यह प्रोसेस एक जीबी स्टोरेज में से 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 डीकंप्रेसर के पास भेज देता है. अनुरोध किए जाने पर और स्लाइस भेजे जाते हैं. ऐसा तब तक होता है, जब तक पूरे ब्लॉब (आम तौर पर कोई लेख या ऐसेट) को डीकंप्रेस नहीं किया जाता. इसका मतलब है कि बड़े संग्रह को कभी भी पूरी तरह से मेमोरी में पढ़ने की ज़रूरत नहीं होती.

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

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

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

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

क्या कोई ऐसा तरीका था जिससे Kiwix, पीडब्ल्यूए का इस्तेमाल करने वालों के लिए स्थिति को बेहतर बना सके?

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

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

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

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

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

फ़ाइल और डायरेक्ट्री पिकर खोले जा रहे हैं

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

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

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

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

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

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

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

तो, 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() का इस्तेमाल नहीं करना ज़रूरी है, क्योंकि यह तरीका 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 काफ़ी हद तक बेहतर है, लेकिन पूरी तरह से नहीं… यह बहुत अच्छी बात है कि मुख्य सुविधाएं, Chromium और Firefox, दोनों ब्राउज़र में काम करती हैं. साथ ही, इन्हें Android के साथ-साथ डेस्कटॉप पर भी लागू किया गया है. हमें उम्मीद है कि Safari और iOS पर भी जल्द ही, सभी सुविधाएं उपलब्ध होंगी. ये समस्याएं बनी रहती हैं:

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

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

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

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