Kiwix PWA, kullanıcıların çevrimdışı kullanım için internetten Gigabaytlarca veriyi nasıl depolamasına olanak tanıyor?

Geoffrey Kantaris
Geoffrey Kantaris
Stéphane Coillet-Matillon
Stéphane Coillet-Matillon

Sol tarafında plastik bir sandalyeyle sade bir masanın üstünde duran bir dizüstü bilgisayarın etrafında toplanan insanlar. Arka plan, gelişmekte olan bir ülkedeki bir okul gibi görünüyor.

Bu örnek olay incelemesinde, kâr amacı gütmeyen bir kuruluş olan Kiwix'in, kullanıcıların büyük internet arşivlerini çevrimdışı kullanım için indirmelerini ve depolamalarını sağlamak amacıyla PWA (Progresif Web Uygulaması) teknolojisi ile File System Access API'yi nasıl kullandığı incelenmektedir. Kiwix PWA'da, dosya yönetimini geliştirerek izin istemi olmadan arşivlere iyileştirilmiş erişim sağlayan yeni bir tarayıcı özelliği olan Kaynak Özel Dosya Sistemi (OPFS) ile ilişkili kodun teknik olarak uygulanması hakkında bilgi edinin. Makalede, bu yeni dosya sistemiyle ilgili zorluklar ele alınmış ve gelecekte ortaya çıkabilecek olası gelişmeler vurgulanmaktadır.

Kiwix Hakkında

Uluslararası Telekomünikasyon Birliği'ne göre, web'in doğuşunun üzerinden 30 yıldan uzun bir süre geçmesine rağmen, dünya nüfusunun üçte biri hâlâ internete güvenilir erişim için bekliyor. Hikaye burada mı bitiyor? Tabii ki hayır. İsviçre merkezli kâr amacı gütmeyen bir kuruluş olan Kiwix'teki çalışanlar, sınırlı internet erişimi olan veya hiç olmayan kullanıcılara bilgi sunmayı amaçlayan bir açık kaynak uygulama ve içerik ekosistemi geliştirdi. Ekip, siz internete kolayca erişemediğinizde birisinin önemli kaynakları, bağlantı olduğunda ve nerede kullanılabilir olduğunu, sizin için indirebileceği ve daha sonra çevrimdışı kullanım için yerel olarak depolayabileceğini düşünüyor. Vikipedi, Project Gutenberg, Stack Exchange ve hatta TED konuşmaları gibi pek çok önemli site artık ZIM dosyası adı verilen yüksek sıkıştırılmış arşivlere dönüştürülebiliyor ve Kiwix tarayıcısı tarafından anında okunabiliyor.

ZIM arşivleri, çoğunlukla HTML, JavaScript ve CSS'yi depolamak için yüksek verimli Zstandard (ZSTD) sıkıştırmasını (daha eski sürümler XZ kullanılır) kullanır. Resimler ise genellikle sıkıştırılmış WebP biçimine dönüştürülür. Her ZIM ayrıca bir URL ve bir başlık dizini içerir. Burada sıkıştırma çok önemlidir. Çünkü Wikipedia'nın İngilizce dilindeki tamamı (6, 4 milyon makale, resimler), ZIM biçimine dönüştürüldükten sonra 97 GB'a sıkıştırılır. Bu da tüm insan bilgisinin artık orta düzey bir Android telefona sığabileceğini anlamaya kadar pek faydalı görünebilir. Vikipedi'nin matematik ve tıp gibi temalı versiyonları da dahil olmak üzere pek çok daha küçük kaynak da sunulmaktadır.

Kiwix, masaüstü (Windows/Linux/macOS) ve mobil (iOS/Android) kullanımını hedefleyen bir dizi yerel uygulama sunmaktadır. Ancak bu örnek olay çalışması, modern bir tarayıcıya sahip tüm cihazlar için evrensel ve basit bir çözüm sunmayı amaçlayan Progresif Web Uygulaması (PWA) özelliğine odaklanacaktır.

Büyük içerik arşivlerine tamamen çevrimdışı olarak hızlı erişim sağlanması gereken evrensel bir Web uygulamasının ve bu zorluklara yenilikçi ve heyecan verici çözümler sağlayan File System Access API ve Origin Private File System olmak üzere bazı modern JavaScript API'lerinin geliştirilmesinde karşılaşılan zorluklara göz atacağız.

Çevrimdışı kullanım için bir web uygulaması mı?

Kiwix kullanıcıları, birçok farklı ihtiyaçları olan eklektik bir gruptur ve Kiwix'in içeriklerine erişecekleri cihazlar ve işletim sistemleri üzerinde çok az kontrol imkanı vardır veya hiç yoktur. Bu cihazlardan bazıları, özellikle de dünyanın düşük gelirli bölgelerinde yavaş veya eski olabilir. Kiwix mümkün olduğunca fazla kullanım örneğini kapsamaya çalışsa da kuruluş, tüm cihazlarda en evrensel yazılım parçası olan web tarayıcısını kullanarak daha da fazla kullanıcıya ulaşabileceğini fark etti. Bu nedenle, JavaScript'te yazılabilecek tüm uygulamaların sonunda JavaScript'te yazılacağını belirten Atwood Yasası'ndan esinlenerek, Kiwix'in bazı geliştiricileri yaklaşık 10 yıl önce Kiwix yazılımını C++'dan JavaScript'e taşımaya karar verdi.

Bu bağlantı noktasının Kiwix HTML5 adı verilen ilk sürümü, artık kullanılmayan Firefox OS ve tarayıcı uzantıları içindi. Temelinde, Emscripten derleyicisi kullanılarak ASM.js ve sonraki Wasm ya da WebAssembly JavaScript dili için derlenen bir C++ sıkıştırma motoru (XZ ve ZSTD) yer alıyordu. Daha sonra Kiwix JS adını aldı ve tarayıcı uzantıları hâlâ aktif olarak geliştiriliyor.

Kiwix JS Çevrimdışı Tarayıcı

Progresif Web Uygulaması'nı (PWA) girin. Bu teknolojinin potansiyelinin farkına varan Kiwix geliştiricileri, Kiwix JS'nin özel bir PWA sürümünü oluşturdu ve uygulamanın özellikle çevrimdışı kullanım, yükleme, dosya işleme ve dosya sistemi erişimi alanlarında yerel benzeri özellikler sunmasına olanak tanıyacak OS entegrasyonları eklemeye karar verdi.

Çevrimdışı öncelikli PWA'lar son derece hafif olduğundan kesintili veya pahalı mobil internetin olduğu bağlamlar için mükemmeldir. Bunun arkasındaki teknoloji, Kiwix JS'ye dayalı tüm uygulamalar tarafından kullanılan Service Worker API ve ilgili Cache API. Bu API'ler uygulamaların bir sunucu gibi davranmasına olanak tanır, görüntülenen ana dokümandan veya makaleden Getirme İstekleri'ni alır ve ZIM arşivinden bir Yanıt ayıklayıp oluşturmak için bunları (JS) arka ucuna yönlendirir.

Depolama alanı, her yerde depolama alanı

ZIM arşivlerinin büyük boyutu, depolama alanı ve buna erişim (özellikle mobil cihazlarda), Kiwix geliştiricilerinin en çok karşılaştığı konu olabilir. Birçok Kiwix son kullanıcısı, internet mevcut olduğunda daha sonra çevrimdışı kullanmak için uygulama içinde içerik indirir. Diğer kullanıcılar, dosyaları bir torrent kullanarak PC'ye indirir ve sonra bir mobil veya tablet cihaza aktarırlar. Bir kısmı da düzenli veya pahalı mobil internet olan yerlerde USB çubuklarında veya taşınabilir sabit sürücülerde içerik alışverişi yaparlar. Kullanıcıların erişebileceği rastgele konumlardan içeriğe erişmenin tüm bu yollarının Kiwix JS ve Kiwix PWA tarafından desteklenmesi gerekir.

Kiwix JS'nin düşük bellekli cihazlarda bile yüzlerce GB'lık devasa arşivleri (ZIM arşivlerimizin biri 166 GB'tır) okumasını mümkün kılan şey File API'dir. Bu API, çok eski tarayıcılar dahil olmak üzere tüm tarayıcılarda evrensel olarak desteklenir ve bu nedenle yeni API'ler desteklenmediğinde evrensel yedek görevi görür. Bu, Kiwix'in örneğinde olduğu gibi, HTML'de input öğesi tanımlamak kadar kolaydır:

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

Seçilen giriş öğesi, temelde depolama alanındaki temel verilere referans veren meta veriler olan Dosya nesnelerini tutar. Teknik olarak Kiwix'in istemci taraflı JavaScript ile yazılmış nesne odaklı arka ucu, gerektiğinde büyük arşivin küçük dilimlerini okur. Bu dilimlerin sıkıştırmasının açılması gerekiyorsa arka uç, bunları Wasm açıcıya iletir ve istendiğinde tam blob'un sıkıştırması açılıncaya kadar (genellikle bir makale veya öğe) başka dilimler alır. Bu, büyük arşivin hiçbir zaman tamamen belleğe okunması gerekmediği anlamına gelir.

Bu API'nin evrensel bir dezavantajı vardır. Bu dezavantajı, Kiwix JS uygulamalarının yerel uygulamalara kıyasla hantal ve eski moda görünmesini sağlar. Kullanıcının dosya seçici kullanarak arşiv seçmesi veya uygulama her başlatıldığında bir dosyayı uygulamaya sürükleyip bırakması gerekir. Çünkü bu API sayesinde bir oturumdan diğerine erişim izinlerini sürdürmek mümkün değildir.

Kiwix JS geliştiricileri, pek çok geliştirici gibi bu kötü kullanıcı deneyimini hafifletmek için başlangıçta Electron rotasını terk etti. ElectronJS, Node API'leri kullanarak dosya sistemine tam erişim dahil olmak üzere güçlü özellikler sunan muhteşem bir çerçevedir. Ancak, bu yöntemin bazı bilinen dezavantajları vardır:

  • Yalnızca masaüstü işletim sistemlerinde çalışır.
  • Büyük ve ağırdır (70 MB-100 MB).

Electron uygulamalarının boyutu, her uygulamaya Chromium'un eksiksiz bir kopyasının eklenmesi nedeniyle, küçültülmüş ve paket halinde sunulan PWA'nın 5,1 MB'lık boyutuyla çok olumsuz yönde karşılaştırılır.

Peki Kiwix, PWA kullanıcıları için mevcut durumu iyileştirebilir miydi?

Yardıma hazır File System Access API

Kiwix, 2019 yılı civarında Chrome 78'de kaynak denemesinden geçen ve ardından Native File System API olarak adlandırılan yeni bir API olduğunu fark etti. Bir dosya veya klasör için bir dosya tanıtıcısı alıp bunu IndexedDB veritabanında depolayabilmeyi vaat ediyordu. Daha da önemlisi, bu herkese açık kullanıcı adı uygulama oturumları arasında kalmaya devam eder. Böylece kullanıcı uygulamayı yeniden başlatırken dosyayı veya klasörü tekrar seçmek zorunda kalmaz (ancak hızlı bir izin istemini yanıtlamaları gerekir). Üretime ulaştığında, File System Access API olarak yeniden adlandırılmış ve çekirdek parçaları WHATWG tarafından File System API (FSA) olarak standartlaştırılmıştı.

Peki API'nin Dosya Sistemi Erişimi bölümü nasıl çalışır? Dikkat edilmesi gereken birkaç önemli nokta:

  • Eşzamansız bir API'dir (Web İşçileri'ndeki özel işlevler hariç).
  • Dosya veya dizin seçicilerin, kullanıcı hareketi (bir kullanıcı arayüzü öğesine tıklama veya dokunma) yakalanarak programlı bir şekilde başlatılması gerekir.
  • Kullanıcının daha önce seçilen bir dosyaya (yeni bir oturumda) yeniden erişim izni vermesi için bir kullanıcı hareketi de gerekir. Aslında tarayıcı, kullanıcı hareketiyle başlatılmıyorsa izin istemini göstermeyi reddeder.

Dosya ve dizin işleyicilerini depolamak için kullanışsız IndexedDB API'yi kullanmanın dışında, kod nispeten basittir. Neyse ki, browser-fs-access gibi işin zor kısmını sizin adınıza yapan birkaç kitaplık vardır. Kiwix JS'de, çok iyi belgelenmiş API'lerle doğrudan çalışmaya karar verdik.

Dosya ve dizin seçicileri açma

Bir dosya seçici şuna benzer şekilde açılır (Burada Promise'leri kullanabilirsiniz, ancak async/await şekeri tercih ediyorsanız Geliştiriciler için Chrome eğiticisine göz atın):

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,
    );
  });

Basitlik sağlamak için bu kodun yalnızca ilk seçilen dosyayı işlediğini (ve birden fazla dosya seçmeyi yasakladığını) unutmayın. { multiple: true } ile birden fazla dosya seçilmesine izin vermek isterseniz her herkese açık kullanıcı adını işleyen tüm Promise'leri Promise.all().then(...) ifadesi içinde sarmalamanız yeterlidir. Örneğin:

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);
)};

Bununla birlikte, özellikle Kiwix kullanıcıları tüm ZIM dosyalarını aynı dizinde düzenleme eğiliminde olduğundan, birden fazla dosya seçmek tartışmasız bir şekilde kullanıcıdan bu dosyaları içeren dizini seçmesini istemekle daha iyi bir yöntem olabilir. Dizin seçiciyi başlatma kodu, window.showDirectoryPicker.then(function (dirHandle) { … }); kullanmanız dışında yukarıdakiyle hemen hemen aynıdır.

Dosya veya dizin tutamacı işleniyor

Herkese açık kullanıcı adını öğrendikten sonra bunu işlemeniz gerekir. Bu durumda processFileHandle işlevi şu şekilde görünebilir:

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;
  });
}

Dosya tanıtıcısını depolama işlevini sağlamanız gerektiğini unutmayın. Soyutlama kitaplığı kullanmadıkça, bunun için herhangi bir kolay yöntem yoktur. Kiwix'in bunu uyguladığını cache.js dosyasında görebilirsiniz, ancak yalnızca dosya veya klasör tanıtıcısını depolamak ve almak için kullanılıyorsa bu işlem önemli ölçüde basitleştirilebilir.

Dizinlerin işlenmesi, istediğiniz dosyaları veya dosya türlerini bulmak için seçilen dizindeki eşzamansız entries.next() girişlerini kullanarak tekrarlamanız gerektiğinden biraz daha karmaşıktır. Bunu yapmanın çeşitli yolları vardır ancak Kiwix PWA'da kullanılan kod özet olarak şöyledir:

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 içindeki her bir giriş için, dosyayı daha sonra kullanmanız gerektiğinde entry.getFile().then(function (file) { … }) ile veya async function içinde const file = await entry.getFile() kullanarak eş değer dosyayı almanız gerekeceğini unutmayın.

Biraz daha devam edebilir miyiz?

Kullanıcının, uygulamanın sonraki başlatmalarında bir kullanıcı hareketiyle başlatılan izin verme şartı, dosya ve klasör açma (yeniden) açma konusunda bir miktar sürtünmeye yol açar ancak bu işlem, dosyayı yeniden seçmeye zorlamaktan çok daha akıcıdır. Chromium geliştiricileri, şu anda yüklü PWA'lar için kalıcı izinlere olanak tanıyacak kodu son haline getirme aşamasındadır. Birçok PWA geliştiricisinin talep ettiği ve heyecanla beklenen bir özellik bu.

Peki beklemek zorunda kalmazsak ne olur? Kiwix geliştiricileri kısa süre önce, hem Chromium hem de Firefox tarayıcıları tarafından desteklenen (ve Safari tarafından kısmen desteklenen, ancak hâlâ eksik FileSystemWritableFileStream olan) File Access API'nin yepyeni bir özelliğini kullanarak tüm izin istemlerini ortadan kaldırmanın mümkün olduğunu fark ettiler. Bu yeni özellik, Kaynak Özel Dosya Sistemi'dir.

Tamamen yerel olma: Kaynak Özel Dosya Sistemi

Kaynak Gizli Dosya Sistemi (OPFS), Kiwix PWA'da hâlâ deneysel bir özellik olsa da yerel uygulamalar ile web uygulamaları arasındaki boşlukta büyük ölçüde köprü oluşturduğundan ekip kullanıcıları bunu denemeye teşvik etmekten büyük heyecan duyuyor. Temel avantajları şunlardır:

  • OPFS'deki arşivlere, lansman sırasında bile izin istemi olmadan erişilebilir. Kullanıcılar, hiçbir sorun yaşamadan önceki oturumda kaldıkları yerden bir makaleyi okumaya ve arşive göz atmaya devam edebilir.
  • İçinde depolanan dosyalara son derece optimize edilmiş erişim sunar: Android'de hızda beş ila on kat daha hızlı iyileşmeler olduğunu görüyoruz.

Büyük arşivler cihazın depolama alanında değil bir microSD kartta depolanıyorsa Android'de File API ile standart dosya erişimi çok yavaş olur, özellikle de Kiwix kullanıcıları için durum budur. Tüm bunlar bu yeni API ile değişir. Çoğu kullanıcı OPFS'de 97 GB boyutunda dosya depolayamaz (microSD kart depolama alanını değil, cihazın depolama alanını kullanır), ancak bu özellik küçük ve orta boyutlu arşivleri depolamak için idealdir. WikiProject Medicine'den en eksiksiz medikal ansiklopediyi mi istiyorsunuz? Sorun değil, 1,7 GB'ta OPFS'ye kolayca sığar. (İpucu: Uygulama içi kitaplık'ta othermdwiki_en_all_maxi kodunu arayın.)

OPFS'nin işleyiş şekli

OPFS, tarayıcı tarafından sağlanan ve her kaynak için ayrı bir dosya sistemidir. Bu sistem, Android'de uygulama kapsamlı depolamaya benzer şekilde düşünülebilir. Dosyalar, kullanıcının görebildiği dosya sisteminden OPFS'ye aktarılabilir veya doğrudan sisteme indirilebilir (API, OPFS'de dosya oluşturulmasına da olanak tanır). OPFS'ye girdikten sonra cihazın geri kalanından izole edilir. Masaüstü Chromium tabanlı tarayıcılarda, dosyaları OPFS'den kullanıcının görebildiği dosya sistemine geri aktarmak da mümkündür.

OPFS'yi kullanmak için ilk adım, navigator.storage.getDirectory() kullanarak buna erişim isteğinde bulunmaktır (yine await kullanarak kod görmek isterseniz Kaynak Özel Dosya Sistemi bölümünü okuyun):

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

Bundan elde ettiğiniz herkese açık kullanıcı adı, yukarıda belirtilen window.showDirectoryPicker() ile aynı türde FileSystemDirectoryHandle olur. Yani, bu işlem için gereken kodu yeniden kullanabilirsiniz (neyse ki indexedDB içinde saklamaya gerek yoktur, ihtiyacınız olduğunda bu kodu alabilirsiniz). OPFS'de zaten bazı dosyalarınız olduğunu ve bunları kullanmak istediğinizi varsayalım. Ardından, daha önce gösterilen iterateAsyncDirEntries() işlevini kullanarak aşağıdaki gibi bir şey yapabilirsiniz:

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 dizisinden çalışmak istediğiniz tüm girişlerde getFile() kullanmanız gerektiğini unutmayın.

Dosyaları OPFS'ye aktarma

Peki, dosyaları OPFS'ye nasıl aktarırsınız? O kadar da kolay değil! Öncelikle, çalışmanız gereken depolama alanı miktarını tahmin etmeniz ve kullanıcıların, dosya sığmayacaksa 97 GB'lık bir dosya koymaya çalışmadığından emin olmanız gerekir.

Tahmini kotayı öğrenmek kolaydır: navigator.storage.estimate().then(function (estimate) { … });. Biraz daha zor olan, bunun kullanıcıya nasıl gösterileceğini belirlemektir. Kiwix uygulamasında, kullanıcıların OPFS'yi denemelerine olanak tanıyan onay kutusunun hemen yanında görünen küçük bir uygulama içi panel ekledik:

Kullanılan depolama alanını yüzde cinsinden ve Gigabayt cinsinden kalan kullanılabilir depolama alanını gösteren panel.

Panel, estimate.quota ve estimate.usage kullanılarak doldurulur. Örneğin:

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>';
});

Gördüğünüz gibi, kullanıcıların kullanıcı tarafından görülebilen dosya sisteminden OPFS'ye dosya eklemesine olanak tanıyan bir düğme de vardır. Burada iyi bir haberimiz var: İçe aktarılacak gerekli Dosya nesnesini (veya nesneleri) almak için File API'yi kullanabilirsiniz. Aslında, window.showOpenFilePicker() yönteminin kullanılmaması önemlidir. Çünkü bu yöntem Firefox tarafından desteklenmezken OPFS desteklenir.

Yukarıdaki ekran görüntüsünde gördüğünüz Dosya ekle düğmesi eski bir dosya seçici değildir, ancak tıklandığında veya dokunulduğunda click()gizli bir eski seçiciyi (<input type="file" multiple … /> öğesi) kullanır. Uygulama daha sonra, gizli dosya girdisinin change etkinliğini yakalar, dosyaların boyutunu kontrol eder ve kota için çok büyüklerse dosyaları reddeder. Her şey yolundaysa kullanıcıya bunları eklemek isteyip istemediğini sorun:

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!)
      });
  }
});

Kullanıcıya kaynak özel dosya sistemine bir .zim dosyaları listesi eklemek isteyip istemediğini soran iletişim kutusu.

Android gibi bazı işletim sistemlerinde arşivlerin içe aktarılması en hızlı işlem değildir. Bu nedenle Kiwix, arşivler içe aktarılırken bir banner ve küçük bir döner simge de gösterir. Ekip, bu işlem için bir ilerleme göstergesinin nasıl ekleneceğini anlayamadı. Her şeye çalışırsanız, cevapları bir kartpostalda göndermenizi rica ediyoruz!

Kiwix, importOPFSEntries() işlevini nasıl uyguladı? Bu, her bir dosyanın OPFS'ye etkili bir şekilde aktarılmasına olanak tanıyan fileHandle.createWriteable() yöntemini kullanmayı içerir. Tüm yoğun işler tarayıcı tarafından yapılır. (Kiwix, eski kod tabanımızla ilgili nedenlerle burada Promise'leri kullanmaktadır ancak bu örnekte await daha basit bir söz dizimi oluşturur ve kıyamet etkisi piramidinden kaçınır.)

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);
    });
}

Dosya akışını doğrudan OPFS'ye indirme

Bunun bir varyasyonu da, bir dosyanın internetten doğrudan OPFS'ye veya dizin işleyicinizin bulunduğu herhangi bir dizine (yani, window.showDirectoryPicker() ile seçilen dizinler) akış olarak ulaşabilmesidir. Yukarıdaki kodla aynı ilkeleri kullanır ancak ReadableStream ile uzak dosyadan okunan baytları sıraya alan bir Response denetleyicisi oluşturur. Sonuçta elde edilen Response.body, daha sonra OPFS içindeki yeni dosyanın yazarına bağlanır.

Bu durumda Kiwix, ReadableStream üzerinden geçen baytları sayabilir. Böylece kullanıcıya ilerleme durumu göstergesi sayesinde indirme işlemi sırasında uygulamadan çıkmama konusunda uyarır. Kod, burada gösterilemeyecek kadar karmaşık ama uygulamamız bir FOSS uygulaması olduğundan benzer bir şey yapmak istiyorsanız kaynağa bakabilirsiniz. Kiwix kullanıcı arayüzünün görünümü şöyledir (aşağıda gösterilen farklı ilerleme değerleri, banner'ı yalnızca yüzde değiştiğinde, İndirme ilerleme durumu panelini daha düzenli olarak güncellediği için):

Kiwix kullanıcı arayüzünde, alt kısımda kullanıcıya uygulamadan çıkmaması gerektiği konusunda uyarı veren ve .zim arşivinin indirme ilerlemesini gösteren bir çubuk yer alıyor.

İndirme işlemi oldukça uzun bir işlem olabildiğinden Kiwix, kullanıcıların işlem sırasında uygulamayı serbest bir şekilde kullanmalarına izin verirken banner'ın her zaman görüntülenmesini sağlayarak indirme işlemi tamamlanana kadar uygulamayı kapatmamalarının hatırlatılmasını sağlar.

Uygulama içinde mini dosya yöneticisi kullanma

Bu noktada Kiwix PWA geliştiricileri, OPFS'ye dosya eklemenin yeterli olmadığını fark etti. Uygulama ayrıca, kullanıcılara artık ihtiyaç duymadıkları dosyaları bu depolama alanından silme ve ideal olarak da OPFS'de kilitli olan dosyaları kullanıcının görebildiği dosya sistemine geri aktarma olanağı sunmalıydı. Verimli bir şekilde, uygulama içinde mini bir dosya yönetimi sistemi kurmak gerekli hale geldi.

Chrome'un mükemmel OPFS Explorer uzantısını (Edge'de de çalışır) kısaca anıyoruz. Geliştirici araçlarına, OPFS'deki içeriği tam olarak görmenize ve sahte veya başarısız dosyaları silmenize olanak tanıyan bir sekme ekler. Kodun çalışıp çalışmadığını kontrol etmek, indirme işlemlerinin davranışını izlemek ve genel olarak geliştirme denemelerimizi temizlemek için son derece değerliydi.

Dosya dışa aktarma özelliği, seçilen bir dosya veya dizinde Kiwix'in dışa aktarılan dosyayı kaydedeceği bir dosya tanıtıcısı alabilme özelliğine bağlıdır. Bu nedenle, bu özellik yalnızca window.showSaveFilePicker() yöntemini kullanabildiği bağlamlarda işe yarar. Kiwix dosyaları birkaç GB'tan küçük olsaydı bellekte bir blob oluşturup bu dosyaya bir URL verebilir ve ardından, kullanıcının görebildiği dosya sistemine indirebilirdik. Maalesef bu kadar büyük arşivlerle bu mümkün değil. Destekleniyorsa dışa aktarma işlemi oldukça basittir: OPFS'ye bir dosya kaydederken bunun tersini yapın (kaydedilecek dosyanın adını bulun, kullanıcıdan dosyayı window.showSaveFilePicker() ile kaydedeceği bir konum seçmesini isteyin, ardından saveHandle üzerinde createWriteable() kodunu kullanın). Kodu depoda görebilirsiniz.

Dosya silme işlemi tüm tarayıcılar tarafından desteklenir ve basit bir dirHandle.removeEntry('filename') ile gerçekleştirilebilir. Kiwix'in örneğinde, OPFS girişlerini yukarıda yaptığımız gibi yinelemeyi tercih ettik. Böylece, önce seçilen dosyanın mevcut olup olmadığını kontrol edip onay isteyebiliriz, ancak bu herkes için gerekli olmayabilir. İlginizi çekiyorsa, kodumuzu inceleyebilirsiniz.

Kiwix kullanıcı arayüzünü bu seçenekleri sunan düğmelerle karıştırmamaya ve bunun yerine doğrudan arşiv listesinin altına küçük simgeler yerleştirmeye karar verildi. Bu simgelerden birine hafifçe vurulduğunda arşiv listesinin rengi, kullanıcıya ne yapacağı hakkında görsel bir ipucu olarak değişir. Kullanıcı daha sonra arşivlerden birini tıklar veya arşivlerden birine dokunur ve ilgili işlem (dışa aktarma veya silme) (onaydan sonra) gerçekleştirilir.

Kullanıcıya .zim dosyasını silmek isteyip istemediğini soran iletişim kutusu.

Son olarak, yukarıda açıklanan tüm dosya yönetimi özelliklerinin ekran video kaydı demosunu görebilirsiniz: OPFS'ye dosya ekleme, doğrudan bu dosyaya dosya indirme, bir dosyayı silme ve kullanıcının görebildiği dosya sistemine dışa aktarma.

Geliştiricinin işi asla bitmez

OPFS, yerel uygulamalar ile web uygulamaları arasındaki boşluğu kapatmaya yönelik oldukça güçlü dosya yönetimi özellikleri sunan PWA geliştiricileri için mükemmel bir yeniliktir. Ancak geliştiriciler berbat durumda. Asla memnun kalmıyorlar. OPFS neredeyse mükemmel, ancak tam doğru değil... Ana özelliklerin hem Chromium hem de Firefox tarayıcılarında çalışması ve hem Android'de hem de masaüstünde uygulanıyor olması güzel. Tüm özelliklerin yakında Safari ve iOS'ta da uygulanacağını umuyoruz. Aşağıdaki sorunlar devam ediyor:

  • Firefox şu anda OPFS kotasına 10 GB'lık bir sınır koyar. Bu sınır ne kadar temel disk alanı olursa olsun. Çoğu PWA yazarı için bu yeterli olsa da Kiwix için bu oldukça kısıtlayıcıdır. Neyse ki Chromium tarayıcıları çok daha cömerttir.
  • window.showSaveFilePicker() uygulanmadığından, büyük dosyaların mobil tarayıcılarda veya masaüstü Firefox'ta OPFS'den kullanıcının görebildiği dosya sistemine dışa aktarılması şu anda mümkün değildir. Bu tarayıcılarda, büyük dosyalar etkili bir şekilde OPFS'ye hapsedilir. Bu, içeriğe açık erişim ve özellikle kesintili veya pahalı internet bağlantısı olan yerlerde kullanıcılar arasında arşiv paylaşma becerisine aykırıdır.
  • Kullanıcı, OPFS sanal dosya sisteminin hangi depolama alanını kullanacağını kontrol edemez. Bu durum özellikle mobil cihazlarda kullanıcıların microSD kartta çok fazla, cihazın depolama alanında ise çok az yer kapladığı mobil cihazlarda yaşanır.

Ancak sonuç olarak tüm bunlar, PWA'larda dosya erişimi konusunda ileriye yönelik çok büyük bir adım olacak, ufak tefek değişikliklerdir. Kiwix PWA ekibi, File System Access API'yi ilk öneren ve tasarlayan Chromium geliştiricileri ve destekçilerine, ayrıca tarayıcı tedarikçileri arasında Kaynak Özel Dosya Sistemi'nin önemi konusunda fikir birliğine varma konusundaki yoğun çalışmalarına minnettardır. Kiwix JS PWA için, geçmişte uygulamanın kafasını karıştıran birçok kullanıcı deneyimi sorununu giderdi ve kiwix içeriğinin herkes için erişilebilirliğini geliştirme arayışımızda bize yardımcı oldu. Lütfen Kiwix PWA'yı deneyin ve geliştiricilere düşüncelerinizi söyleyin.

PWA özellikleriyle ilgili bazı mükemmel kaynaklar için şu sitelere göz atın: