Kaynak gizli dosya sistemi

Dosya Sistemi Standardı, sayfanın kaynağına özel ve kullanıcı tarafından görülemeyen bir depolama uç noktası olarak özel kaynak dosya sistemi (OPFS) sunar. Bu sistem, performans için yüksek oranda optimize edilmiş özel bir dosya türüne isteğe bağlı erişim sağlar.

Kaynak özel dosya sistemi modern tarayıcılar tarafından desteklenir ve File System Living Standard'da Web Hiper Metin Uygulama Teknolojisi Çalışma Grubu (WHATWG) tarafından standartlaştırılır.

Tarayıcı desteği

  • Chrome: 86.
  • Kenar: 86.
  • Firefox: 111.
  • Safari: 15.2.

Kaynak

Motivasyon

Bilgisayarınızdaki dosyaları düşündüğünüzde muhtemelen bir dosya hiyerarşisini düşünürsünüz: işletim sisteminizin dosya gezginiyle keşfedebileceğiniz klasörler halinde düzenlenmiş dosyalar. Örneğin, Windows'da Tolga adlı bir kullanıcının Yapılacaklar listesi C:\Users\Tom\Documents\ToDo.txt'da olabilir. Bu örnekte ToDo.txt dosya adı, Users, Tom ve Documents ise klasör adlarıdır. Windows'daki "C:", sürücünün kök dizinini temsil eder.

Web'de dosyalarla çalışmanın geleneksel yolu

Bir web uygulamasında yapılacaklar listesini düzenlemek için genellikle şu akış izlenir:

  1. Kullanıcı dosyayı bir sunucuya yükler veya <input type="file"> ile istemcide açır.
  2. Kullanıcı değişikliklerini yapar ve ardından JavaScript aracılığıyla programatik olarak click() eklediğiniz bir <a download="ToDo.txt> ile oluşturulan dosyayı indirir.
  3. Klasörleri açmak için <input type="file" webkitdirectory> ürününde özel bir özellik kullanırsınız. Bu özellik, kendi adına olmasına rağmen neredeyse evrensel tarayıcı desteğine sahiptir.

Web'de dosyalarla çalışmanın modern yolu

Bu akış, kullanıcıların dosya düzenleme konusundaki düşüncelerini yansıtmaz ve kullanıcıların, giriş dosyalarının indirilmiş kopyalarına sahip olduğu anlamına gelir. Bu nedenle File System Access API, tam olarak adlarının belirttiği gibi üç seçici yöntemi (showOpenFilePicker(), showSaveFilePicker() ve showDirectoryPicker()) kullanıma sundu. Aşağıdaki gibi bir akışı etkinleştirirler:

  1. ToDo.txtshowOpenFilePicker() ile açın ve bir FileSystemFileHandle nesnesi alın.
  2. FileSystemFileHandle nesnesinden, dosya tutamacının getFile() yöntemini çağırarak bir File alın.
  3. Dosyayı değiştirin, ardından herkese açık kullanıcı adında requestPermission({mode: 'readwrite'}) değerini çağırın.
  4. Kullanıcı izin isteğini kabul ederse değişiklikleri orijinal dosyaya geri kaydedin.
  5. Alternatif olarak showSaveFilePicker() simgesini tıklayıp kullanıcının yeni bir dosya seçmesine izin verin. (Kullanıcı daha önce açılmış bir dosyayı seçerse içeriğinin üzerine yazılır.) Tekrarlanan kayıtlar için dosya adını saklayabilirsiniz. Böylece, dosya kaydetme iletişim kutusunu tekrar göstermeniz gerekmez.

Web üzerindeki dosyalarla çalışmaya ilişkin kısıtlamalar

Bu yöntemler aracılığıyla erişilebilen dosya ve klasörler, kullanıcı tarafından görülebilen dosya sisteminde bulunur. Web'den kaydedilen dosyalar ve özel olarak yürütülebilir dosyalar web işareti ile işaretlenir. Bu nedenle, tehlikeli olabilecek bir dosya yürütülmeden önce işletim sisteminin gösterebileceği ek bir uyarı olur. Ek bir güvenlik özelliği olarak web'den alınan dosyalar Güvenli Tarama tarafından da korunur. Bu özelliği, basitlik ve bu makale bağlamında bulut tabanlı bir virüs taraması olarak düşünebilirsiniz. File System Access API'yi kullanarak bir dosyaya veri yazdığınızda yazma işlemi yerinde değil, geçici bir dosya kullanılarak yapılır. Dosya, tüm bu güvenlik kontrollerinden geçmediği sürece değiştirilmez. Tahmin edebileceğiniz gibi, bu çalışma, mümkün olduğunda (ör. macOS'te) uygulanan iyileştirmelere rağmen dosya işlemlerini nispeten yavaşlatır. Yine de her write() çağrısı bağımsız olduğundan, arka planda dosyayı açar, belirtilen ofsete doğru arama yapar ve son olarak veri yazar.

İşlemenin temeli olarak dosyalar

Aynı zamanda dosyalar, verileri kaydetmenin mükemmel bir yoludur. Örneğin, SQLite veritabanlarının tamamını tek bir dosyada saklar. Görsel işlemede kullanılan mipmap'ler de buna örnek gösterilebilir. Mipharitalar, önceden hesaplanmış ve optimize edilmiş görüntü dizileridir. Bunların her biri, bir öncekinin giderek daha düşük çözünürlüklü temsilidir. Bu da, daha hızlı yakınlaştırma gibi birçok işlemi beraberinde getirir. Peki web uygulamaları, web tabanlı dosya işlemeyle ilgili performans maliyetleri olmadan dosyaların avantajlarından nasıl yararlanabilir? Cevap, orijinal özel dosya sistemidir.

Kullanıcı tarafından görülebilen ve kaynak gizli dosya sistemi

İşletim sisteminin dosya gezgini kullanılarak gezinilen, kullanıcıların okuyabileceği, yazabileceği, taşıyabileceği ve yeniden adlandırabileceği dosya ve klasörlerin bulunduğu kullanıcı tarafından görülebilen dosya sisteminin aksine, kaynak özel dosya sisteminin kullanıcılar tarafından görülmesi amaçlanmamıştır. Adından da anlaşılacağı gibi, kaynak özel dosya sistemindeki dosyalar ve klasörler özeldir. Daha açık belirtmek gerekirse, bir sitenin kaynağı için özeldir. Geliştirici Araçları Konsolu'nda location.origin yazarak bir sayfanın kaynağını keşfedin. Örneğin, https://developer.chrome.com/articles/ sayfasının kaynağı https://developer.chrome.com'dır (yani /articles kısmı kaynağın bir parçası değildir). Kaynak teorisi hakkında daha fazla bilgiyi "Aynı site" ve "aynı kaynak" kavramlarını anlama başlıklı makalede bulabilirsiniz. Aynı kaynağı paylaşan tüm sayfalar aynı kaynak özel dosya sistemi verilerini görebilir. Bu nedenle https://developer.chrome.com/docs/extensions/mv3/getstarted/extensions-101/, önceki örnektekiyle aynı ayrıntıları görebilir. Her kaynağın kendi bağımsız kaynak gizli dosya sistemi vardır. Yani https://developer.chrome.com kaynağının gizli dosya sistemi, https://web.dev kaynağının gizli dosya sisteminden tamamen farklıdır. Windows'da, kullanıcı tarafından görülebilen dosya sisteminin kök dizini C:\\'tür. Kaynak özel dosya sisteminin eşdeğeri, navigator.storage.getDirectory() adlı ayarsız yöntem çağrılarak erişilen, başlangıçta boş bir kök dizindir. Kullanıcı tarafından görülebilen dosya sistemi ile orijinal gizli dosya sisteminin karşılaştırması için aşağıdaki şemaya bakın. Şemada, kök dizin dışındaki her şeyin kavramsal olarak aynı olduğu gösterilmektedir. Veri ve depolama alanı ihtiyaçlarınıza göre düzenleyebileceğiniz bir dosya ve klasör hiyerarşisi vardır.

İki örnek dosya hiyerarşisine sahip, kullanıcı tarafından görülebilen dosya sisteminin ve kaynak gizli dosya sisteminin şeması. Kullanıcı tarafından görülebilen dosya sisteminin giriş noktası sembolik bir sabit disktir, kaynak gizli dosya sisteminin giriş noktası ise &quot;navigator.storage.getDirectory&quot; yönteminin çağrılmasıdır.

Kaynak özel dosya sisteminin özellikleri

Kaynak özel dosya sistemi, tarayıcıdaki diğer depolama mekanizmaları (ör. localStorage veya IndexedDB) gibi tarayıcı kota kısıtlamalarına tabidir. Kullanıcı tüm tarama verilerini veya tüm site verilerini temizlediğinde kaynak gizli dosya sistemi de silinir. navigator.storage.estimate() komutunu çağırın ve sonuçta ortaya çıkan yanıt nesnesinde, uygulamanızın halihazırda kullandığı depolama alanı miktarını görmek için usage girişine bakın. Bu giriş, fileSystem girişine özel olarak bakmak istediğiniz usageDetails nesnesindeki depolama mekanizmasına göre ayrılmıştır. Orijinal özel dosya sistemi kullanıcı tarafından görülemediğinden izin istemi ve Güvenli Tarama kontrolü yoktur.

Kök dizine erişim

Kök dizine erişmek için aşağıdaki komutu çalıştırın. Boş bir dizin herkese açık kullanıcı adı, daha açık bir ifadeyle FileSystemDirectoryHandle elde edersiniz.

const opfsRoot = await navigator.storage.getDirectory();
// A FileSystemDirectoryHandle whose type is "directory"
// and whose name is "".
console.log(opfsRoot);

Ana iş parçacığı veya Web Çalışanı

Kaynak gizli dosya sistemini kullanmanın iki yolu vardır: ana iş parçacığında veya Web İşleyici'de. Web işçileri ana iş parçacığını engelleyemez. Bu bağlamda API'ler senkronize olabilir. Bu, genellikle ana iş parçacığında izin verilmeyen bir modeldir. Eşzamanlı API'ler vaatlerle uğraşmak zorunda kalmadıkları için daha hızlı olabilir ve dosya işlemleri genellikle WebAssembly'de derlenebilen C gibi dillerde eşzamanlıdır.

// This is synchronous C code.
FILE *f;
f = fopen("example.txt", "w+");
fputs("Some text\n", f);
fclose(f);

Mümkün olan en hızlı dosya işlemlerine ihtiyacınız varsa veya WebAssembly ile uğraşıyorsanız Web İşleyici'de kaynak gizli dosya sistemini kullanma bölümüne atlayın. Aksi takdirde okumaya devam edebilirsiniz.

Ana iş parçacığında kaynak gizli dosya sistemini kullanın

Yeni dosya ve klasör oluşturma

Kök klasör oluşturduktan sonra sırasıyla getFileHandle() ve getDirectoryHandle() yöntemlerini kullanarak dosya ve klasör oluşturun. {create: true} iletilerek dosya veya klasör mevcut değilse oluşturulur. Başlangıç noktası olarak yeni oluşturulmuş bir dizin kullanarak bu işlevleri çağırarak bir dosya hiyerarşisi oluşturun.

const fileHandle = await opfsRoot
    .getFileHandle('my first file', {create: true});
const directoryHandle = await opfsRoot
    .getDirectoryHandle('my first folder', {create: true});
const nestedFileHandle = await directoryHandle
    .getFileHandle('my first nested file', {create: true});
const nestedDirectoryHandle = await directoryHandle
    .getDirectoryHandle('my first nested folder', {create: true});

Önceki kod örneğinden elde edilen dosya hiyerarşisi.

Mevcut dosya ve klasörlere erişme

Adlarını biliyorsanız getFileHandle() veya getDirectoryHandle() yöntemlerini çağırarak, dosya ya da klasörün adını ileterek önceden oluşturulmuş dosya ve klasörlere erişin.

const existingFileHandle = await opfsRoot.getFileHandle('my first file');
const existingDirectoryHandle = await opfsRoot
    .getDirectoryHandle('my first folder');

Okuma için bir dosya tutma yeriyle ilişkilendirilmiş dosyayı alma

FileSystemFileHandle, dosya sistemindeki bir dosyayı temsil eder. İlişkili File değerini almak için getFile() yöntemini kullanın. File nesnesi, belirli bir Blob türüdür ve Blob öğesinin yapabileceği herhangi bir bağlamda kullanılabilir. Özellikle FileReader, URL.createObjectURL(), createImageBitmap() ve XMLHttpRequest.send() hem Blobs hem de Files'i kabul eder. Bir FileSystemFileHandle'dan File elde etmek, verileri "serbest bırakır". Böylece, verilere erişebilir ve kullanıcı tarafından görülebilen dosya sistemine sunabilirsiniz.

const file = await fileHandle.getFile();
console.log(await file.text());

Akışla bir dosyaya yazma

createWritable() işlevini çağırarak verileri bir dosyaya aktarın. Bu işlev, içeriği write() edebileceğiniz bir FileSystemWritableFileStream oluşturur. Son olarak, aktarmayı close() yapmanız gerekir.

const contents = 'Some text';
// Get a writable stream.
const writable = await fileHandle.createWritable();
// Write the contents of the file to the stream.
await writable.write(contents);
// Close the stream, which persists the contents.
await writable.close();

Dosya ve klasör silme

Dosya veya dizin tutamaclarının belirli remove() yöntemini çağırarak dosyaları ve klasörleri silin. Bir klasörü tüm alt klasörleri içerecek şekilde silmek için {recursive: true} seçeneğini iletin.

await fileHandle.remove();
await directoryHandle.remove({recursive: true});

Alternatif olarak, bir dizindeki silinecek dosyanın veya klasörün adını biliyorsanız removeEntry() yöntemini kullanın.

directoryHandle.removeEntry('my first nested file');

Dosyaları ve klasörleri taşıma ve yeniden adlandırma

move() yöntemini kullanarak dosya ve klasörleri yeniden adlandırabilir ve taşıyabilirsiniz. Taşıma ve yeniden adlandırma işlemleri birlikte veya ayrı ayrı yapılabilir.

// Rename a file.
await fileHandle.move('my first renamed file');
// Move a file to another directory.
await fileHandle.move(nestedDirectoryHandle);
// Move a file to another directory and rename it.
await fileHandle
    .move(nestedDirectoryHandle, 'my first renamed and now nested file');

Dosya veya klasörün yolunu çözümleme

Belirli bir dosya veya klasörün, referans dizinle ilişkili olarak nerede bulunduğunu öğrenmek için resolve() yöntemini kullanın ve yönteme bağımsız değişken olarak bir FileSystemHandle gönderin. Kaynak gizli dosya sistemindeki bir dosya veya klasörün tam yolunu almak için navigator.storage.getDirectory() aracılığıyla alınan referans dizin olarak kök dizini kullanın.

const relativePath = await opfsRoot.resolve(nestedDirectoryHandle);
// `relativePath` is `['my first folder', 'my first nested folder']`.

İki dosya veya klasör etiketinin aynı dosyayı ya da klasörü gösterip göstermediğini kontrol etme

Bazen iki herkese açık kullanıcı adınız olur ve bunların aynı dosyayı mı yoksa klasörü mü gösterdiğini bilemezsiniz. Bunun doğru olup olmadığını kontrol etmek için isSameEntry() yöntemini kullanın.

fileHandle.isSameEntry(nestedFileHandle);
// Returns `false`.

Klasörün içeriğini listeleme

FileSystemDirectoryHandle, for await…of döngüsüyle yineleme yaptığınız bir eşzamansız yinelemedir. Eşzamansız iterasyon aracı olarak, ihtiyacınız olan bilgilere bağlı olarak seçebileceğiniz entries(), values() ve keys() yöntemlerini de destekler:

for await (let [name, handle] of directoryHandle) {}
for await (let [name, handle] of directoryHandle.entries()) {}
for await (let handle of directoryHandle.values()) {}
for await (let name of directoryHandle.keys()) {}

Bir klasörün ve tüm alt klasörlerin içeriğini yinelemeli olarak listeleme

Yinelemeyle eşleştirilen eşzamansız döngüler ve işlevlerle uğraşırken hata yapmak kolaydır. Aşağıdaki işlev, bir klasörün ve tüm alt klasörlerinin içeriğini (tüm dosyalar ve boyutları dahil) listelemek için başlangıç noktası olarak kullanılabilir. Dosya boyutlarına ihtiyacınız yoksa işlevi basitleştirebilirsiniz. Bunun için directoryEntryPromises.push ifadesinin bulunduğu yerde handle.getFile() vaadini değil, doğrudan handle öğesini gönderin.

  const getDirectoryEntriesRecursive = async (
    directoryHandle,
    relativePath = '.',
  ) => {
    const fileHandles = [];
    const directoryHandles = [];
    const entries = {};
    // Get an iterator of the files and folders in the directory.
    const directoryIterator = directoryHandle.values();
    const directoryEntryPromises = [];
    for await (const handle of directoryIterator) {
      const nestedPath = `${relativePath}/${handle.name}`;
      if (handle.kind === 'file') {
        fileHandles.push({ handle, nestedPath });
        directoryEntryPromises.push(
          handle.getFile().then((file) => {
            return {
              name: handle.name,
              kind: handle.kind,
              size: file.size,
              type: file.type,
              lastModified: file.lastModified,
              relativePath: nestedPath,
              handle
            };
          }),
        );
      } else if (handle.kind === 'directory') {
        directoryHandles.push({ handle, nestedPath });
        directoryEntryPromises.push(
          (async () => {
            return {
              name: handle.name,
              kind: handle.kind,
              relativePath: nestedPath,
              entries:
                  await getDirectoryEntriesRecursive(handle, nestedPath),
              handle,
            };
          })(),
        );
      }
    }
    const directoryEntries = await Promise.all(directoryEntryPromises);
    directoryEntries.forEach((directoryEntry) => {
      entries[directoryEntry.name] = directoryEntry;
    });
    return entries;
  };

Web çalışanında kaynak özel dosya sistemini kullanma

Daha önce belirtildiği gibi, Web İşçileri ana iş parçacığını engelleyemez. Bu nedenle, bu bağlamda eşzamanlı yöntemlere izin verilir.

Senkronize erişim işleyicisi alma

Mümkün olan en hızlı dosya işlemlerine giriş noktası, createSyncAccessHandle() çağrısı yapılarak normal bir FileSystemFileHandle öğesinden alınan FileSystemSyncAccessHandle değeridir.

const fileHandle = await opfsRoot
    .getFileHandle('my highspeed file.txt', {create: true});
const syncAccessHandle = await fileHandle.createSyncAccessHandle();

Yerinde eşzamanlı dosya yöntemleri

Eşzamanlı erişim sahibi olduktan sonra, tümü eşzamanlı olan ve yerinde, hızlı dosya yöntemlerine erişim elde edersiniz.

  • getSize(): Dosyanın bayt cinsinden boyutunu döndürür.
  • write(): İsteğe bağlı olarak belirli bir ofsette bir arabelleğin içeriğini dosyaya yazar ve yazılan bayt sayısını döndürür. Döndürülen yazılan bayt sayısını kontrol etmek, arayanların hataları ve kısmi yazma işlemlerini algılayıp ele almasını sağlar.
  • read(): Dosyanın içeriğini isteğe bağlı olarak belirli bir ofsette bir arabelleğe okur.
  • truncate(): Dosyayı belirtilen boyuta yeniden boyutlandırır.
  • flush(): Dosya içeriğinin, write() aracılığıyla yapılan tüm değişiklikleri içermesini sağlar.
  • close(): Erişim tutamacını kapatır.

Yukarıda belirtilen tüm yöntemleri kullanan bir örnek aşağıda verilmiştir.

const opfsRoot = await navigator.storage.getDirectory();
const fileHandle = await opfsRoot.getFileHandle('fast', {create: true});
const accessHandle = await fileHandle.createSyncAccessHandle();

const textEncoder = new TextEncoder();
const textDecoder = new TextDecoder();

// Initialize this variable for the size of the file.
let size;
// The current size of the file, initially `0`.
size = accessHandle.getSize();
// Encode content to write to the file.
const content = textEncoder.encode('Some text');
// Write the content at the beginning of the file.
accessHandle.write(content, {at: size});
// Flush the changes.
accessHandle.flush();
// The current size of the file, now `9` (the length of "Some text").
size = accessHandle.getSize();

// Encode more content to write to the file.
const moreContent = textEncoder.encode('More content');
// Write the content at the end of the file.
accessHandle.write(moreContent, {at: size});
// Flush the changes.
accessHandle.flush();
// The current size of the file, now `21` (the length of
// "Some textMore content").
size = accessHandle.getSize();

// Prepare a data view of the length of the file.
const dataView = new DataView(new ArrayBuffer(size));

// Read the entire file into the data view.
accessHandle.read(dataView);
// Logs `"Some textMore content"`.
console.log(textDecoder.decode(dataView));

// Read starting at offset 9 into the data view.
accessHandle.read(dataView, {at: 9});
// Logs `"More content"`.
console.log(textDecoder.decode(dataView));

// Truncate the file after 4 bytes.
accessHandle.truncate(4);

Bir dosyayı kaynak özel dosya sisteminden kullanıcı tarafından görülebilen dosya sistemine kopyalama

Yukarıda belirtildiği gibi, kaynak gizli dosya sisteminden kullanıcıların görebildiği dosya sistemine dosya taşımak mümkün değildir ancak dosyaları kopyalayabilirsiniz. showSaveFilePicker() yalnızca ana iş parçacığında gösterildiğinden ve İşçi iş parçacığında gösterilmediğinden kodu burada çalıştırdığınızdan emin olun.

// On the main thread, not in the Worker. This assumes
// `fileHandle` is the `FileSystemFileHandle` you obtained
// the `FileSystemSyncAccessHandle` from in the Worker
// thread. Be sure to close the file in the Worker thread first.
const fileHandle = await opfsRoot.getFileHandle('fast');
try {
  // Obtain a file handle to a new file in the user-visible file system
  // with the same name as the file in the origin private file system.
  const saveHandle = await showSaveFilePicker({
    suggestedName: fileHandle.name || ''
  });
  const writable = await saveHandle.createWritable();
  await writable.write(await fileHandle.getFile());
  await writable.close();
} catch (err) {
  console.error(err.name, err.message);
}

Kaynak özel dosya sisteminde hata ayıklama

Yerleşik DevTools desteği eklenene kadar (crbug/1284595 adresine bakın) kaynak özel dosya sisteminde hata ayıklama yapmak için OPFS Explorer Chrome uzantısını kullanın. Yukarıdaki Yeni dosya ve klasör oluşturma bölümündeki ekran görüntüsü doğrudan uzantıdan alınmıştır.

Chrome Web Mağazası&#39;ndaki OPFS Explorer Chrome Geliştirici Araçları uzantısı.

Uzantı yüklendikten sonra Chrome Geliştirici Araçları'nı açın, OPFS Gezgini sekmesini seçin. Artık dosya hiyerarşisini incelemeye hazırsınız. Dosya adını tıklayarak orijinal özel dosya sistemindeki dosyaları kullanıcı tarafından görülebilen dosya sistemine kaydedebilir ve çöp kutusu simgesini tıklayarak dosyaları ve klasörleri silebilirsiniz.

Demo

Kaynak özel dosya sistemini (OPFS Explorer uzantısını yüklüyorsanız) iş başında görün. Bu demoda, sistem WebAssembly'de derlenmiş SQLite veritabanı için arka uç olarak kullanılır. Glitch'teki kaynak koduna göz atın. Aşağıdaki yerleşik sürümde, kaynak gizli dosya sistemi arka ucu kullanılmaz (çünkü iframe kaynaktan farklıdır). Demoyu ayrı bir sekmede açtığınızda ise bu arka uç kullanılır.

Sonuçlar

WHEREWG tarafından belirtilen kaynak gizli dosya sistemi, web'deki dosyaları kullanma ve bunlarla etkileşim kurma şeklimizi şekillendirmiştir. Kullanıcının görebildiği dosya sistemiyle mümkün olmayan yeni kullanım alanlarına olanak tanıdı. Apple, Mozilla ve Google gibi büyük tarayıcı tedarikçileri bu projeye katılmış ve ortak bir vizyon paylaşmaktadır. Orijinal özel dosya sisteminin geliştirilmesi büyük ölçüde ortak bir çalışmadır ve geliştiricilerden ve kullanıcılardan gelen geri bildirimler, ilerleme için çok önemlidir. Standartı hassaslaştırıp iyileştirmeye devam ederken whatwg/fs deposunda sorun veya çekme isteği şeklinde geri bildirimlerinizi bekliyoruz.

Teşekkür ederiz

Bu makale Austin Sully, Etienne Noël ve Rachel Andrew tarafından incelendi. Unsplash'taki Christina Rumpf tarafından oluşturulan hero resim.