Статьи

Автономные файлы в HTML5: API FileSystem

Последние несколько недель я экспериментировал с API-интерфейсом FileSystem в Chrome и решил поделиться своими размышлениями с небольшой демонстрацией системы синхронизации файлов, которую я собираюсь использовать в приложении, где пользователь может загрузить копию. файла, работайте с ним в автономном режиме, а затем синхронизируйте его при повторном подключении. В этом посте я просто хочу показать сторону синхронизации, где файл загружается и реплицируется в локальной файловой системе (в браузере), а затем я постараюсь опубликовать второй пост, показывающий тихую синхронизацию с сервером (push и потяните на основе последней отметки времени редактирования), как только у меня эта часть работает.

 

На сервере у меня просто плоская файловая структура в корневой папке и несколько файлов, из которых я строю список в пользовательском интерфейсе. В настоящее время каждая запись файла имеет кнопку синхронизации, чтобы вручную просто перетащить файл с сервера в локальную файловую систему. Теперь эта файловая система не является файловой системой вашей операционной системы, а является изолированной средой в браузере, доступной только приложению, которое ее запросило и создало. Чтобы иметь возможность постоянно хранить файлы, вам нужно сначала попросить пользователя освободить это пространство и позволить вам создать локальную файловую систему для приложения.

window.webkitStorageInfo.requestQuota(
    window.PERSISTENT
  , 5*1024*1024
  , function(gb) {
      window.webkitRequestFileSystem(
          window.PERSISTENT
        , gb
        , fileSync.init
        , fileSync.err
      );
    }
  , fileSync.err
);

Здесь я запрашиваю 5 МБ дискового пространства для размещения файлов, и как только пользователь принимает файловую систему, запрашивается функция инициализации объекта fileSync, заданная как обратный вызов успеха. Это только один раз спросит пользователя о выделенном пространстве, а при последующих запросах просто запросит файловую систему и запустит функцию init. При инициализации объект fileSync устанавливает некоторую обработку событий и проверяет, включено приложение или нет. Если он подключен к Интернету, он запрашивает последний список файлов с сервера и включает кнопки синхронизации. В автономном режиме он просто считывает структуру локального каталога и запускает список файлов, помечая те, которые уже синхронизированы локально в пользовательском интерфейсе, оставляя кнопки синхронизации скрытыми и отключенными.

// set up event handlers and file system
api.init = function (fs) {
 
  dir = document.getElementById('dir-tree');
  dir.addEventListener('click', api.fileAction, false);
 
  root = fs.root;
 
  // off/online detection
  w.addEventListener("offline", api.toggleOnlineState, false);
  w.addEventListener("online", api.refreshFiles, false);
 
  if (w.navigator.onLine) {
 
    api.refreshFiles();
    return;
 
  }
 
  api.toggleOnlineState();
  api.syncStatus();
 
};
 
// get the latest list of files from the server
api.refreshFiles = function () {
 
  var xhr = new XMLHttpRequest;
 
  xhr.open('get', 'file-list.php', true);
  xhr.onerror = api.err;
  xhr.onload = function () {
 
    dir.innerHTML = this.response;
    api.syncStatus();
 
  }
 
  xhr.send();
 
};
 
// marks synced files in the dir tree
api.syncStatus = function () {
 
  var dr = root.createReader();
  dr.readEntries(api.updateStatus, api.err);
  api.toggleOnlineState();
 
};
 
// show / hide sync buttons when off or online
api.toggleOnlineState = function () {
 
  var i = 0
    , d = 'none'
    , sy = dir.querySelectorAll(".sync");
 
  if (w.navigator.onLine) {
    d = 'inline-block';
  }
 
  for (i = 0; i < sy.length; ++i) {
    sy.item(i).style.display = d;
  }
 
};
 
// mark synced items in the tree
api.updateStatus = function (listing) {
 
  var i = 0, entry;
 
  for (; i < listing.length; ++i) {
 
    entry = listing.item(i);
    api.flagSynced(entry);
 
  }
 
};

Здесь происходит кое-что, и, разумеется, все это можно сделать с некоторой оптимизацией, но в любом случае … в функции init я установил ссылку на корень файловой системы, который является объектом DirectoryEntry. Я добавляю прослушиватели событий во включенные и отключенные события, чтобы отображать и скрывать кнопки синхронизации соответствующим образом и проверять, включено приложение или нет, чтобы выполнить соответствующую инициализацию. Если он находится в автономном режиме, он переходит к настройке дерева, читая файлы с помощью DirectoryReader для просмотра содержимого корневого каталога. Я перебираю возвращенный EntryArray и отмечаю файлы как синхронизированные, добавляя класс в элемент списка, где они находятся в пользовательском интерфейсе. Если вы хотите поместить кусочки происходящего здесь, вы можете проверить полный код на Github.

Хорошо, это здорово, но как я в первую очередь поместил файлы в локальную файловую систему …

Ajax файл скачать

Возможно, вы читали некоторые из моих других постов по спецификации уровня 2 XMLHttpRequest, в которой добавлены некоторые очень приятные новые функции. Одной из таких функций является добавление responseType. При этом мы можем установить тип ответа на запрос как Blob или ArrayBuffer, чтобы мы могли иметь дело с двоичными данными напрямую, круто, да. При этом я запрашиваю файл и записываю данные ответа прямо в файл в локальной файловой системе.

// pull file down into local
api.pull = function (url, name) {
 
  var xhr = new XMLHttpRequest;
 
  // request the file
  xhr.open('get', url, true);
  xhr.responseType = 'arraybuffer'; // give us an array buffer back please
  xhr.onload = function () {
 
    var res = this.response; // ArrayBuffer!
 
    // get the local file or create it if it doesn't exists
    root.getFile(name, {create: true}, function (fe) {
 
      // get a handle to write to the file
      fe.createWriter(function(writer) {
 
        // create a blob builder to append the data to
        var bb = new w.WebKitBlobBuilder;
 
        writer.onwriteend = function () {
          api.flagSynced(fe) // mark as synced in the UI
        }
        writer.onerror = api.err;
 
        // append the data and write to the file
        bb.append(res);
        writer.write(bb.getBlob());
 
      });
 
    }, api.err);
 
  }
 
  xhr.send(); // send the request
 
};

Это действительно круто, надеюсь, комментарии в коде достаточно хорошо объясняют, что происходит. Объект (WebKit) BlobBuilder предоставляет методы для создания большого двоичного объекта и добавления к нему данных, что замечательно, если мы хотим работать с фрагментарными данными или нарезать файл и передавать его обратно на сервер, подробнее об этом в другом посте. !

Получив наши локальные файлы, мы можем открыть их, указав окно на URL-адрес файла в локальном хранилище. Угадай, что, есть способ для этого тоже — toURL

api.open = function (name) {
 
  // get the file and open it
  root.getFile(name, {}, function (fe) {
 
    w.location = fe.toURL();
 
  }, api.err);
 
};

Отключение приложения

Настройка этого приложения для запуска в автономном режиме требует, чтобы мы создали файл манифеста для кэша приложения и указали ссылку на него в теге HTML страницы индекса.

<html manifest="sync.appcache">

Файл кэша заставил меня работать кругами, так как после кэширования файлов браузер всегда использует кэш и даже не пытается получить файлы из сети при подключении. Это понятно, но попытка загрузить файлы, когда он-лайн, привела к 404-м, поскольку он пытался получить их из кэша. В итоге я добавил страницу file-list.php в раздел NETWORK моего файла манифеста, чтобы при работе в Интернете я мог получать свежие файлы с сервера. Остальные файлы — просто js и css, так что они отлично кешируют, пока коровы не вернутся домой. Ниже то, что я закончил в моем файле манифеста.

CACHE MANIFEST
 
NETWORK:
file-list.php
/fs
 
CACHE:
index.php
css/style.css
css/images/sync.png
js/sync.js

Я поражен тем, насколько все это просто и насколько мощными становятся эти новые API, с небольшой помощью статьи Эрика Бидельмана о HTML5-скалах, которую я написал за пару часов вчера вечером.

 

Источник: http://www.profilepicture.co.uk/tutorials/html5-filesystem-api/