22.7. Прикладной интерфейс к файловой системе

We use cookies. Read the Privacy and Cookie Policy

В разделе 22.6.5 вы познакомились с классом FileReader, использовавшимся для чтения содержимого файлов, выбираемых пользователем, или любых двоичных объектов. Типы File и Blob определяются проектом спецификации, известной как «File АРІ». Проект другой спецификации, еще более новой, чем «File АРІ», дает веб-приложениям управляемый доступ к частной файловой системе, где они могут писать в файлы, читать файлы, создавать каталоги, читать содержимое каталогов и т. д. На момент написания этих строк данный прикладной интерфейс к файловой системе был реализован только в броузере Google Chrome. Это мощная и важная форма локального хранилища, поэтому она будет описана здесь, несмотря на то что ее прикладной интерфейс еще менее стабилен, чем другие прикладные интерфейсы, описываемые в этой главе. Данный раздел охватывает лишь основные задачи, выполняемые с файловой системой, и не демонстрирует всех возможностей прикладного интерфейса. Так как обсуждаемый здесь прикладной интерфейс является новым и нестабильным, он не описывается в справочном разделе этой книги.

Работа с файлами в локальной файловой системе является многоэтапным процессом. Прежде всего, необходимо получить объект, представляющий саму файловую систему. Сделать это можно с помощью синхронного прикладного интерфейса в фоновом потоке или асинхронного - в основном потоке выполнения:

// Метод синхронного получения файловой системы. Принимает параметры,

// определяющие срок существования файловой системы и ее размер.

// Возвращает объект файловой системы или возбуждает исключение,

var fs = requestFileSystemSync(PERSISTENT, 1024*1024);

// Асинхронная версия принимает функции обратного вызова для обработки

// успешного или неудачного создания файловой системы

requestFileSystem(TEMPORARY,     // срок существования

                   50*1024*1024, // размер: 50 Мбайт

                  function(fs) { // будет вызвана с объектом файловой системы

                                 // Здесь используется объект fs

                  },

                  function(e) { // будет вызвана с объектом ошибки

                    console.log(е); // Или как-то иначе обработать ошибку

});

В обеих версиях прикладного интерфейса, синхронной и асинхронной, указываются срок существования и желаемый размер файловой системы. Файловая система, срок существования которой определяется константой PERSISTENT (постоянная), подходит для веб-приложений, которым требуется хранить пользовательские данные постоянно. Броузер не будет удалять эту файловую систему, пока пользователь явно не потребует этого. Файловая система, срок существования которой определяется константой TEMPORARY (временная), подходит для веб-приложений, которые требуют кэшировать данные, но также сохраняют работоспособность и после того, как веб-броузер удалит файловую систему. Размер файловой системы определяется в байтах и должен быть равен разумному верхнему пределу объема данных, которые потребуется сохранять. Броузер может ограничивать этот размер, устанавливая квоты.

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

Обратите внимание, что в именах этих функций присутствует слово «request» (запросить). Когда приложение вызывает одну из этих функций в первый раз, броузер может запросить разрешение у пользователя, прежде чем создать файловую систему и разрешить доступ к ней.[59]

После получения доступа все последующие вызовы метода request будут просто возвращать объект, представляющий уже имеющуюся локальную файловую систему.

Объект файловой системы, полученный одним из методов, представленных выше, имеет свойство root, ссылающееся на корневой каталог файловой системы. Это объект DirectoryEntry, и он может иметь вложенные каталоги, представленные собственными объектами DirectoryEntry. Каждый каталог в файловой системе может содержать файлы, представленные объектами FileEntry. Объект DirectoryEntry определяет методы для получения объектов DirectoryEntry и FileEntry по пути в файловой системе (они могут создавать новые каталоги и файлы, если указанное имя не существует). Объект DirectoryEntry также определяет фабричный метод createReader(), возвращающий объект DirectoryReader, который позволяет получить список содержимого каталога.

Класс FileEntry определяет метод для получения объекта File (двоичный объект Blob), представляющий содержимое файла. Получив этот объект, его содержимое можно прочитать с помощью объекта FileReader (как показано в разделе 22.6.5). Объект FileEntry определяет еще один метод, возвращающий объект FileWriter, который можно использовать для записи в файл.

Операции чтения и записи с использованием этого прикладного интерфейса представляют собой многоэтапный процесс. Прежде всего, необходимо получить объект файловой системы. Затем, используя свойство root этого объекта, необходимо отыскать (и при необходимости создать) объект FileEntry, представляющий требуемый файл. Затем с помощью объекта FileEntry нужно получить объект File или FileWriter для выполнения операции чтения или записи. Этот процесс становится особенно сложным при использовании асинхронного прикладного интерфейса:

// Читает текстовый файл "hello.txt" и выводит его содержимое. При использовании

// асинхронного прикладного интерфейса глубина вложенности функций достигает

// четырех уровней. Этот пример не включает определения функций обработки ошибок.

requestFileSystem(PERSISTENT, 10*1024*1024, function(fs) { // Получить объект ФС

  fs.root.getFile("hello.txt”, {}, function(entry) { // Получить FileEntry

    entry.file(function(file) { // Получить File

      var reader = new FileReader();

      reader.readAsText(file);

      reader.onload = function() { // Получить содержимое файла

        console.log(reader.result);

      };

    });

  });

});

В примере 22.13 демонстрируется более полное решение. В нем показано, как использовать асинхронный прикладной интерфейс для чтения, записи и удаления файлов, создания каталогов и получения списков их содержимого.

Пример 22.13. Использование асинхронного прикладного интерфейса доступа к файловой системе

/*

* Следующие функции были протестированы в Google Chrome 10.0 dev.

* Вам может потребоваться запустить Chrome со следующими ключами:

* --unlimited-quota-for-files : разрешает доступ к файловой системе

* —allow-file-access-from-files : разрешает тестировать из URL file://

*/

// Многие асинхронные функции, используемые здесь, принимают необязательные функции

// обратного вызова для обработки ошибок.

// Следующая функция просто выводит сообщение об ошибке,

function logerr(e) { console.log(e); }

// requestFileSystem() возвращает локальную файловую систему, доступную

// только приложениям с указанным происхождением. Приложение может читать

// и писать файлы в ней, но не может получить доступ к остальной файловой системе,

var filesystem; // Предполагается, что эта переменная будет инициализирована

                // перед вызовом функции, объявленной ниже.

requestFileSystem(PERSISTENT, // Или TEMPORARY для кэширования файлов 10*1024*1024,

                              // Требуется 10 Мбайт

                  function(fs) { // После выполнения вызвать эту функцию

                    filesystem = fs; // Просто сохранить ссылку на файловую систему

                  },                 //в глобальной переменной,

                  logerr); // Вызвать эту функцию в случае ошибки

// Читает содержимое указанного файла как текст и передает его функции обратного вызова,

function readTextFile(path, callback) {

  // Вызвать getFile(), чтобы получить объект FileEntry для файла

  // с указанным именем

  filesystem.root.getFile(path, {}, function(entry) {

    // При вызове этой функции передается объект FileEntry, соответствующий файлу.

    // Теперь следует вызвать метод FileEntry.file(), чтобы получить объект File

    entry.file(function(file) { // Вызвать с объектом File

      var reader = new FileReader(); // Создать объект FileReader

      reader.readAsText(file);       // И прочитать файл

      reader.onload = function() {   // В случае успешного чтения

        callback(reader.result);     // Передать данные функции callback

      }

      reader.onerror = logerr; // Сообщить об ошибке в readAsText()

    },  logerr);               // Сообщить об ошибке в file()

  },

  logerr); // Сообщить об ошибке в getFile()

}

// Добавляет указанное содержимое в конец файла с указанным именем, создает новый файл,

// если файл с указанным именем не существует. Вызывает callback по завершении операции,

function appendToFile(path, contents, callback) {

  // filesystem.root - это корневой каталог,

  filesystem.root.getFile( // Получить объект FileEntry

  path,                    // Имя и путь к требуемому файлу

  {create:true},           // Создать, если не существует

  function(entry) { // Вызвать эту функцию, когда файл будет найден

    entry.createWriter( // Создать для файла объект FileWriter

    function(writeг) { // Вызвать эту функцию после создания

      // По умолчанию запись производится в начало файла.

      // Нам же требуется выполнить запись в конец файла

      writer.seek(writer.length); // Переместиться в конец файла

      // Преобразовать содержимое файла в объект Blob. Аргумент contents

      // может быть строкой, объектом Blob или объектом ArrayBuffer.

      var bb = new BlobBuilder();

      bb.append(contents);

      var blob = bb.getBlob();

      // Записать двоичный объект в файл

      writer.write(blob);

      writer.onerror = logerr; // Сообщить об ошибке в write()

      if (callback) // Если указана функция callback

        writer.onwrite = callback: // вызвать в случае успеха

    },

    logerr); // Сообщить об ошибке в createWriter()

  },

  logerr); // Сообщить об ошибке в getFile()

}

// Удаляет файл с указанным именем, вызывает callback по завершении операции,

function deleteFile(name, callback) {

  filesystem.root.getFile(name, {}, // Получить FileEntry по имени файла

  function(entry) { // Передать FileEntry сюда

                   entry.remove(callback, // Удалить файл

                   logerr);               // Или сообщить

                  },                      //об ошибке в remove()

  logerr): // Сообщить об ошибке в getFile()

}

// Создает новый каталог с указанным именем

function makeDirectory(name, callback) {

  filesystem.root.getDirectory(name, // Имя создаваемого каталога

                               { // Параметры

                                create: true, // Создать, если не сущ.

                                exclusive:true // Ошибка, если существует

                               },

                               callback, // Вызвать по завершении

                               logerr);  // Выводить любые ошибки

}

// Читает содержимое указанного каталога и передает его в виде массива строк

// указанной функции callback

function listFiles(path, callback) {

  // Если каталог не указан, получить содержимое корневого каталога.

  // Иначе отыскать каталог с указанным именем и вернуть список

  // с его содержимым (или сообщить об ошибке поиска),

  if (!path) getFiles(filesystem.root);

  else filesystem.root.getDirectory(path, {}, getFiles, logerr);

  function getFiles(dir) { // Эта функция используется выше

    var reader = dir.createReader(); // Объект DirectoryReader

    var list = []; // Для сохранения имен файлов

    reader.readEntries(handleEntries, // Передать функции ниже

                       logerr); // или сообщить об ошибке.

    // Чтение каталогов может превратиться в многоэтапный процесс.

    // Необходимо сохранять результаты вызовов readEntries(), пока не будет

    // получен пустой массив. На этом операция будет закончена,

    // и полный список можно будет передать функции callback,

    function handleEntries(entries) {

      if (entries.length == 0) callback(list); // Операция закончена

      else {

        // Иначе добавить эти записи в общий список и запросить

        // очередную порцию. Объект, подобный массиву, содержит

        // объекты FileEntry, и нам следует получить имя для каждого.

        for(var і = 0; і < entries.length; i++) {

          var name = entries[i].name;              // Получить имя записи

          if (entries[i].isDirectory) name += "/"; // Пометить каталоги

          list.push(name);                         // Добавить в список

        }

        // Получить следующую порцию записей

        reader.readEntries(handleEntries, logerr);

      }

    }

  }

}

Работать с файлами и с файловой системой намного проще в фоновых потоках выполнения, где допускается выполнять блокирующие вызовы и можно использовать синхронный прикладной интерфейс. Пример 22.14 определяет те же функции для работы с файловой системой, что и пример 22.13, но использует синхронный прикладной интерфейс, и потому получился намного короче.

Пример 22.14. Синхронный прикладной интерфейс для работы с файловой системой

// Утилиты для работы с файловой системой, использующие синхронный прикладной

// интерфейс, предназначенный для фоновых потоков выполнения

var filesystem = requestFileSystemSync(PERSISTENT, 10*1024*1024);

function readTextFile(name) {

  // Получить объект File из объекта FileEntry из корневого DirectoryEntry

  var file = filesystem.root.getFile(name).file();

  // Использовать для чтения синхронную версию FileReader

  return new FileReaderSync().readAsText(file);

}

function appendToFile(name, contents) {

  // Получить FileWriter из FileEntry из корневого DirectoryEntry

  var writer = filesystem.root.getFile(name, {create:true}).createWriter();

  writer.seek(writer.length); // Начать запись с конца файла

  var bb = new BlobBuilder()  // Собрать содержимое в виде объекта Blob

  bb.append(contents);

  writer.write(bb.getBlob()); // Записать двоичный объект в файл

}

function deleteFile(name) {

  filesystem.root.getFile(name).remove();

}

function makeDirectory(name) {

  filesystem.root.getDirectory(name. { create: true, exclusive:true });

}

function listFiles(path) {

  var dir = filesystem.root;

  if (path) dir = dir.getDirectory(path);

  var lister = dir.createReader();

  var list = [];

  do {

    var entries = lister.readEntries();

    for(var і = 0; і < entries.length; i++) {

      var name = entries[i].name;

      if (entries[i]. isDirectory) name += list.push(name);

    }

  } while(entries.length > 0); return list;

}

// Разрешить основному потоку выполнения использовать эти утилиты, посылая сообщения

onmessage = function(e) {

  // Сообщение должно быть объектом:

  // { function: "appendToFile", args: ["test", "testing, testing"]}

  // Вызвать указанную функцию с указанными аргументами и послать сообщение обратно

  var f = self[e.data.function];

  var result = f.apply(null, e.data.args);

  postMessage(result);

};