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.
Tarayıcı desteği
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.
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
Web uygulamasındaki yapılacaklar listesini düzenlemek için genellikle şu akış izlenir:
- 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 aracılığıyla programatik olarak
click()
eklediğiniz bir<a download="ToDo.txt>
ile oluşturulan dosyayı indirir. - Klasörleri açmak için
<input type="file" webkitdirectory>
özelliğinde özel bir özellik kullanırsınız. Bu özellik, tescilli adına rağmen neredeyse tüm tarayıcılarda desteklenir.
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 işlevi yapan üç seçici yöntemi (showOpenFilePicker()
, showSaveFilePicker()
ve showDirectoryPicker()
) kullanıma sundu. Akışları aşağıdaki şekilde etkinleştirirler:
ToDo.txt
'üshowOpenFilePicker()
ile açın ve birFileSystemFileHandle
nesnesi alın.FileSystemFileHandle
nesnesinden, dosya tutamacınıngetFile()
yöntemini çağırarak birFile
alın.- Dosyayı değiştirin, ardından herkese açık kullanıcı adında
requestPermission({mode: 'readwrite'})
değerini çağırın. - Kullanıcı izin isteğini kabul ederse değişiklikleri orijinal dosyaya kaydedin.
- 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'de dosyalarla çalışmayla ilgili 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 özellikle de yürütülebilir dosyalar web işaretiyle işaretlenir. Bu nedenle, işletim sistemi, tehlikeli olabilecek bir dosya çalıştırılmadan önce ek bir uyarı gösterebilir. 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ızdır. Bu nedenle, arka planda dosyayı açar, belirtilen ofsete gider ve son olarak verileri 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. Mipmap'ler, önceden hesaplanmış, optimize edilmiş resim dizileridir. Her biri öncekinin giderek daha düşük çözünürlüklü bir temsilidir. Bu sayede yakınlaştırma gibi birçok işlem daha hızlı gerçekleşir. 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ıya görünür 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. DevTools 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). 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.
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()
işlevini çağırın ve elde edilen yanıt nesnesinde usage
girişine bakın. Bu giriş, uygulamanızın şu anda ne kadar depolama alanı kullandığını gösterir. Bu değer, usageDetails
nesnesinde depolama mekanizmasına göre ayrılır. Burada özellikle fileSystem
girişine bakmak istersiniz. 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. Senkron API'ler, söz vermelerle uğraşmak zorunda kalmadıkları için daha hızlı olabilir ve dosya işlemleri genellikle C gibi WebAssembly'ye derlenebilen 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 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 özel dosya sistemini kullanma
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});
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 tutamacı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 tür Blob
'dir ve Blob
'nin kullanılabildiği her 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. Tüm alt klasörleri de dahil olmak üzere 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 dosyaları 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ü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. Orijinal özel dosya sistemindeki bir dosya veya klasörün tam yolunu almak için navigator.storage.getDirectory()
aracılığıyla elde edilen referans dizini 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 vardır ve bunların aynı dosyayı veya klasörü gösterip göstermediğ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ü ile iterasyon yaptığınız bir eşzamansız iteratördür. Asenkron bir iteratör olarak entries()
, values()
ve keys()
yöntemlerini de destekler. İhtiyacınız olan bilgilere bağlı olarak bu yöntemlerden birini seçebilirsiniz:
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 İşleyiciler ana iş parçasını engelleyemez. Bu nedenle, bu bağlamda senkronize yöntemlere izin verilir.
Senkronize erişim işleyicisi alma
Olası en hızlı dosya işlemlerine giriş noktası, createSyncAccessHandle()
çağrısı yapılarak normal bir FileSystemFileHandle
'ten elde edilen FileSystemSyncAccessHandle
'dir.
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 mülküne sahip olduğunuzda, tümü eşzamanlı olan hızlı yerinde dosya yöntemlerine erişebilirsiniz.
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ın kontrol edilmesi, 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, 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 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.
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 sisteminden kullanıcı tarafından görülebilen dosya sistemine dosya kaydedebilir ve çöp kutusu simgesini tıklayarak dosyaları ve klasörleri silebilirsiniz.
Demo
Orijinal özel dosya sisteminin (OPFS Explorer uzantısını yüklerseniz) WebAssembly'ye derlenmiş bir SQLite veritabanı için arka uç olarak kullanıldığı bir demo'da işleyiş şeklini inceleyin. 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
WHATWG tarafından belirtilen kaynak özel dosya sistemi, web'de dosyaları kullanma ve dosyalarla 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.
İlgili bağlantılar
- Dosya Sistemi Standart spesifikasyonu
- Dosya Sistemi Standart deposu
- Kaynak Özel Dosya Sistemi WebKit ile File System API
- OPFS Explorer uzantısı
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.