Граємо з API файлової системи в HTML5

19

Від автора: HTML5 дозволяє нам пожинати урожай нових можливостей, таких як малювання з допомогою canvas, реалізація мультимедіа за допомогою аудіо — і відео — API і так далі. Одним із порівняно нових інструментів є API файлової системи. Він дає нам доступ до розділу-«пісочниці» локальної файлової системи користувача, таким чином ще більше заповнюючи прогалину між робочим столом і веб-додатками! Сьогодні в підручнику ми пробіжимося по основам цього нового хвилюючого API, дослідивши звичні завдання файлових систем. Давайте приступимо!

Вступ

Для застосування більше не потрібно завантажувати і встановлювати програмне забезпечення. Всього лише веб-браузер і з’єднання з Інтернетом дає нам можливість використовувати будь-який веб-додаток в будь-який час, де завгодно і на будь-якій платформі.

Загалом, веб-додатки – це відмінно, але в порівнянні з додатками для робочого столу, у них все ще є один значний недолік: відсутність способу взаємодії і організації даних в структуровану ієрархію папок – справжню файлову систему. На щастя, за допомогою нового API файлової системи все змінюється. Він дає веб-додатками контрольований доступ до приватної локальній файловій системі «sandbox» («пісочниця»), де можна записувати і читати файли, створювати і заносити в списки каталоги і так далі. Хоча на момент написання цієї статті «повну реалізацію API файлової системи підтримує тільки браузер Google Chrome, вона все ж заслуговує на вивчення, як потужна і зручна форма місцевого зберігання файлів.

Граємо з API файлової системи в HTML5

API файлових систем буває двох різних версій. Асинхронний API, зручний для звичайних додатків, і синхронний API, призначений для застосування веб-користувачами. В рамках цього підручника ми вивчимо тільки асинхронну версію API.

Крок 1 – Починаємо

Ваш перший крок – отримати доступ до файлової системи HTML5, зробивши запит до об’єкта системи LocalFile з допомогою глобального методу window.requestFileSystem():

window.requestFileSystem(type size, successCallback, opt_errorCallback)

Веб-додатком «вирватися» за межі місцевого кореневого каталогу неможливо.

В якості перших двох параметрів ви визначаєте час існування та розмір потрібної вам файлової системи. ПЕРМАНЕНТНА файлова система підходить для веб-додатків, які зобов’язані зберігати дані користувача постійно. Браузер не буде їх видаляти, за винятком випадків певного користувальницького запиту. ТИМЧАСОВА файлова система підходить для веб-додатків, кешуючих дані, але може працювати, якщо веб-браузер видаляє файлову систему. Розмір файлової системи визначається в байтах і повинен мати розумну верхню межу кількості даних, які потребують зберіганні.

Третій параметр – це функція зворотного виклику, що запускається, коли агент користувача успішно надає файлову систему. Її аргумент – об’єкт FileSystem. І, нарешті, можна додати необов’язкову функцію зворотного виклику, яка викликається в разі помилки, або коли запит до файлової системи відхиляється. Її аргумент – об’єкт FileError. Хоча цей параметр необов’язковий, відслідковувати помилки для користувачів дуже корисно, так як помилки трапляються в багатьох місцях.

Файлова система, що діє з цими функціями, залежить від походження містить її документа. Всі документи або веб-додатки одного походження (хост, порт і протокол) поділяють файлову систему. Два документа або додатка різного походження мають абсолютно різні і несумісні файлові системи. Файлова система вузько призначена для одного додатка й не може отримати доступу до даних, збереженим в іншому додатку. На жорсткому диску користувача вона також ізольована від інших файлів, і це добре: веб-додатком «вирватися» за межі місцевого кореневого каталогу або ж отримати доступ до довільних файлів яким-небудь іншим способом неможливо.

Давайте розглянемо приклад:

window.requestFileSystem = window.requestFileSystem || window.webkitRequestFileSystem;
window.requestFileSystem(window.TEMPORARY, 5*1024*1024, initFS, errorHandler);
function initFS(fs){
alert(«Welcome to Filesystem! It’s showtime»); // Just to check if everything is OK
// тут розмістіть ті функції, про які дізнаєтеся
}
function errorHandler(){
console.log(‘An error occured’);
}

Так створюється тимчасова файлова система з 5MB пам’яті. Потім вона забезпечує успішну функцію зворотного виклику, яку ми застосуємо для управління своєю файловою системою. І, звичайно, сюди доданий обробник помилки – просто на випадок, якщо щось піде не так. Функція errorHandler() тут дуже універсальна. Так що якщо хочете, можна створити злегка оптимізовану версію, яка дає користувачеві більш повне описове повідомлення про помилку:

function errorHandler(err){
var msg = ‘An error occured: ‘;
switch (err.code) {
case FileError.NOT_FOUND_ERR:
msg += ‘File or directory not found’;
break;
case FileError.NOT_READABLE_ERR:
msg += ‘File or directory not readable’;
break;
case FileError.PATH_EXISTS_ERR:
msg += ‘File or directory already exists’;
break;
case FileError.TYPE_MISMATCH_ERR:
msg += ‘Invalid filetype’;
break;
default:
msg += ‘Unknown Error’;
break;
};
console.log(msg);
};

Одержуваний об’єкт файлової системи має назву name (унікальне ім’я файлової системи, що призначається браузером) і властивість root, яке відноситься до кореневого каталогу файлової системи. Це об’єкт DirectoryEntry, і у нього можуть бути вкладені папки, самі по собі представлені об’єктами DirectoryEntry. Кожен каталог системи може містити файли представлені об’єктами FileEntry. Об’єкт DirectoryEntry визначає способи отримання об’єктів DirectoryEntry і FileEntry по імені маршруту (вони додатково будуть створювати нові папки або файли, якщо ви призначите неіснуюче ім’я). DirectoryEntry також визначає метод createReader(), що повертає об’єкт DirectoryReader для складання списку вмісту каталогу. Клас FileEntry визначає метод отримання об’єкта File (великий двійковий об’єкт), що представляє вміст файлу. Потім для читання файлу можна застосувати об’єкт FileReader. FileEntry визначає інший метод повернення об’єкта FileWriter, який можна використовувати для запису вмісту в файл.

Уффф…складно? Не турбуйтеся. Все стане зрозумілим у міру розбору наведених нижче прикладів.

Крок 2 – Робота з каталогами

Очевидно, саме перше, що потрібно створити в файловій системі – це ділянки пам’яті, або каталоги. Хоча коренева папка вже існує, вам не потрібно розміщувати там всі файли. Каталоги створюються об’єктом DirectoryEntry. У наступному прикладі ми створюємо каталог з назвою Documents всередині кореневої папки:

fs.root.getDirectory(‘Documents’, {create: true}, function(dirEntry) {
alert(‘You have just created the’ + dirEntry.name + ‘ directory.’);
}, errorHandler);

Метод getDirectory() застосовується як для читання, так і створення каталогу. В якості першого параметра можна передати ім’я або маршрут, як директорію для пошуку або створення. Встановлюємо другий аргумент на true, тому що намагаємося створити каталог – а не прочитати вже існуючий. В кінці додаємо зворотний виклик помилки.

Поки все в порядку. В нас є каталог; тепер давайте додамо підкаталог. Функція в точності така ж, за винятком одного відмінності: ми змінюємо перший аргумент з ‘Documents’ на ‘Documents/Music’. Досить просто; але що, якщо вам потрібно створити підпапку Sky, з двома батьківськими папками, Images і Nature, всередині папки Documents? Якщо ви напишете ‘Documents/Images/Nature/Sky’ в якості аргументу маршруту, то отримаєте помилку, так як можна створити каталог, коли не існує безпосереднього батьківського елемента. Рішенням цієї проблеми є створення папок одна за одною: Images всередині Documents, Nature всередині Images, а потім Sky всередині Nature. Але цей процес дуже повільний і незручний. Є рішення краще: створити функцію, яка автоматично створить всі необхідні папки.

function createDir(rootDir, folders) {
rootDir.getDirectory(folders[0], {create: true}, function(dirEntry) {
if (folders.length) {
createDir(dirEntry, folders.slice(1));
}
}, errorHandler);
};
createDir(fs.root, ‘Documents/Images/Nature/Sky/’.split(‘/’));

Все, що потрібно при цій маленькій хитрості – це забезпечити повний маршрут, який представляє ті папки, які треба створити. Тепер папка Sky успішно створено, а ви можете створювати всередині неї інші файли або каталоги.

Тепер настав час подивитися, що є в нашій файловій системі. Створимо об’єкт DirectoryReader і застосуємо для читання вмісту каталогу метод readEntries().

fs.root.getDirectory(‘Documents’, {}, function(dirEntry){
var dirReader = dirEntry.createReader();
dirReader.readEntries(function(entries) {
for(var i = 0; i < entries.length; i++) {
var entry = entries;
if (entry.isDirectory){
console.log(‘Directory:’ + entry.fullPath);
}
else if (entry.isFile){
console.log(‘File:’ + entry.fullPath);
}
}
}, errorHandler);
}, errorHandler);

У наведеному вище коді властивості isDirectory і isFile використовуються для того, щоб отримати інший висновок каталогів і файлів. До того ж, ми застосовуємо властивість fullPath для отримання повного маршруту входу замість одного його імені.

Видалити DirectoryEntry з файлової системи можна двома способами: remove() і removeRecursively(). Перший видаляє папку, тільки якщо вона порожня. Інакше ви отримаєте помилку.

fs.root.getDirectory(‘Documents/Music’, {}, function(dirEntry) {
dirEntry.remove(function(){
console.log(‘Directory removed successfully.’);
}, errorHandler);
}, errorHandler);

Якщо всередині папки Music є файли, то вам знадобиться застосувати другий спосіб, який рекурсивно видаляє папку і весь її вміст.

fs.root.getDirectory(‘Documents/Music’, {}, function(dirEntry) {
dirEntry.removeRecursively(function(){
console.log(‘Directory successufully removed.’);
}, errorHandler);
}, errorHandler);

Крок 3 – Робота з файлами

Тепер, коли відомо, як створювати каталоги, пора заповнити їх файлами! У наступному прикладі створюється порожній test.txt у кореневій папці:

fs.root.getFile(‘test.txt’, {create: true, exclusive: true}, function(fileEntry) {
alert(‘A file’ + fileEntry.name + ‘ was created successfully.’);
}, errorHandler);

Перший аргумент до getFile() може бути абсолютним або відносним маршрутом, але він повинен бути валідним. Наприклад, помилково намагатися створити файл при неіснуючому безпосередньому батьківському елементі. Другий аргумент – це буквальний об’єкта, що описує поведінку функції, якщо файл не існує. У цьому прикладі create: true створює файл, якщо той не существуе, і видає помилку, якщо існує (exclusive: true). В іншому випадку при create: false файл просто вибирається і повертається.

Однак порожній файл не дуже потрібен, так що давайте додамо всередину трохи контенту. Для цього можна застосувати об’єкт FileWriter.

fs.root.getFile(‘test.txt’, {create: false}, function(fileEntry) {
fileEntry.createWriter(function(fileWriter) {
window.BlobBuilder = window.BlobBuilder || window.WebKitBlobBuilder;
var bb = new BlobBuilder();
bb.append(‘Filesystem API is awesome!’);
fileWriter.write(bb.getBlob(‘text/plain’));
}, errorHandler);
}, errorHandler);

Як показано вище, ми витягуємо файл test.txt і створюємо для нього об’єкт FileWriter. Потім прикріплюємо до нього вміст, створюючи новий об’єкт BlobBuilder і застосовуючи до FileWriter метод write().

Виклик getFile() тільки витягує FileEntry. Він не повертає вміст файлу. Тому, якщо нам потрібно прочитати контент, слід застосувати об’єкти File і FileReader.

fs.root.getFile(‘test.txt’, {}, function(fileEntry) {
fileEntry.file(function(file) {
var reader = new FileReader();
reader.onloadend = function(e) {
alert(this.result);
};
reader.readAsText(file);
}, errorHandler);
}, errorHandler);

Ми записали в свій файл трохи контенту, але що, якщо у нас виникне бажання додати ще? Щоб прикріпити дані до існуючого файлу, ще раз застосовується FileWriter. Можна переставити додаток для запису в кінець файлу з допомогою методу seek(). seek приймає в якості аргументу офсет в байтах і встановлює розташування програми для запису у файли до цього офсету.

fs.root.getFile(‘test.txt’, {create: false}, function(fileEntry) {
fileEntry.createWriter(function(fileWriter) {
fileWriter.seek(fileWriter.length);
window.BlobBuilder = window.BlobBuilder || window.WebKitBlobBuilder;
var bb = new BlobBuilder();
bb.append(‘Yes, it is!’);
fileWriter.write(bb.getBlob(‘text/plain’));
}, errorHandler);
}, errorHandler);

Для видалення файлу з системи просто викличте entry.remove(). Перший аргумент цього методу – функція зворотного виклику з нульовим параметром, яка викликається, коли файл вдало видаляється. Другий – це зворотний виклик випадкової помилки.

fs.root.getFile(‘test.txt’, {create: false}, function(fileEntry) {
fileEntry.remove(function() {
console.log(‘File successufully removed.’);
}, errorHandler);
}, errorHandler);

Крок 4 – Управління файлами і папками

FileEntry і DirectoryEntry ділять одні методи копіювання API, переміщення і перейменування вводів. Для цих операцій можна застосовувати два методи: copyTo() і moveTo(). Вони обидва беруть одні параметри:

copyTo(parentDirEntry, opt_newName, opt_successCallback, opt_errorCallback);
moveTo(parentDirEntry, opt_newName, opt_successCallback, opt_errorCallback);

Перший параметр – це батьківська тека для переміщення/копіювання введення. Другий – довільне нове ім’я, що дається переміщеному/скопіювати введення, яке насправді потрібно, коли ви копіюєте введення в тій же папці, інакше у вас буде помилка. Третій і четвертий параметри пояснювалися раніше.

Давайте розглянемо нескладні приклади. В наступному ми копіюємо файл test.txt з root в папку Documents.

function copy(currDir, srcEntry, destDir) {
currDir.getFile(srcEntry, {}, function(fileEntry) {
currDir.getDirectory(destDir, {}, function(dirEntry) {
fileEntry.copyTo(dirEntry);
}, errorHandler);
}, errorHandler);
}
copy(fs.root, ‘test.txt’, ‘Documents/’);

У цьому прикладі замість копіювання test.txt переміщається в Documents:

function move(currDir, srcEntry, dirName) {
currDir.getFile(srcEntry, {}, function(fileEntry) {
currDir.getDirectory(dirName, {}, function(dirEntry) {
fileEntry.moveTo(dirEntry);
}, errorHandler);
}, errorHandler);
}
move(fs.root, ‘test.txt’, ‘Documents/’);

У наступному прикладі ім’я test.txt змінюється на text.txt:

function rename(currDir, srcEntry, newName) {
currDir.getFile(srcEntry, {}, function(fileEntry) {
fileEntry.moveTo(currDir, newName);
}, errorHandler);
}
rename(fs.root, ‘test.txt’, ‘text.txt’);

Дізнатися більше

У цьому вступному уроці ми лише побіжно торкнулися різні інтерфейси файлових систем. Якщо хочете дізнатися більше і глибше покопатися в API файлових систем, вам слід звернутися до специфікації W3C:

API файлів: каталоги і система (File API: Directories and System)

API файлів: запис (File API: Writer)

API файлів (File API)

Тепер, коли вам зрозумілі основи API файлової системи, і те, як його можна застосовувати, буде набагато простіше зрозуміти документацію по API, яка на перший погляд може здатися заплутаною.

Висновок

API файлової системи – це потужна і легка в застосуванні технологія, що надає веб-розробникам цілий набір нових можливостей при побудові веб-додатків. Варто відзначити, що вона все ще досить нова і широко підтримується основними браузерами, але це, безумовно, зміниться в майбутньому. Можна в реальності стати одним з піонерів розробки!