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

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

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

Kiwix के बारे में

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

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

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

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

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

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

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

Kiwix JS ऑफ़लाइन ब्राउज़र

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

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

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

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

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

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

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

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

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

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

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

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

नतीजे बचाने के लिए, File System Access API

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

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

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

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

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

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

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

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

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

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

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

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

OPFS एक फ़ाइल सिस्टम है जिसे ब्राउज़र उपलब्ध कराता है. यह हर ऑरिजिन के लिए अलग होता है. इसे Android के ऐप्लिकेशन के स्कोप वाले स्टोरेज की तरह ही समझा जा सकता है. फ़ाइलों को उपयोगकर्ता को दिखने वाले फ़ाइल सिस्टम से OPFS में इंपोर्ट किया जा सकता है या उन्हें सीधे तौर पर डाउनलोड किया जा सकता है. एपीआई, ओपीएफ़एस में भी फ़ाइलें बनाने की अनुमति देता है. ओपीएफ़एस में पहुंचने पर, उन्हें बाकी डिवाइस से अलग कर दिया जाता है. डेस्कटॉप पर Chromium-आधारित ब्राउज़र पर, फ़ाइलों को OPFS से वापस उपयोगकर्ता को दिखने वाले फ़ाइल सिस्टम में एक्सपोर्ट किया जा सकता है.

OPFS का इस्तेमाल करने के लिए, सबसे पहले navigator.storage.getDirectory() का इस्तेमाल करके इसके ऐक्सेस का अनुरोध करें (एक बार फिर, अगर आपको await का इस्तेमाल करके कोड देखना है, तो ऑरिजिन प्राइवेट फ़ाइल सिस्टम पढ़ें):

return navigator.storage
  .getDirectory()
  .then(function (handle) {
    return processDirHandle(handle);
  })
  .catch(function (err) {
    console.warn('Unable to get the OPFS directory entry', err);
  });

आपको इससे मिलने वाला हैंडल, ऊपर बताए गए window.showDirectoryPicker() से मिलता-जुलता FileSystemDirectoryHandle हैंडल है. इसका मतलब है कि इसे फिर से इस्तेमाल किया जा सकता है. इसका मतलब है कि इसे मैनेज करने वाले कोड को indexedDB में सेव करने की ज़रूरत नहीं है – बस ज़रूरत पड़ने पर इसे पाएं. मान लें कि आपके पास ओपीएफ़एस में पहले से कुछ फ़ाइलें हैं और आपको उनका इस्तेमाल करना है. इसके बाद, ऊपर दिखाए गए 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 ऐप्लिकेशन में, हमने चेकबॉक्स के बगल में एक छोटा सा इन-ऐप्लिकेशन पैनल चुना है, जो उपयोगकर्ताओं को ओपीएफ़एस को आज़माने की सुविधा देता है:

पैनल, जिसमें इस्तेमाल किया गया स्टोरेज प्रतिशत में और गीगाबाइट में बाकी उपलब्ध स्टोरेज दिखाया गया है.

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

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

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

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

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

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

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

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

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

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

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

PWA की सुविधाओं के बारे में जानने के लिए, इन साइटों पर जाएं:

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