От автора: HTML5 позволяет нам пожинать урожай новых возможностей, таких как рисование с помощью canvas, реализация мультимедиа с помощью аудио- и видео— API и так далее. Одним из этих сравнительно новых инструментов является API файловой системы. Он дает нам доступ к разделу-«песочнице» локальной файловой системы пользователя, таким образом еще больше заполняя брешь между рабочим столом и веб-приложениями! Сегодня в учебнике мы пробежимся по основам этого нового волнующего API, исследовав привычные задачи файловых систем. Давайте приступим!
Вступление
Для применения больше не нужно скачивать и устанавливать данное программное обеспечение. Всего лишь веб-браузер и соединение с Интернетом дает нам возможность использовать любое веб-приложение в любое время, где угодно и на любой платформе.
В общем, веб-приложения – это отлично; но в сравнении с приложениями для рабочего стола, у них все еще имеется один значительный недостаток: отсутствие способа взаимодействия и организации данных в структурированную иерархию папок – настоящую файловую систему. К счастью, при помощи нового API файловой системы все меняется. Он дает веб-приложениям контролируемый доступ к частной локальной файловой системе “sandbox” («песочница»), где можно записывать и читать файлы, создавать и заносить в списки каталоги и так далее. Хотя на момент написания этой статьи «полную» реализацию API файловой системы поддерживает только браузер Google Chrome, она все же заслуживает изучения, как мощная и удобная форма местного хранения файлов.
API файловых систем бывает двух разных версий. Асинхронный API, удобный для обычных приложений, и синхронный API, предназначенный для применения веб-пользователями. В рамках этого учебника мы изучим только асинхронную версию API.
Шаг 1 – Начинаем
Ваш первый шаг – получить доступ к файловой системе HTML5, сделав запрос к объекту системы LocalFile с помощью глобального метода window.requestFileSystem():
1 |
window.requestFileSystem(type, size, successCallback, opt_errorCallback) |
Веб-приложению «вырваться» за пределы местного корневого каталога невозможно.
В качестве первых двух параметров вы определяете время существования и размер нужной вам файловой системы. ПЕРМАНЕНТНАЯ файловая система подходит для тех веб-приложений, которые обязаны хранить данные пользователя постоянно. Браузер не будет их удалять, за исключением случаев определенного пользовательского запроса. ВРЕМЕННАЯ файловая система подходит для веб-приложений, кэширующих данные, но может работать, если веб-браузер удаляет файловую систему. Размер файловой системы определяется в байтах и должен иметь разумную верхнюю границу количества данных, нуждающихся в хранении.
Третий параметр – это функция обратного вызова, запускаемая, когда агент пользователя успешно предоставляет файловую систему. Ее аргумент – объект FileSystem. И, наконец, можно добавить опциональную функцию обратного вызова, которая вызывается в случае ошибки, или когда запрос к файловой системе отклоняется. Ее аргумент – объект FileError. Хотя этот параметр необязательный, отслеживать ошибки для пользователей очень полезно, так как ошибки случаются во многих местах.
Файловая система, действующая с этими функциями, зависит от происхождения содержащего ее документа. Все документы или веб-приложения одного происхождения (хост, порт и протокол) разделяют файловую систему. Два документа или приложения различного происхождения имеют совершенно разные и несовместимые файловые системы. Файловая система узко предназначена для одного приложения и не может получить доступа к данным, хранимым в другом приложении. На жестком диске пользователя она также изолирована от остальных файлов, и это хорошо: веб-приложению «вырваться» за пределы местного корневого каталога или получить доступ к произвольным файлам каким-либо иным способом невозможно.
Давайте рассмотрим пример:
1 2 3 4 5 6 7 8 9 10 11 12 |
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() здесь слишком универсальна. Так что если хотите, можно создать слегка оптимизированную версию, которая дает пользователю более полное описательное сообщение об ошибке:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 |
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 внутри корневой папки:
1 2 3 |
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. Но этот процесс очень медленный и неудобный. Есть решение получше: создать функцию, которая автоматически создаст все необходимые папки.
1 2 3 4 5 6 7 8 9 |
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().
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
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[i]; 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(). Первый удаляет данную папку, только если она пустая. Иначе вы получите ошибку.
1 2 3 4 5 |
fs.root.getDirectory('Documents/Music', {}, function(dirEntry) { dirEntry.remove(function(){ console.log('Directory successfully removed.'); }, errorHandler); }, errorHandler); |
Если внутри папки Music есть файлы, то вам понадобится применить второй способ, который рекурсивно удаляет папку и все ее содержимое.
1 2 3 4 5 |
fs.root.getDirectory('Documents/Music', {}, function(dirEntry) { dirEntry.removeRecursively(function(){ console.log('Directory successufully removed.'); }, errorHandler); }, errorHandler); |
Шаг 3 – Работа с файлами
Теперь, когда известно, как создавать каталоги, пора заполнить их файлами! В следующем примере создается пустой test.txt в корневой папке:
1 2 3 |
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.
1 2 3 4 5 6 7 8 |
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.
1 2 3 4 5 6 7 8 9 |
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 принимает в качестве аргумента офсет в байтах и устанавливает расположение приложения для записи в файлы к этому офсету.
1 2 3 4 5 6 7 8 9 |
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(). Первый аргумент этого метода – функция обратного вызова с нулевым параметром, которая вызывается, когда файл удачно удаляется. Второй – это обратный вызов случайной ошибки.
1 2 3 4 5 |
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(). Они оба принимают одни параметры:
1 2 3 |
copyTo(parentDirEntry, opt_newName, opt_successCallback, opt_errorCallback); moveTo(parentDirEntry, opt_newName, opt_successCallback, opt_errorCallback); |
Первый параметр – это родительская папка для перемещения/копирования в нее ввода. Второй – произвольное новое имя, даваемое перемещенному/скопированному вводу, которое на самом деле требуется, когда вы копируете ввод в той же папке, иначе у вас будет ошибка. Третий и четвертый параметры объяснялись ранее.
Давайте рассмотрим несложные примеры. В следующем мы копируем файл test.txt из root в директорию Documents.
1 2 3 4 5 6 7 8 9 |
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:
1 2 3 4 5 6 7 8 9 |
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:
1 2 3 4 5 6 7 |
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 файловой системы – это мощная и легкая в применении технология, предоставляющая веб-разработчикам целый набор новых возможностей при построении веб-приложений. Стоит отметить, что она все еще довольно новая и широко не поддерживается основными браузерами, но это определенно изменится в будущем. Можно в реальности стать одним из пионеров разработки!
Автор: Ivaylo Gerchev
Источник: //net.tutsplus.com/tutorials/
Редакция: Команда webformyself.