Kaynak gizli dosya sistemi

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

Tarayıcı desteği

Origin private file system, modern tarayıcılar tarafından desteklenir ve WHATWG (Web Hypertext Application Technology Working Group) tarafından File System Living Standard'da standartlaştırılmıştır.

Browser Support

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

Source

Motivasyon

Bilgisayarınızdaki dosyaları düşündüğünüzde muhtemelen bir dosya hiyerarşisi aklınıza gelir: İşletim sisteminizin dosya gezginiyle keşfedebileceğiniz klasörlerde düzenlenmiş dosyalar. Örneğin, Windows'da Tom adlı bir kullanıcının Yapılacaklar listesi C:\Users\Tom\Documents\ToDo.txt konumunda olabilir. Bu örnekte, ToDo.txt dosya adı, Users, Tom ve Documents ise klasör adlarıdır. Windows'da `C:` sürücünün kök dizinini temsil eder.

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

Yapılacaklar listesini bir web uygulamasında düzenlemek için genellikle şu akış kullanılır:

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

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

Bu akış, kullanıcıların dosyaları düzenleme konusundaki düşüncelerini yansıtmaz ve kullanıcıların giriş dosyalarının kopyalarını indirmesine neden olur. Bu nedenle, File System Access API, tam olarak adlarının önerdiği işi yapan üç seçici yöntemi (showOpenFilePicker(), showSaveFilePicker() ve showDirectoryPicker()) kullanıma sundu. Bu özellikler, aşağıdaki gibi bir akış sağlar:

  1. ToDo.txt uygulamasını showOpenFilePicker() ile açın ve FileSystemFileHandle nesnesi alın.
  2. FileSystemFileHandle nesnesinden, dosya tutamacının getFile() yöntemini çağırarak File alın.
  3. Dosyayı değiştirin, ardından tutma yerinde requestPermission({mode: 'readwrite'}) işlevini çağırın.
  4. Kullanıcı izin isteğini kabul ederse değişiklikleri orijinal dosyaya geri kaydedin.
  5. Alternatif olarak, showSaveFilePicker() numaralı telefonu arayarak kullanıcının yeni bir dosya seçmesini sağlayabilirsiniz. (Kullanıcı daha önce açılmış bir dosyayı seçerse içeriğinin üzerine yazılır.) Tekrar eden kaydetme işlemleri için dosya tutamacını saklayabilirsiniz. Böylece, dosya kaydetme iletişim kutusunu tekrar göstermeniz gerekmez.

Web'deki dosyalarla çalışma kısıtlamaları

Bu yöntemlerle erişilebilen dosyalar ve klasörler, kullanıcı tarafından görülebilen dosya sisteminde bulunur. Web'den kaydedilen dosyalar ve özellikle yürütülebilir dosyalar web işareti ile işaretlenir. Böylece, işletim sistemi potansiyel olarak tehlikeli bir dosya yürütülmeden önce ek bir uyarı gösterebilir. Ek bir güvenlik özelliği olarak, web'den alınan dosyalar da Güvenli Tarama ile korunur. Bu özelliği, basitlik ve bu makalenin bağlamı açısından 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 yapılmaz, geçici bir dosya kullanılır. Dosya, bu güvenlik kontrollerinin tümünden geçmediği sürece değiştirilmez. Tahmin edebileceğiniz gibi, bu çalışma, mümkün olan yerlerde (ör. macOS'te) uygulanan iyileştirmelere rağmen dosya işlemlerini nispeten yavaşlatır. Bununla birlikte, her write() çağrısı bağımsızdır. Bu nedenle, dosya açılır, verilen ofset aranır ve son olarak veriler yazılır.

İşlemenin temeli olarak dosyalar

Aynı zamanda dosyalar, verileri kaydetmek için mükemmel bir yöntemdir. Örneğin, SQLite, tüm veritabanlarını tek bir dosyada depolar. Görüntü işlemede kullanılan mipmap'ler de başka bir örnektir. Mipmap'ler, önceden hesaplanmış, optimize edilmiş resim dizileridir. Her biri, bir öncekinin giderek daha düşük çözünürlüklü bir gösterimidir. Bu sayede yakınlaştırma gibi birçok işlem daha hızlı gerçekleştirilir. Peki web uygulamaları, web tabanlı dosya işlemenin performans maliyetleri olmadan dosyalardan nasıl yararlanabilir? Yanıt, origin private file system'dir.

Kullanıcı tarafından görülebilen dosya sistemi ile kaynak özel dosya sistemi arasındaki fark

Kullanıcıların işletim sisteminin dosya gezginiyle göz atarak okuyabildiği, yazabildiği, taşıyabildiği ve yeniden adlandırabildiği dosyalar ve klasörlerin bulunduğu kullanıcı tarafından görülebilen dosya sisteminin aksine, kaynak özel dosya sistemi kullanıcılar tarafından görülmek üzere tasarlanmamıştır. Adından da anlaşılacağı gibi, kaynak özel dosya sistemindeki dosyalar ve klasörler özeldir. Daha somut bir ifadeyle, bir sitenin kaynağına özeldir. Geliştirici Araçları Konsolu'na 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). Köken 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 özel dosya sistemi vardır. Bu nedenle, https://developer.chrome.com kaynağının özel dosya sistemi, örneğin https://web.dev kaynağının özel dosya sisteminden tamamen farklıdır. Windows'da, kullanıcı tarafından görülebilen dosya sisteminin kök dizini C:\\'dır. Kaynak özel dosya sistemindeki eşdeğeri, navigator.storage.getDirectory() asenkron yöntemi çağrılarak erişilen kaynak başına başlangıçta boş olan bir kök dizindir. Kullanıcı tarafından görülebilen dosya sistemi ile kaynak özel dosya sisteminin karşılaştırması için aşağıdaki şemaya bakın. Şemada, kök dizin dışında her şeyin kavramsal olarak aynı olduğu, veri ve depolama ihtiyaçlarınıza göre düzenlemek ve ayarlamak için dosya ve klasör hiyerarşisinin bulunduğu gösterilmektedir.

Kullanıcı tarafından görülebilen dosya sistemi ile iki örnek dosya hiyerarşisi içeren kaynak özel dosya sisteminin diyagramı. Kullanıcı tarafından görülebilen dosya sistemi için giriş noktası sembolik bir sabit disk, kaynak özel dosya sistemi için giriş noktası ise &quot;navigator.storage.getDirectory&quot; yönteminin çağrılmasıdır.

Origin Private File System'in özellikleri

Tarayıcıdaki diğer depolama mekanizmaları (örneğin, localStorage veya IndexedDB) gibi, kaynak özel dosya sistemi de tarayıcı kota kısıtlamalarına tabidir. Bir kullanıcı tüm tarama verilerini veya tüm site verilerini temizlediğinde, kaynak özel dosya sistemi de silinir. navigator.storage.estimate() işlevini çağırın ve sonuçta elde edilen yanıt nesnesinde, uygulamanızın ne kadar depolama alanı kullandığını görmek için usage girişine bakın. Bu giriş, usageDetails nesnesinde depolama mekanizmasına göre ayrılmıştır. Özellikle fileSystem girişine bakmak isteyeceksiniz. Kaynak özel dosya sistemi kullanıcıya görünmediğinden izin istemleri ve Güvenli Tarama kontrolleri yoktur.

Kök dizine erişme

Kök dizine erişmek için aşağıdaki komutu çalıştırın. Sonuç olarak boş bir dizin tanıtıcısı, daha doğrusu 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 Worker

Origin private file system'i kullanmanın iki yolu vardır: Ana iş parçacığında veya Web Worker'da. Web Workers, ana iş parçacığını engelleyemez. Bu nedenle, bu bağlamda API'ler senkron olabilir. Bu, genellikle ana iş parçacığında izin verilmeyen bir kalıptır. Senkron API'ler, sözlerle uğraşmak zorunda kalmadıkları için daha hızlı olabilir. Dosya işlemleri genellikle WebAssembly'ye derlenebilen C gibi dillerde senkrondur.

// 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 çalışıyorsanız Web Worker'da kaynak özel dosya sistemini kullanma bölümüne geçin. Aksi takdirde okumaya devam edebilirsiniz.

Ana iş parçacığında kaynak özel dosya sistemini kullanma

Yeni dosya ve klasör oluşturma

Bir kök klasörünüz olduğunda, sırasıyla getFileHandle() ve getDirectoryHandle() yöntemlerini kullanarak dosya ve klasör oluşturun. {create: true} iletilirse dosya veya klasör mevcut değilse oluşturulur. Yeni oluşturulan bir dizini başlangıç noktası olarak 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

Adını biliyorsanız dosya veya klasörün adını ileterek getFileHandle() veya getDirectoryHandle() yöntemlerini çağırarak daha önce oluşturulmuş dosya ve klasörlere erişin.

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

Okumak için dosya tutamağıyla ilişkili 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'nin kullanılabildiği her bağlamda kullanılabilir. Özellikle FileReader, URL.createObjectURL(), createImageBitmap() ve XMLHttpRequest.send() hem Blobs hem de Files değerlerini kabul eder. Bu durumda, FileSystemFileHandle kaynağından File almak verileri "serbest bırakır". Böylece verilere erişebilir ve bunları kullanıcı tarafından görülebilen dosya sisteminde kullanılabilir hale getirebilirsiniz.

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

Akışla aktararak dosyaya yazma

createWritable() işlevini çağırarak verileri bir dosyaya aktarın. Bu işlev, FileSystemWritableFileStream oluşturur. Ardından, içeriği write(). Son olarak, akışı close() etmeniz 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örleri silme

Dosya veya dizin tutamaçlarının belirli remove() yöntemini çağırarak dosya ve klasörleri silin. Tüm alt klasörleri içeren bir klasörü silmek için {recursive: true} seçeneğini iletin.

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

Alternatif olarak, bir dizinde 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ırın ve taşıyın. 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 yolunu çözme

Belirli bir dosyanın veya klasörün referans dizine göre nerede bulunduğunu öğrenmek için resolve() yöntemini kullanın ve bağımsız değişken olarak FileSystemHandle iletin. Kaynak özel dosya sistemindeki bir dosya veya klasörün tam yolunu almak için navigator.storage.getDirectory() üzerinden 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 tanıtıcısının aynı dosyayı ya da klasörü işaret edip etmediğini kontrol etme

Bazen iki tutamağınız olur ve bunların aynı dosyayı mı yoksa klasörü mü işaret ettiğini bilemezsiniz. Böyle bir durumun söz konusu 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 üzerinde yineleme yaptığınız bir eşzamansız yineleyicidir. Asenkron bir yineleyici 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

Eşzamansız döngüler ve özyinelemeyle eşleştirilmiş işlevlerle uğraşmak kolayca yanlış yapılabilir. Aşağıdaki işlev, bir klasörün ve tüm alt klasörlerinin içeriklerini (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 yazan yerde handle.getFile() sözünü değil, doğrudan handle sözünü iletin.

  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 Worker'da kaynak özel dosya sistemini kullanma

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

Senkron erişim işleyicisi alma

Mümkün olan en hızlı dosya işlemlerine giriş noktası, createSyncAccessHandle() çağrılarak normal bir FileSystemFileHandle'tan elde edilen FileSystemSyncAccessHandle'dir.

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

Eşzamanlı yerinde dosya yöntemleri

Eşzamanlı erişim işleyiciniz olduğunda, tümü eşzamanlı olan hızlı yerinde dosya yöntemlerine erişebilirsiniz.

  • getSize(): Dosyanın bayt cinsinden boyutunu döndürür.
  • write(): Bir arabelleğin içeriğini dosyaya yazar (isteğe bağlı olarak belirli bir ofsette) ve yazılan bayt sayısını döndürür. Döndürülen yazılmış bayt sayısını kontrol etmek, arayanların hataları ve kısmi yazma işlemlerini tespit edip ele almasına olanak tanır.
  • read(): Dosyanın içeriğini isteğe bağlı olarak belirli bir ofsetle arabelleğe okur.
  • truncate(): Dosyayı belirtilen boyuta yeniden boyutlandırır.
  • flush(): Dosyanın içeriğinin, write() üzerinden yapılan tüm değişiklikleri içerdiğinden emin olunur.
  • close(): Erişim tutamacını kapatır.

Aşağıda, yukarıda bahsedilen tüm yöntemlerin kullanıldığı bir örnek 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);

Kaynak özel dosya sisteminden kullanıcı tarafından görülebilen dosya sistemine dosya kopyalama

Yukarıda belirtildiği gibi, dosyaları kaynak özel dosya sisteminden kullanıcı tarafından görülebilen dosya sistemine taşımak mümkün değildir ancak dosyaları kopyalayabilirsiniz. showSaveFilePicker() yalnızca ana iş parçacığında kullanıma sunulduğu için kodu orada ç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);
}

Origin Private File System'de hata ayıklama

Yerleşik Geliştirici Araçları desteği eklenene kadar (crbug/1284595'e bakın) kaynak özel dosya sisteminde hata ayıklamak için OPFS Explorer Chrome uzantısını kullanın. Bu arada, yukarıdaki ekran görüntüsü Yeni dosya ve klasör oluşturma bölümünden 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ı yükledikten sonra Chrome Geliştirici Araçları'nı açın, OPFS Explorer sekmesini seçin. Artık dosya hiyerarşisini inceleyebilirsiniz. Dosya adını tıklayarak dosyaları kaynak özel dosya sisteminden kullanıcı tarafından görülebilen dosya sistemine kaydedin ve çöp kutusu simgesini tıklayarak dosyaları ve klasörleri silin.

Demo

WebAssembly'ye derlenmiş bir SQLite veritabanı için arka uç olarak kullanılan bir demoda (OPFS Explorer uzantısını yüklerseniz) origin private file system'in nasıl çalıştığını görebilirsiniz. Glitch'teki kaynak koduna mutlaka göz atın. Aşağıdaki yerleştirilmiş sürümün, kaynaklar arası bir iframe olduğu için kaynak özel dosya sistemi arka ucunu kullanmadığını ancak demoyu ayrı bir sekmede açtığınızda kullandığını unutmayın.

Sonuçlar

WHATWG tarafından belirtilen kaynak özel dosya sistemi, web'deki dosyaları kullanma ve bunlarla etkileşim kurma şeklimizi belirlemiştir. Kullanıcı tarafından görülebilen dosya sistemiyle mümkün olmayan yeni kullanım alanları etkinleştirildi. Apple, Mozilla ve Google gibi tüm büyük tarayıcı sağlayıcılar bu projeye dahil olup ortak bir vizyonu paylaşıyor. Kaynak özel dosya sisteminin geliştirilmesi büyük ölçüde ortak bir çalışmadır ve geliştiriciler ile kullanıcıların geri bildirimleri, bu sistemin ilerlemesi için çok önemlidir. Standartta iyileştirme çalışmalarımıza devam ederken whatwg/fs deposu ile ilgili geri bildirimlerinizi Issues (Sorunlar) veya Pull Requests (Çekme İstekleri) şeklinde gönderebilirsiniz.

Teşekkür

Bu makale Austin Sully, Etienne Noël ve Rachel Andrew tarafından incelenmiştir. Christina Rumpf'un Unsplash'teki hero resmi.