Dosya Sistemi Standardı, sayfanın kaynağına özel ve kullanıcıya görünür olmayan bir depolama uç noktası olarak bir kaynak özel dosya sistemi (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
Kaynak özel dosya sistemi, 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.
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'de dosyalarla çalışmanın tipik yolu
Bir web uygulamasında Yapılacaklar listesini düzenlemek için genellikle şu akış kullanılır:
- Kullanıcı, dosyayı bir sunucuya yükler veya
<input type="file">ile istemcide açar. - Kullanıcı değişikliklerini yapar ve ardından JavaScript ile programatik olarak
click()<a download="ToDo.txt>eklenmiş sonuç dosyasını indirir. - 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 konusunda nasıl düşündüğünü yansıtmaz ve kullanıcıların giriş dosyalarının kopyalarını indirmesine neden olur. Bu nedenle, File System Access API üç seçici yöntemi kullanıma sundu:
Akışı şu şekilde etkinleştirirler:
ToDo.txtuygulamasınıshowOpenFilePicker()ile açın veFileSystemFileHandlenesnesi kazanın.FileSystemFileHandlenesnesinden, dosya işleyiciningetFile()yöntemini çağırarakFilealın.- Dosyayı değiştirin, ardından
requestPermission({mode: 'readwrite'})işleyiciyi çağırın. - Kullanıcı izin isteğini kabul ederse değişiklikleri orijinal dosyaya geri kaydedin.
- 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 tutma yerini koruyabilirsiniz. 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. Güvenli Tarama'yı, bu doküman bağlamında ve basitlik 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.
Bu çalışma, mümkün olan yerlerde (ör. macOS'te) uygulanan iyileştirmelere rağmen dosya işlemlerini nispeten yavaşlatır.
Yine de her write()
çağrısı bağımsızdır. Bu nedenle, arka planda dosyayı açar, verilen ofseti arar ve son olarak verileri yazar.
İş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. Bir başka örnek de görüntü işlemede kullanılan mipmap'lerdir. 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ı yapılır. Peki web uygulamaları, web tabanlı dosya işlemenin performans maliyetleri olmadan dosyaların avantajlarından nasıl yararlanabilir? Yanıt, origin private dosya sistemi'dir.
Kullanıcı tarafından görülebilen dosya sistemi ve kaynak özel dosya sistemi
Kullanıcı tarafından görülebilen ve işletim sisteminin dosya gezgini kullanılarak göz atılan, okuyabileceğiniz, yazabileceğiniz, taşıyabileceğiniz ve yeniden adlandırabileceğiniz dosyalar ve klasörlerden farklı olarak, 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 net 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.
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 örnekteki 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 sistemi için eşdeğer olan, navigator.storage.getDirectory() asenkron yöntemi çağrılarak erişilen kaynak başına başlangıçta boş 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üzenlenecek ve ayarlanacak dosya ve klasör hiyerarşisinin bulunduğu gösterilmektedir.
navigator.storage.getDirectory yönteminin çağrılmasıdır.Origin Private File System'in özellikleri
Tarayıcıdaki diğer depolama mekanizmalarında (örneğin, localStorage veya IndexedDB) olduğu 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 sildiğinde, kaynak özel dosya sistemi de silinir.
navigator.storage.estimate() işlevini çağırın
ve sonuçta elde edilen yanıt nesnesinde
usage
girişini inceleyerek uygulamanızın ne kadar depolama alanı kullandığını görün. Bu bilgi, usageDetails
nesnesindeki depolama mekanizmasına göre ayrılmıştır. Özellikle fileSystem girişine bakmanız gerekir. 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 tutacınız olur. Daha spesifik olarak, FileSystemDirectoryHandle olur.
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 eşzamanlı 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. Ayrıca, WebAssembly'ye derlenebilen C gibi dillerde dosya işlemleri genellikle 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.
Ana iş parçacığında kaynak özel dosya sistemini kullanma
Yeni dosya ve klasör oluşturma
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} iletilerek dosya veya klasör mevcut değilse oluşturulur. Yeni oluşturulan bir dizini başlangıç noktası olarak kullanarak bu işlevleri çağırarak 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});

Mevcut dosya ve klasörlere erişme
Adını biliyorsanız getFileHandle() veya getDirectoryHandle() yöntemlerini çağırıp dosya ya da klasörün adını ileterek 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. Bir File nesnesi, belirli bir Blob türüdür ve Blob'nin kullanılabildiği tüm bağlamlarda kullanılabilir.
Özellikle FileReader,
URL.createObjectURL(),
createImageBitmap(),
ve XMLHttpRequest.send()
hem Blobs hem de Files değerlerini kabul eder. File dosyasını FileSystemFileHandle kaynağından 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, içeriğini write() yapacağınız bir FileSystemWritableFileStream oluşturur. Son olarak, akışı 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örleri silme
Dosya veya dizin tutmaçlarının belirli remove() yöntemini çağırarak dosyaları 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 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ı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 dosya 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 değerini iletin. Kaynak özel dosya sistemindeki bir dosyanın veya klasörün tam yolunu almak için navigator.storage.getDirectory() kullanılarak elde edilen 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 yinelediğiniz bir eşzamansız yineleyicidir. Asenkron bir yineleyici olarak, ihtiyacınız olan bilgilere bağlı olarak aralarından seçim yapabileceğ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ç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 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'dan elde edilen bir FileSystemSyncAccessHandle'dir.
const fileHandle = await opfsRoot
.getFileHandle('my highspeed file.txt', {create: true});
const syncAccessHandle = await fileHandle.createSyncAccessHandle();
Eşzamanlı yerinde dosya yöntemleri
Senkron erişim işleyiciniz olduğunda, tümü senkron 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 isteğe bağlı olarak belirli bir ofsetle 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 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.
Her yöntemin kullanıldığı bir örneği aşağıda bulabilirsiniz.
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 sistemindeki bir dosyayı kullanıcı tarafından görülebilen dosya sistemine 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ğundan (Worker iş parçacığında değil) 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, Yeni dosya ve klasör oluşturma bölümündeki ekran görüntüsü doğrudan uzantıdan alınmıştır.

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. Çöp kutusu simgesini tıklayarak dosyaları ve klasörleri silin.
Demo
OPFS Explorer uzantısını yüklerseniz WebAssembly'ye derlenmiş bir SQLite veritabanı için arka uç olarak kullanılan bir demoda, Origin Private File System'in nasıl çalıştığını görebilirsiniz. GitHub'daki kaynak kodunu incelemeyi unutmayın. Yerleştirilmiş sürümün, kaynaklar arası bir iframe olduğundan kaynak özel dosya sistemi arka ucunu kullanmadığına, ancak demoyu ayrı bir sekmede açtığınızda kullandığına dikkat edin.
Sonuç
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 konuda hemfikir ve ortak bir vizyon paylaşıyor. Origin Private File System'in geliştirilmesi büyük ölçüde ortak bir çalışmadır ve geliştiricilerden ve kullanıcılardan gelen geri bildirimler bu çalışmanın ilerlemesi için çok önemlidir.
Standartta iyileştirme ve geliştirme çalışmalarımıza devam ederken whatwg/fs deposu ile ilgili geri bildirimlerinizi Issue (Sorun) veya Pull Request (Çekme İsteği) şeklinde gönderebilirsiniz.
İlgili bağlantılar
- Dosya Sistemi Standardı spesifikasyonu
- File System Standard repo
- Origin Private File System WebKit yayınıyla birlikte File System API
- OPFS Explorer uzantısı
Teşekkür
Bu belge Austin Sully, Etienne Noël ve Rachel Andrew tarafından incelenmiştir.