Giriş
Her zaman web uygulamarının dosya okuma, yazma yapabilmelerinin yararlı olabileceğini düşünürdüm. Offline iken online'a taşımak gibi, uygulamalar gün geçtikçe daha karmaşık oluyor ve file system Apilerin eksikliği ileri web hareketlerine engel oluyor veya ileri web uygulamalarının geliştirilmesinin önüne set çekiyorda diyebiliriz.
Depolama veya ikili veri etkileşiminin masaüstü ile sınırlı olmaması gerekiyor. Neyse ki artık FileSystem API sayesinde bu sorunun üstesinden gelebiliyoruz. FileSystem API ile bir web uygulaması kullanıcın kişisel dosya sisteminin sanal alanlı bölümünde yazabilir, okuyabilir, birşeyler yaratabilir ve gezinebilir.
API çeşitli temalara sahip:
- Dosya okuma ve işleme :
File
/Blob
,FileList
,FileReader
- Dosya oluşturma ve içerisine yazma :
Blob()
,FileWriter
- Directory (rehber) and dosya sistemine erişim :
DirectoryReader
,FileEntry
/DirectoryEntry
,LocalFileSystem
Tarayıcı desteği & depolama sınırlaması
Zamanında bu makaleyi hazırlarken, Sadece Google Chrome çalışan bir FileSystem API uygulamasına sahipti. Dosya/kota yönetimi için geliştirilmiş özel bir tarayıcı henüz yoktu. Kullanıcının sisteminde veri saklamak için kota isteği (request quota) gerekebiliyor. Her halükarda, test etmek için, Chrome -unlimited-quota-for-files bayrağıyla çalıştırılabilir. Dahası, şayet Chrome Web Deposu için bir uygulama yada eklenti yapıyorsanız, Sınırsız depolama bildirim dosyası ( the
unlimitedStorage
manifest file) izni kota isteğinin yerine kullanılabilir. Er yada geç, kullanıcılar bir web uygulaması için reddetme, kabul etme veya depolama artışı için bir izin penceresi ile karşılacaklar.Şayet uygulamınızı file://'dan debug ediyorsanız dosyalardan dosyalara erişime izin (-allow-file-access-from-files ) vermeniz gerekiyor. Bu bayrakaları (flag) kullanmamak
SECURITY_ERR yada QUOTA_EXCEEDED_ERR FileError
hatalarına neden olacaktır.Dosya sistem isteği
Bir web uygulaması sanal alanlı bir dosya sistemine (sandboxed file system) şu şekilde bir erişim isteğinde bulunabilir :
window.requestFileSystem()
:// Dikkat: Dosya sistemi Google Chrome 12'nin ön eki olmuştur:
window.requestFileSystem = window.requestFileSystem || window.webkitRequestFileSystem;
window.requestFileSystem(type, size, successCallback, opt_errorCallback)
- type
- Dosya depolamanın devam edip etmeyeceğidir. Alabileceği değerler :
window.TEMPORARY
yadawindow.PERSISTENT TEMPORARY kullanılarak depolanan veri tarayıcının takdiriyle silinebilir (örneğin daha fazla alana ihtiyaç olduğunda). PERSISTENT depolama kullanıcı tarafından veya uygulama tarafından açıkça izin verilmedikçe temizlenemez ve kullanıcının uygulamanıza kota vermesi gerekir.
Detay için kota isteği bölümüne bakınız. - size
- Depolama için gereken boyuttur (byte).
- successCallback
- Dosya sistemin isteği başarıyla gerçekleştirildiğinde çağırılır. FileSystem nesnese ait bağımsız bir değişkendir.
- opt_errorCallback
- Hataların üstesinden gelmek için bulunulan yada dosya sistemini edinme istediğinde bulunulduğunda isteğe bağlı geri bildirimdir. FileError nesnesine ait bağımsız bir değişkendir.
requestFileSystem() ilk kez çağırılıyorsa, uygulama için yeni depolama hemen oluşturulacaktır. Dosya sisteminin sanal alanlı (sandboxed) olduğu unutulmamalı, bu bir web uygulamasının diğer uygulamaların dosyalarına erişemeyeceği anlamına gelir. Ayrıca kullanıcın hard diskinde rastgele bir dosyaya yazamayacağınız veya dosya okuyamayacağınız anlamına gelir (Örn: Resimlerim veya Belgelerim klasörleri gibi).
Örnek kullanım :
function onInitFs(fs) {
console.log('Opened file system: ' + fs.name);
}window.requestFileSystem(window.TEMPORARY, 5*1024*1024 /*5MB*/, onInitFs, errorHandler);
Web geliştiricilerinin kullanması için FileSystem senkron bir api tanımlar, fakat konumuz senkron api olmadığı için detaylara girmiyorum.
Makalenin geri kalanı boyunca, asenkron çağrı hatalarını görmek veya işlemek için aşağıdaki hata tutucu fonksiyonu kullanacağız :
function errorHandler(e) {
var msg = '';
switch (e.code) {
case FileError.QUOTA_EXCEEDED_ERR:
msg = 'QUOTA_EXCEEDED_ERR';
break;
case FileError.NOT_FOUND_ERR:
msg = 'NOT_FOUND_ERR';
break;
case FileError.SECURITY_ERR:
msg = 'SECURITY_ERR';
break;
case FileError.INVALID_MODIFICATION_ERR:
msg = 'INVALID_MODIFICATION_ERR';
break;
case FileError.INVALID_STATE_ERR:
msg = 'INVALID_STATE_ERR';
break;
default:
msg = 'Unknown Error';
break;
};
console.log('Error: ' + msg);
}
Geri çağrılan hata çok genel fakat hata hakkında bir fikir sahibi olursunuz. Daha sonra kullanıcıların anlayabilmesi için daha açık mesajlar vermek isteyeceksiniz.
Depolama kotası istemek (Requesting storage quota)
PERSISTENT depolamayı kullanmak için, kullanıcıdan persistent veri depolama yapmak için izin almak zorundasınız. Aynı kısıtlama TEMPORARY depolama için geçerli değil çünkü tarayıcı geçici (Temporary) olarak depolanan veriyi tahliye seçeneğini elinde bulundurur.
PERSISTENT depolama yapabilmek için, Chrome window.webkitStorageInfo altında yeni bir APi meydana getirir :
window.webkitStorageInfo.requestQuota(PERSISTENT, 1024*1024, function(grantedBytes) {
window.requestFileSystem(PERSISTENT, grantedBytes, onInitFs, errorHandler);
}, function(e) {
console.log('Error', e);
});
İlk olarak kullanıcı izin verir, requestQuota() özelliğini çağırmaya gerek yoktur (uygulamanızın kotasını yukarı çıkarmadıkça). Eşit kotalı veya daha az sonraki çağrılar nooptur.
Bir kaynağın mevcut kota kullanımını ve dağılımını sorgulamak için kullanılan API : window.webkitStorageInfo.queryUsageAndQuota()
Dosyalarla çalışmak
Properties and methods of
FileEntry
:Sanal alanlı (sandboxed) ortamdaki dosyalar FileEntry arayüzü tarafından temsil edilmektedir. FileEntry dosya sistem standartından beklenen (name,isFile...) gibi özellik tiplerini ve (remove,moveTo,copyTo...) metodlarını içerir.
FileEntry özellikleri ve metodları :
fileEntry.isFile === true
fileEntry.isDirectory === false
fileEntry.name
fileEntry.fullPath
...
fileEntry.getMetadata(successCallback, opt_errorCallback);
fileEntry.remove(successCallback, opt_errorCallback);
fileEntry.moveTo(dirEntry, opt_newName, opt_successCallback, opt_errorCallback);
fileEntry.copyTo(dirEntry, opt_newName, opt_successCallback, opt_errorCallback);
fileEntry.getParent(successCallback, opt_errorCallback);
fileEntry.toURL(opt_mimeType);
fileEntry.file(successCallback, opt_errorCallback);
fileEntry.createWriter(successCallback, opt_errorCallback);
...
FileEnrty'i daha iyi anlamak için, makalenin geri kalanında sık kullanılan işlemleri kullanmayı göreceğiz.
Dosya oluşturmak
DirectoryEntry arayüzünün bir metodu olan, dosya sisteminin getFile() metoduyla yeni bir dosya oluşturabilirsiniz. Dosya
Dosya sisteminden istekte bulunulur. Başarılı geri dönüş sağlanmışsa uygulamının kaynağını (root) gösteren ve
DirectoryEntry
(fs.root
) nesnesini içeren FileSystem
e geçilir.Aşağıdaki kod uygulamanın ana dizininde "log.txt" adlı bir boş dosya oluşturur :
function onInitFs(fs) {
fs.root.getFile('log.txt', {create: true, exclusive: true}, function(fileEntry) {
// fileEntry.isFile === true
// fileEntry.name == 'log.txt'
// fileEntry.fullPath == '/log.txt'
}, errorHandler);
}
window.requestFileSystem(window.TEMPORARY, 1024*1024, onInitFs, errorHandler);
İlk önce dosya sistemi isteminden istekte bulunuldu. Başarılı olduktan sonra FileSystem nesnesine geçildi. Callback içerisinde, fs.root.getFile() oluşturulan dosya ismiyle çağrılabilir. Geçerli olduğu sürece bağıntılı veya tam yol (path) verilebilir. Örneğin ; üst dosya/klasörü olmayan bir dosya oluşturmaya çalışmak bir hatadır. getFile() içerisinde yer alan ikinci değişken şayet bir dosya yoksa fonksiyonun davranışını tanımlar. Örneğin : "create true" ifadesi şayet dosya yoksa yeni dosya oluşturur ve şayet varsa bir hata döndürür (
exclusive: true
). Diğer taraftan şayet "create : false" ise dosyayı getir ve döndür demektir. Her iki durumda da dosya içindekiler üstüne yazılmaz çünkü bizim yaptığımız söz konusu dosya için bir referans elde etmekten başka bir şey değildir.Bir dosyayı adıyla okumak
Aşağıdaki kod "log.txt" dosyasını elde eder.FileReader API si kullanılarak içindekiler okunur ve sayfa içinde yeni bir <textarea> içine eklenir. Şayet log.txt yoksa hata dönecektir.
function onInitFs(fs) {fs.root.getFile('log.txt', {}, function(fileEntry) {
// file objesini alıyoruz,
// içeriğini okumak için filereader kullanıyoruz.
fileEntry.file(function(file) {
var reader = new FileReader();
reader.onloadend = function(e) {
var txtArea = document.createElement('textarea');
txtArea.value = this.result;
document.body.appendChild(txtArea);
};
reader.readAsText(file);
}, errorHandler);
}, errorHandler);
}
window.requestFileSystem(window.TEMPORARY, 1024*1024, onInitFs, errorHandler);
Dosyaya yazmak
Aşağıdaki kodlar (şayet yoksa) boş bir dosya oluşturacak ve dosya içine 'Örnek dosya yazısı' yazacak.
function onInitFs(fs) {
fs.root.getFile('log.txt', {create: true}, function(fileEntry) {
// FileEnrty için FileWriter nesnesi oluşturuluyor (log.txt).
fileEntry.createWriter(function(fileWriter) {
fileWriter.onwriteend = function(e) {
console.log('Write completed.');
};
fileWriter.onerror = function(e) {
console.log('Write failed: ' + e.toString());
};
// Yeni bir Blob yaratılıyor ve log.txt içine yazılıyor
var blob = new Blob(['Örnek dosya yazısı'], {type: 'text/plain'});
fileWriter.write(blob);
}, errorHandler);
}, errorHandler);
}
window.requestFileSystem(window.TEMPORARY, 1024*1024, onInitFs, errorHandler);
Bu kez, FileEntry'nin createWriter() metodunu çağırarak FileWriterobject nesnesini elde ettik. Callback başarılı olunca içerisinde error ve writeend olaylarını (event) olay tutucular olarak set ettik. Text değer oluşturula bir blob aracılığıyla dosyaya yazıldı. Sonra blob Text ve FileWriter.write() içerisine yerleştirildi.
Dosyaya veri eklemek
Aşağıdaki kod şayet dosya yoksa bir hata döndürür varsa log dosyamızın en altına "Merhaba dünya" eklenecek.
function onInitFs(fs) {
fs.root.getFile('log.txt', {create: false}, function(fileEntry) {
// FileEntry için FileWriter nesnesi oluşturuluyor.
fileEntry.createWriter(function(fileWriter) {
fileWriter.seek(fileWriter.length); // EOF yazma pozisyonu.
// log.txt içerisine yazmak için blob oluşturuldu.
var blob = new Blob(['Merhaba Dünya'], {type: 'text/plain'});
fileWriter.write(blob);
}, errorHandler);
}, errorHandler);
}
window.requestFileSystem(window.TEMPORARY, 1024*1024, onInitFs, errorHandler);
Seçilen dosyayı kopyalamak (Duplicate)
Aşağıdaki kod <input type="file" multiple /> elemanını kullanarak kullanıcıya çoklu dosya seçme imkanı tanır ve dosya sistem uygulamasının sanal alanında (sandboxed) bu dosyaların bir kopyasını yaratır.
<input type="file" id="myfile" multiple />
document.querySelector('#myfile').onchange = function(e) {
var files = this.files;
window.requestFileSystem(window.TEMPORARY, 1024*1024, function(fs) {
// Kullanıcın seçtiği her dosyayı kopyala.
for (var i = 0, file; file = files[i]; ++i) {
// Kopyalanan dosyayı yakala
(function(f) {
fs.root.getFile(f.name, {create: true, exclusive: true}, function(fileEntry) {
fileEntry.createWriter(function(fileWriter) {
fileWriter.write(f); // Dikkat: write() File veya Blob nesnesi alabilir.
}, errorHandler);
}, errorHandler);
})(file);
}
}, errorHandler);
};
Yorum satırında görüleceği gibi, FileWriter.write() içerisine Blob veya File eklenebilir çünkü File Blob'dan kalıtılmıştır, bu nedenle tüm file nesneleri aslında bloblardır.
Dosya Silmek
Aşağıdaki kod log.txt dosyasını silecek :
window.requestFileSystem(window.TEMPORARY, 1024*1024, function(fs) {
fs.root.getFile('log.txt', {create: false}, function(fileEntry) {
fileEntry.remove(function() {
console.log('File removed.');
}, errorHandler);
}, errorHandler);
}, errorHandler);
Dizinlerle (Directories) çalışmak
Sanal alan içerisindeki dizinler FileEnrty özelliklerinin (Ortak bir Enrty biriminden kalıtılırlar) çoğunu paylaşan DirectoryEntry arayüzü tarafından temsil edilirler. Ayrıca DirectoryEntry ekstra olarak dizinleri işlemek için metodlara sahiptir.
DirectoryEntry özellikler ve metodlar :
dirEntry.isDirectory === true
// Kalıtılan diğer özellik/metodlar için FileEnrty bölümüne bakınız (Dosyalarla çalışmak bölümünde).
...
var dirReader = dirEntry.createReader();
dirEntry.getFile(path, opt_flags, opt_successCallback, opt_errorCallback);
dirEntry.getDirectory(path, opt_flags, opt_successCallback, opt_errorCallback);
dirEntry.removeRecursively(successCallback, opt_errorCallback);
...
Dizin oluşturmak
Dizin oluşturmak veya okumak için DirectoryEntry'nin getDirectory() metodu kullanılır.
Örneğin ; Aşağıdaki kod root dizininde "Resimlerim" adlı bir dizin oluşturacak ;
window.requestFileSystem(window.TEMPORARY, 1024*1024, function(fs) {
fs.root.getDirectory('MyPictures', {create: true}, function(dirEntry) {
...
}, errorHandler);
}, errorHandler);
Alt dizinler
Alt dizin yaratmanın her hangi bir dizin oluşturmaktan farkı yok. Bununla beraber üst dizin yoksa API bir hata döndürecektir. Çözümü , her bir diziyiyi sıralı veya ardışık olarak oluşturmak. Bu işlemi asenkron bir API ile yapması oldukça zordur.
Aşağıdaki kod üst klasör oluşturduktan sonra her bir alt dizini uygulamanın dizinine yeni bir hiyerarşide (muzik/tur/jazz) şeklinde ekliyor :
var path = 'muzik/tur/jazz/';
function createDir(rootDirEntry, folders) {
// '/foo/.//bar' gibi bir hatalı dizin oluşmaasını engellemek için './' ifadesini kaldır veya '/' ifadesini taşı
if (folders[0] == '.' || folders[0] == '') {
folders = folders.slice(1);
}
rootDirEntry.getDirectory(folders[0], {create: true}, function(dirEntry) {
// Yeni bir alt dizin ekleniyor (Oluşturulacak dizin varsa).
if (folders.length) {
createDir(dirEntry, folders.slice(1));
}
}, errorHandler);
};
function onInitFs(fs) {
createDir(fs.root, path.split('/')); // fs.root bir DirectoryEnrty'dir.
}
window.requestFileSystem(window.TEMPORARY, 1024*1024, onInitFs, errorHandler);
Artık "muzik/tur/jazz" adında bir klasörümüz var. Klasör yolunu (path) belirleterek getFile() metoduyla yeni alt dizin oluşturabiliriz.
Örnek :
window.requestFileSystem(window.TEMPORARY, 1024*1024, function(fs) {
fs.root.getFile('/muzik/tur/jazz/muzik.mp3', {create: true}, function(fileEntry) {
...
}, errorHandler);
}, errorHandler);
Dizin içeriğini okumak
Bir dizinin içerisini okumak için, DirectoryReader oluşturulur ve readEntries() metodu çağrılır. Tüm dizin girdilerinin basitçe bir çağırımla geriye readEntries() döndürmesinin garantisi yoktur. Bu şu anlama geliyor ; Daha fazla sonuç dönene kadar yada dönmeden önce DirectoryReader.readEntries() metodunu saklamak gerekir .Aşağıdaki kod bunu gösteren bir örnek kullanım içeriyor :
<ul id="filelist"></ul>function toArray(list) {
return Array.prototype.slice.call(list || [], 0);
}
function listResults(entries) {
// Dom'a bir kere eklendiği için döküman fragmanlarının performansı artabilir.
var fragment = document.createDocumentFragment();
entries.forEach(function(entry, i) {
var img = entry.isDirectory ? '<img src="folder-icon.gif">' :
'<img src="file-icon.gif">';
var li = document.createElement('li');
li.innerHTML = [img, '<span>', entry.name, '</span>'].join('');
fragment.appendChild(li);
});
document.querySelector('#filelist').appendChild(fragment);
}
function onInitFs(fs) {
var dirReader = fs.root.createReader();
var entries = [];
// reader.readEntries() daha fazla sonuç dönene kadar veya dönmeden önce çağrılır.
var readEntries = function() {
dirReader.readEntries (function(results) {
if (!results.length) {
listResults(entries.sort());
} else {
entries = entries.concat(toArray(results));
readEntries();
}
}, errorHandler);
};
readEntries(); // dirs. okumaya başlıyor.
}
window.requestFileSystem(window.TEMPORARY, 1024*1024, onInitFs, errorHandler);
Dizin silmek
DirectoryEntry.remove() metodu FileEntry gibi davranır. Farkı şudur : boş olmayan bir diziyi silmeye teşebbüs etmek hataya sebep olur.
Aşağıdaki kod boş olan "jazz" dizinini "/muzik/tur/" içerisinden silecek.
window.requestFileSystem(window.TEMPORARY, 1024*1024, function(fs) {
fs.root.getDirectory('music/genres/jazz', {}, function(dirEntry) {
dirEntry.remove(function() {
console.log('Dizi silindi.');
}, errorHandler);
}, errorHandler);
}, errorHandler);
Özyinelemeli (recursively) dizin silmek
Şayet dizin içerisinde sinir bozucu girdiler varsa removeRecursively() işinizi görecektir. Bu metod dizin ve içeriğini temizler.
Aşağıdaki kod muzik içerisinde özyinelemeli veya tekrarlanan dosya ve dizinleri ve içerikleri silecektir.
window.requestFileSystem(window.TEMPORARY, 1024*1024, function(fs) {
fs.root.getDirectory('/misc/../muzik', {}, function(dirEntry) {
dirEntry.removeRecursively(function() {
console.log('Directory removed.');
}, errorHandler);
}, errorHandler);
}, errorHandler);
Kopyalama, isimlendirme, ve taşıma
FileEntry
ve DirectoryEntry
ortak işlemleri paylaşır.Girdi (entry) kopyalamak
FileEntry
ve DirectoryEntry her iki birimde girdi kopyalamak için kullanılan copyTo() metoduna sahiptir. Bu metod otomatik olarak klasörlerde özyinelemeli kopyalama yapar.
Aşağıdaki kod "me.png" dosyasını bir dizinden farklı bir dizine kopyalıyor.
function copy(cwd, src, dest) {
cwd.getFile(src, {}, function(fileEntry) {
cwd.getDirectory(dest, {}, function(dirEntry) {
fileEntry.copyTo(dirEntry);
}, errorHandler);
}, errorHandler);
}
window.requestFileSystem(window.TEMPORARY, 1024*1024, function(fs) {
copy(fs.root, '/folder1/me.png', 'folder2/mypics/');
}, errorHandler);
Bir enteryi taşımak veya yeniden isimlendirmek
FileEntry ve DirectoryEntry içerisindeki moveTo() metodu bir dosya veya dizini taşıma veya yeniden adlandırma yapmak için kullanılır. İlk parametre ana dizin altıntaki taşınacak dosyadır. İkinci parametre ise isteğe bağlı yeni dosya adıdır. Şayet yeni ad verilmemişse dosyanın varolan adı varsayılan ad olarak kullanılacaktır.
Aşağıdaki kod "me.png" dosyasını "you.png" olarak isimlendiriyor fakat taşıma yapmıyor :
function rename(cwd, src, newName) {
cwd.getFile(src, {}, function(fileEntry) {
fileEntry.moveTo(cwd, newName);
}, errorHandler);
}
window.requestFileSystem(window.TEMPORARY, 1024*1024, function(fs) {
rename(fs.root, 'me.png', 'you.png');
}, errorHandler);
Aşağıdaki örnekte "me.png" (root dizininde yer alan) "newfolder" isimli klasöre taşınıyor.
function move(src, dirName) {fs.root.getFile(src, {}, function(fileEntry) {
fs.root.getDirectory(dirName, {}, function(dirEntry) {
fileEntry.moveTo(dirEntry);
}, errorHandler);
}, errorHandler);
}
window.requestFileSystem(window.TEMPORARY, 1024*1024, function(fs) {
move('/me.png', 'newfolder/');
}, errorHandler);
filesystem: URLs
FileSystem API src ve href özellilerine ile kullanılabilien bir URL şeması sunar. Örneğin : Şayet bir resim görüntülemek istiyorsanız ve fileEntry varsa toURL() metodunu çağırarak istediğiniz resmi görüntülersiniz .
Örnek :
var img = document.createElement('img');
img.src =fileEntry.toURL(); // filesystem:http://ornek.com/temporary/myfile.png
document.body.appendChild(img);
Alternatif olarak, zaten bir
filesystem:
URL mevcutsa resolveLocalFilesSystemURL() geriye fileEntry döndürecektir.window.resolveLocalFileSystemURL = window.resolveLocalFileSystemURL ||
window.webkitResolveLocalFileSystemURL;
var url = 'filesystem:http://example.com/temporary/myfile.png';window.resolveLocalFileSystemURL(url, function(fileEntry) {
...
});
Kaynak : http://www.html5rocks.com/en/tutorials/file/filesystem/
Hiç yorum yok:
Yorum Gönder