Dosya Sistemi Standardı, sayfanın kaynağına özel olan ve performans için son derece optimize edilmiş özel bir dosya türüne isteğe bağlı erişim sağlayan kullanıcı tarafından görülmeyen depolama uç noktası olarak bir kaynak gizli dosya sistemi (OPFS) sunar.
Tarayıcı desteği
Kaynak gizli dosya sistemi, modern tarayıcılar tarafından desteklenir ve File System Living Standard'da (Dosya Sistemi Yaşam Standardı) Web Hypertext Application Technology Working Group (WHEREWG) tarafından standart hâle getirilmiştir.
Motivasyon
Bilgisayarınızdaki dosyaları düşündüğünüzde aklınıza bir dosya hiyerarşisi gelir. Bu hiyerarşi, işletim sisteminizin dosya gezginini kullanarak keşfedebileceğiniz klasörler halinde düzenlenmiş dosyalardır. Örneğin, Windows'da Tarık adlı bir kullanıcının Yapılacaklar listesi C:\Users\Tom\Documents\ToDo.txt
konumunda yayında 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
Bir web uygulamasında Yapılacaklar listesini düzenlemek için her zamanki akış şu şekildedir:
- Kullanıcı dosyayı bir sunucuya yükler veya
<input type="file">
ile istemcide açır. - Kullanıcı gerekli değişiklikleri yapar, ardından JavaScript aracılığıyla programatik olarak
click()
yüklediğiniz bir dosya yerleştirilmiş<a download="ToDo.txt>
ile oluşturulan dosyayı indirir. - 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'deki dosyalarla çalışmanın modern yolu
Bu akış, kullanıcıların dosyaları düzenlemeyi nasıl düşündüğünü yansıtmaz ve kullanıcıların giriş dosyalarının kopyalarını indirdikleri anlamına gelir. Bu nedenle File System Access API, tam olarak adından da anlaşılacağı üzere üç seçici yöntemi (showOpenFilePicker()
, showSaveFilePicker()
ve showDirectoryPicker()
) kullanıma sundu. Aşağıdaki şekilde bir akışı etkinleştirirler:
ToDo.txt
uygulamasınıshowOpenFilePicker()
ile açıp birFileSystemFileHandle
nesnesi alın.- Dosya herkese açık kullanıcı adının
getFile()
yöntemini çağırarakFileSystemFileHandle
nesnesinden birFile
alın. - Dosyayı değiştirin ve herkese açık kullanıcı adında
requestPermission({mode: 'readwrite'})
çağrısı yapın. - Kullanıcı izin isteğini kabul ederse değişiklikleri orijinal dosyaya geri kaydedin.
- Alternatif olarak,
showSaveFilePicker()
dosyasını çağırın ve kullanıcının yeni bir dosya seçmesine izin verin. (Kullanıcı daha önce açılmış bir dosyayı seçerse dosya içeriğinin üzerine yazılır.) Tekrarlanan kayıtlarda, dosya tutma yerini sabit tutabilirsiniz. Böylece, dosya kaydetme iletişim kutusunu tekrar görüntülemek zorunda kalmazsınız.
Web üzerindeki dosyalarla çalışmaya ilişkin kısıtlamalar
Bu yöntemlerle erişilebilen dosya ve klasörler, kullanıcı tarafından görülebilir 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 edinilen dosyalar da Güvenli Tarama ile korunur. Bu Güvenli Tarama, kolaylık sağlamak amacıyla ve bu makale bağlamında bulut tabanlı bir virüs taraması gibi düşünebilirsiniz. File System Access API'yi kullanarak bir dosyaya veri yazarken yazma işlemleri yerinde değildir ancak geçici bir dosya kullanır. Dosyanın kendisi tüm bu güvenlik kontrollerini geçmediği sürece değiştirilmez. Tahmin edebileceğiniz gibi bu çalışma, örneğin macOS'te gibi, mümkün olduğunda iyileştirmelerin uygulanmasına rağmen dosya işlemlerini nispeten yavaşlatıyor. 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, veri kaydetmek için mükemmel bir yoldur. Örneğin, SQLite tüm veritabanlarını tek bir dosyada depolar. Diğer bir örnek olarak resim işlemede kullanılan mipmaps verilebilir. Mipmaps'ler, ö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şlemenin performans maliyetleri olmadan dosyaların avantajlarından nasıl yararlanabilir? Cevap, kaynak gizli dosya sistemidir.
Kullanıcı tarafından görülebilen kaynak gizli dosya sistemi ile kaynaktaki gizli dosya sistemi
İşletim sisteminin dosya gezginini kullanarak göz atılan, kullanıcı tarafından görülebilen dosya sisteminin aksine; okuyabileceğiniz, yazabileceğiniz, taşıyabileceğiniz ve yeniden adlandırabileceğiniz dosya ve klasörlere sahip olan kaynak gizli dosya sistemi kullanıcılar tarafından görülmez. Kaynak gizli dosya sistemindeki dosya ve klasörler, adından da anlaşılacağı üzere gizlidir ve daha somut bir şekilde, bir sitenin kaynağına ö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
şeklindedir (yani /articles
bölümü, kaynağın parçası değildir). "Aynı site"yi anlama bölümünde ise kaynak teorisi hakkında daha fazla bilgi edinebilirsiniz. ve "same-origin" yer alır. Aynı kaynağı paylaşan tüm sayfalar, aynı kaynak gizli dosya sistemi verilerini görebilir. Bu nedenle https://developer.chrome.com/docs/extensions/mv3/getstarted/extensions-101/
, önceki örnekle 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
gibi kaynaklardan tamamen farklıdır. Windows'da, kullanıcının görebildiği dosya sisteminin kök dizini C:\\
şeklindedir.
Kaynak gizli dosya sisteminin eşdeğeri, eşzamansız yöntemin çağrılmasıyla erişilen kaynak başına başlangıçta boş bir kök dizindir
navigator.storage.getDirectory()
.
Kullanıcı tarafından görülebilen dosya sistemi ile kaynak gizli dosya sisteminin karşılaştırmasını görmek için aşağıdaki şemaya bakın. Şemada, kök dizinden ayrı olarak diğer her şeyin kavramsal olarak aynı olduğu gösterilmektedir. Buna ek olarak, verileriniz ve depolama ihtiyaçlarınız için gerektiğinde organize edilecek ve düzenlenecek bir dosya ve klasör hiyerarşisi vardır.
Kaynak gizli dosya sistemine ilişkin bilgiler
Tarayıcıdaki diğer depolama mekanizmalarında (örneğin, localStorage veya IndexedDB) olduğu gibi, kaynak gizli dosya sistemi de 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. Kaynak gizli dosya sistemi kullanıcı tarafından görülemediğinden izin istemi ve Güvenli Tarama kontrolü yoktur.
Kök dizine erişim elde etme
Kök dizine erişim elde etmek için aşağıdaki komutu çalıştırın. Sonuçta boş bir dizin tutma yeri (daha açık bir ifadeyle, bir 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 çalışanı'nda. Web İşçileri 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. 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 çalışıyorsanız Bir web çalışanında kaynak gizli dosya sistemini kullanma bölümüne geçin. Aksi takdirde okumaya devam edebilirsiniz.
Ana iş parçacığında kaynak gizli dosya sistemini kullanın
Yeni dosya ve klasörler oluşturma
Kök klasörü oluşturduktan sonra getFileHandle()
ve getDirectoryHandle()
yöntemlerini kullanarak dosya ve klasör oluşturun. {create: true}
iletilirken mevcut değilse dosya veya klasör oluşturulur. Başlangıç noktası olarak yeni oluşturulan bir dizini kullanıp 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});
Mevcut dosyalara ve klasörlere erişme
Kullanıcının adını biliyorsanız getFileHandle()
veya getDirectoryHandle()
yöntemlerini çağırıp 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
yöntemini 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
kabul ediliyor. Yapacaksanız, FileSystemFileHandle
"ücretsiz"den bir File
alma Böylece verilere erişebilir ve kullanıcı tarafından görülebilen dosya sisteminin kullanımına sunabilirsiniz.
const file = await fileHandle.getFile();
console.log(await file.text());
Dosyaya akışla yazma
createWritable()
komutunu çağırarak bir dosyaya veri akışı sağlayın. Bu durumda FileSystemWritableFileStream
oluşturulur, ardından içerik write()
oluşturulur. Sonunda 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ör silme
Dosya veya dizin tanıtıcılarına özel 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');
.
Dosya 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 dosyanın veya klasörün referans dizinle ilişkili olarak nerede bulunduğunu öğrenmek için resolve()
yöntemini kullanın ve bağımsız değişken olarak bir FileSystemHandle
ileterek. 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 tutma yerinin aynı dosyaya veya klasöre işaret edip etmediğini kontrol edin
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. Böyle bir durumun 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
Eşzamansız döngüler ve özyinelemeyle eşlenmiş işlevlerle çalışırken hata yapmak kolaydır. Aşağıdaki işlev, tüm dosyalar ve boyutları dahil olmak üzere bir klasörün içeriğini ve tüm alt klasörlerini listelemek için başlangıç noktası olarak kullanılabilir. Dosya boyutlarına ihtiyacınız yoksa, handle.getFile()
vaadini değil, doğrudan handle
öğesini aktararak directoryEntryPromises.push
ifadesini kullanarak işlevi basitleştirebilirsiniz.
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;
};
Bir web çalışanında kaynak gizli dosya sistemini kullan
Daha önce belirtildiği gibi, Web İşçileri ana iş parçacığını engelleyemez. Bu nedenle, bu bağlamda eşzamanlı yöntemlere izin verilir.
Eşzamanlı erişim herkese açık kullanıcı adı 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();
.
Eşzamanlı yerinde 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()
: Arabelleğin içeriğini dosyaya isteğe bağlı olarak belirli bir uzaklıkta yazar ve yazılan bayt sayısını döndürür. Döndürülen yazılı bayt sayısını kontrol etmek, çağrıda bulunanların hataları ve kısmi yazma işlemlerini tespit edip işlemesine olanak tanır.read()
: Dosyanın içeriğini isteğe bağlı olarak belirli bir ofsette bir arabelleğe okur.truncate()
: Dosyayı belirtilen boyuta göre yeniden boyutlandırır.flush()
: Dosya içeriğininwrite()
üzerinden yapılan tüm değişiklikleri içermesini sağlar.close()
: Erişim herkese açık kullanıcı adını kapatır.
Yukarıda belirtilen tüm yöntemlerin 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 gizli dosya sistemindeki bir dosyayı kullanıcıların görebildiği 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ği, Çalışan iş parçacığında gösterilmediği 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);
}
Kaynak gizli dosya sisteminde hata ayıklama
Yerleşik Geliştirici Araçları desteği eklenene kadar (bkz. crbug/1284595), kaynak özel dosya sisteminde hata ayıklamak için OPFS Explorer Chrome uzantısını kullanın. Yukarıdaki Yeni dosya ve klasör oluşturma bölümünde yer alan 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 ve OPFS Gezgini sekmesini seçin. Artık dosya hiyerarşisini incelemeye hazır olursunuz. Kaynak gizli dosya sistemindeki dosyaları, dosya adını tıklayarak kullanıcının görebildiği dosya sistemine kaydedin ve çöp kutusu simgesini tıklayarak dosya ve klasörleri silin.
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 kodu kontrol etmeyi unutmayın. Aşağıdaki yerleştirilmiş sürümün, kaynak gizli dosya sistemi arka ucunu kullanmadığını (iframe'in kaynaklar arası olması nedeniyle) ancak demoyu ayrı bir sekmede açtığınızda kullandığına dikkat edin.
Sonuçlar
WHEREWG tarafından belirtilen kaynak gizli dosya sistemi, web'deki dosyaları kullanma ve bunlarla etkileşim kurma şeklimizi şekillendirmiştir. Kullanıcıların görebildiği dosya sistemiyle ulaşılması mümkün olmayan yeni kullanım alanları ortaya çıkardı. Tüm büyük tarayıcı tedarikçileri (Apple, Mozilla ve Google) bu sürece dahildir ve ortak bir vizyonu paylaşır. Kaynak gizli dosya sisteminin geliştirilmesi büyük ölçüde ortak bir çalışmadır ve geliştiricilerden ve kullanıcılardan gelen geri bildirimler, sürecin ilerlemesi için büyük önem taşımaktadır. Standardı hassaslaştırıp iyileştirmeye devam ederken whatwg/fs deposu ile ilgili sorunlar veya çekme istekleri biçiminde geri bildirimlerinizi bekliyoruz.
İlgili bağlantılar
- Dosya Sistemi Standart özellikleri
- Dosya Sistemi Standart deposu
- Kaynak Gizli Dosya Sistemi WebKit yayını içeren File System API
- OPFS Explorer uzantısı
Teşekkür
Bu makale Austin Sully, Etienne Noël ve Rachel Andrew tarafından incelenmiştir. Christina Rumpf'ın Unsplash'teki hero resim.