2013年7月29日月曜日

[HTML5]FileSystemAPIでファイルやディレクトリを操作

HTML5の FileSystem API を使ってファイルを操作してみました。
忘れやすいので備忘録としてメモ。

サンプルには下の処理が含まれています。

  • Temporary/Persistent領域確保 (webkitRequestFileSystem)
  • フォルダ作成 (DirectoryEntry.getDirectory)
  • ファイル作成 (FileEntry.getFile)
  • ファイル書込 (FileWriter)
  • ファイル読込 (FileReader)
  • ファイル削除 (FileEntry.remove)

※なおサンプルはChromeブラウザで動かすことを前提にしています。webkitなprefixをmozにすればMozilla系でも動くかもしれませんが自分は試してません。

<!DOCTYPE html>
<html>
<meta charset = "utf-8"></meta>
<script type="text/javascript">
window.onload = function() {

  // ボタンとかのDOM取得
  var read_btn = document.querySelector('#read_btn');
  var remove_btn = document.querySelector('#remove_btn');

  // FileSystem Instance
  var globalFS = null;

  // 5MBのTemporary領域を確保してファイル作成
  webkitRequestFileSystem(TEMPORARY, 5 * 1024 * 1024 /*5MB*/ , createFile, onErr);
  // ちなみにPersistent領域を確保するときはこんな感じ
  /*
  navigator.webkitPersistentStorage.queryUsageAndQuota(function(usage, quota) {
    console.log('usage:' + usage + ', quota:' + quota);
    navigator.webkitPersistentStorage.requestQuota(1024 * 1024, function(grantedQuota) {
      console.log('grantedQuota:' + grantedQuota);
      webkitRequestFileSystem(PERSISTENT, 1024 * 1024, function(fs) {
        console.log(fs);
      });
    });
  });
  */

  //
  // define functions
  //

  /**
   * func for onerror
   */
  function onErr(err) {
    var msg = '';
    switch (err.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);
  }

  /**
   * func for initializing FileSystem and creating a file
   */
  function createFile(fs) {
    globalFS = fs;
    console.log('Opened file system: ' + globalFS.name);
    // create a directory "mydir"
    globalFS.root.getDirectory('mydir', { create: true }, function(dirEntry) {
      // create a file "hoge.txt"
      /*
       * 既にファイルがある場合には内容が上書きされないので注意。
       * 上書きした時は一端ファイルを削除してから新規作成すること。
       */
      globalFS.root.getFile('mydir/hoge.txt', { create: true }, function(fileEntry) {
        console.log('created: ' + fileEntry.fullPath);
        // Create a FileWriter object for our FileEntry
        fileEntry.createWriter(function(fileWriter) {
          var truncated = false;
          var data = null;

          fileWriter.onwriteend = function(evt) {
            console.log('Write completed.');
            // 余分なデータ尻をちょん切る。
            if(!truncated) {
              fileWriter.seek(data.size);
              fileWriter.truncate(data.size);
              truncated = true;
            }
          };

          fileWriter.onerror = function(evt) {
            console.log('Write failed: ' + evt.toString());
          };

          // create blob data
          data = new Blob([ 'helloworld.' ]);
          // write data into the file.
          fileWriter.write(data);
        }, onErr);
      }, onErr);
    }, onErr);
  }

  /**
   * Read a file
   */
  function readFile(){
    globalFS.root.getFile('mydir/hoge.txt', {}, function(fileEntry) {
      // Get a File object representing the file,
      // then use FileReader to read its contents.
      fileEntry.file(function(file) {
        var reader = new FileReader();

        reader.onloadend = function(evt) {
          console.log('file contents: ' + this.result);
        };

        reader.readAsText(file);
      }, onErr);
    }, onErr);
  }


  /**
   * Remove a file
   */
  function removeFile(){
    globalFS.root.getFile('mydir/hoge.txt', {}, function(fileEntry) {
      // Get a File object representing the file,
      // then use FileReader to read its contents.
      fileEntry.remove(function() {
        console.log('removed: ' + fileEntry.fullPath);
      }, onErr);
    }, onErr);
  }


  //
  // define onclick actions
  //

  read_btn.onclick = function() {
    readFile();
  }

  remove_btn.onclick = function() {
    removeFile();
  }

}
</script>
<button id='read_btn'>readFile</button><br/>
<button id='remove_btn'>removeFile</button><br/>
</html>

上のサンプルはこんな動作をします。

まずブラウザ起動時の動き

1. FileSystemのTemporary領域が確保され、
2. Temporary領域にフォルダ mydir が作成され、
3. Temporary領域にファイル /mydir/hoge.txt が作成され、
4. 文字列 "helloworld." が書き込まれる

この時点で作成されたファイル /mydir/hoge.txt はChromeブラウザの Peehole から確認することができるし、
ブラウザから
filesystem:http://localhost:8080/temporary/mydir/hoge.txt
にアクセスして確認することもできます。

次にボタン押下時の動き

5. readFileボタンが押下されると mydir/hoge.txt が読み込まれログ表示
6. removeFileボタンが押下されると mydir/hoge.txt が削除される

この時点で /mydir/hoge.txt は削除されているので、再びreadFileボタンを押すと
Error: NOT_FOUND_ERR
がログ出力されます。


ログ出力結果

Opened file system: http_localhost_8080:Temporary
created: /mydir/hoge.txt
Write completed.
file contents: helloworld.
removed: /mydir/hoge.txt



ちょっとした小技

/dir1/dir2/dir3/myfile.txt といったフルパスを指定してファイルを作りたい時には、以下のサンプルが役に立つかもしれません。
下のコードの createDirsAndFile() の処理に注目してください。

window.onload = function() {
    var filepath = '/dir1/dir2/dir3/myfile.txt';
    var dirs = filepath.split('/');
    var filename = dirs[dirs.length - 1];
    dirs.pop(); // ディレクトリ・リストから "myfile.txt" を除去

    webkitRequestFileSystem(TEMPORARY, 5 * 1024 * 1024 /*5MB*/ , function(fs){
        createDirsAndFile(fs.root, dirs);
    }, onErr);

    /**
     * ディレクトリを先に準備してからファイルを作成
     */
    function createDirsAndFile(fs, dirs) {
        // Throw out './' or '/' and move on to prevent something like '/foo/.//bar'.
        if (dirs[0] == '.' || dirs[0] == '') {
            dirs = dirs.slice(1);
        }

        if (dirs.length) {
            fs.getDirectory(dirs[0], { create: true }, function(dirEntry) {
                // 再帰的にディレクトリを作成
                createDirsAndFile(dirEntry, dirs.slice(1));
            }, onErr);
        } else {
            // ここでファイル作成
            fs.getFile(filename, { create: true }, function(fileEntry){
                console.log(fileEntry.fullPath + ' created.');
            });
        }
    }

    /**
     * func for onerror
     */
    function onErr(err) {
        var msg = '';
        switch (err.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);
    }
}

createDirsAndFile()の中では、フォルダが再帰的に作られ、フォルダの階層構造が完成したときに初めてファイルが作成されています。なぜこのような小技が必要になるかというと、階層構造の深いファイルを非同期で次々と作成する際に、フォルダの用意が間に合わずにファイル作成が失敗することが考えられるからです。


参考にしたサイト

FileSystem API について知る - HTML5 Rocks

0 件のコメント:

コメントを投稿