chrome.fileSystem API 使い方備忘録

諸用でChrome拡張からファイルシステムを扱う事が出来ないかと思って調べたらちゃんとAPIが用意されてたんですが、例のごとく情報が少なくて使い方が良くわからなかったんで、公式ドキュメントやサンプルを読みながら理解した際の覚書です。

# はじめに

以下の公式ドキュメントとサンプルを参考にしました。
細かいことはこちらを見ると良いと思います。
[chrome.fileSystem API](https://developer.chrome.com/apps/fileSystem)
[GoogleChrome/chrome-app-samples/samples/filesystem-access/ - Github](https://github.com/GoogleChrome/chrome-app-samples/tree/master/samples/filesystem-access)

そもそも、chrome.fileSystem APIはChrome拡張に向けて独自に用意された物ではなく、W3Cのドラフトにあった物をChromeに実装したものみたいですね。
[File API: Directories and System - W3C](https://www.w3.org/TR/file-system-api/)
結局勧告まで至っておらず、これを実装しているのはChromeだけで、それがChrome拡張のAPIに転用されたという経緯っぽいですね。
chrome APIのドキュメントを見てもEntryに関するリファレンスが見当たらなかったのでどういう事かと思っていたんですが、MDNのドキュメントを見るのが良さそうです。
[Entry - Web API インターフェイス | MDN](https://developer.mozilla.org/ja/docs/Web/API/Entry)

※追記
当初はChrome拡張から使えるのかと思ってたんですが、Chrome拡張(Extension)からは使えず、ChromeAppしか使えないみたいですね。ChromeExtensionとChromeAppの違いもよく分かってなかったので、改めて知識を整理したい…

# 準備

chrome.fileSystem APIを使うにはmanifest.jsonでpermissionを設定してやる必要があります。

```manifest.json
"permissions": [
  "...",
  "fileSystem"
]
```

~~また、拡張機能のパッケージ化を行わないと、fileSystem APIは使えません。~~
そもそもChromeExtensionからはfileSystem APIを利用する事はできないようです。
fileSystem APIが使えるのはChromeAppのみのようです。

# 基本的な使い方

[chrome.fileSystem.chooseEntry](https://developer.chrome.com/apps/fileSystem#method-chooseEntry)で、ダイアログを表示してユーザーにファイルやディレクトリを選択させる事ができます。
コールバックでユーザーが選択したファイル（ディレクトリ）のEntryが返ってくるので、よしなにしてやります。
（Entryのリファレンスは[こちら](https://developer.mozilla.org/en-US/docs/Web/API/Entry)）

Entryの情報を保存しておきたい場合（例えば、chooseEntryでユーザーに作業ディレクトリを選択させて、そのディレクトリに対して操作するというような形にしたい時など）は、[retainEntry](https://developer.chrome.com/apps/fileSystem#method-retainEntry)でEntryの情報をstring化した物を[chrome.storage](https://developer.chrome.com/apps/storage)で保存しておき、適宜それを読みだして[restoreEntry](https://developer.chrome.com/apps/fileSystem#method-restoreEntry)で再びEntryに復元するという手順を踏みます。

# 例：選択したディレクトリ内のファイルを列挙する

私が当初やりたかった事です。
[公式のサンプル](https://github.com/GoogleChrome/chrome-app-samples/tree/master/samples/filesystem-access)のコードを参照しながら流れを解説します。

https://github.com/GoogleChrome/chrome-app-samples/blob/master/samples/filesystem-access/js/app.js#L193-L203
https://github.com/GoogleChrome/chrome-app-samples/blob/master/samples/filesystem-access/js/app.js#L127-L152

```js:app.js#L193-L203
// 1.
chooseDirButton.addEventListener('click', function(e) {
  // 2.
  chrome.fileSystem.chooseEntry({type: 'openDirectory'}, function(theEntry) {
    // 3.
    if (!theEntry) {
      output.textContent = 'No Directory selected.';
      return;
    }
    // 4.
    // use local storage to retain access to this file
    chrome.storage.local.set({'chosenFile': chrome.fileSystem.retainEntry(theEntry)});
    // 5.
    loadDirEntry(theEntry);
  });
});
```


```js:app.js#L127-L152
function loadDirEntry(_chosenEntry) {
  chosenEntry = _chosenEntry;
  if (chosenEntry.isDirectory) {
    // 6.
    var dirReader = chosenEntry.createReader();
    var entries = [];

    // 7.
    // Call the reader.readEntries() until no more results are returned.
    var readEntries = function() {
       dirReader.readEntries (function(results) {
        if (!results.length) {
          textarea.value = entries.join("\n");
          saveFileButton.disabled = true; // don't allow saving of the list
          displayEntryData(chosenEntry);
        } 
        else {
          results.forEach(function(item) { 
            entries = entries.concat(item.fullPath);
          });
          readEntries();
        }
      }, errorHandler);
    };

    readEntries(); // Start reading dirs.    
  }
}
```

※説明の為に一部コメントを付加しています

1. ボタンのクリックイベントを検知してchooseEntryしています。
2. chooseEntryのタイミングでダイアログが表示されます。ここでは`{type: 'openDirectory'}`が指定されているので、ディレクトリ選択のダイアログが表示されます。
3. ユーザーがダイアログ上で選択したディレクトリの[DirectoryEntry](https://developer.mozilla.org/en-US/docs/Web/API/DirectoryEntry)が、コールバックの引数（`theEntry`）に渡されます。
theEntryをnullチェックして、何も選択されなかった場合（ダイアログでキャンセルされた場合）はreturnしています。
4. chrome.storage.localにEntryを保存しておき、再起動時に再び読み取れるようにしておきます。（ファイルを列挙するのに必要ではありません）
5. ディレクトリ内のEntryを読み取る関数に処理を渡しています。
6. DirectoryEntryから[createReader](https://developer.mozilla.org/en-US/docs/Web/API/DirectoryEntry#createReader)を使って[DirectoryReader](https://developer.mozilla.org/en-US/docs/Web/API/DirectoryReader)を生成しています。
7. DirectoryReaderから[readEntries](https://developer.mozilla.org/en-US/docs/Web/API/DirectoryReader#readEntries)でディレクトリ内のEntryを読み出します。readEntriesのコールバックにEntryの配列が返ってくるので、結果が無くなるまで再帰的に呼び出しています。結果から各Entryのfullpathを取得して、結合したものをテキストエリアに表示しています。

# まとめ

- FileSystem API自体は[W3Cのドラフト](https://dev.w3.org/2009/dap/file-system/file-dir-sys.html)（勧告には至らなかった）にあったものに則っている
- chrome.fileSystem.chooseEntryでダイアログを出してEntryを取得
- 取得したEntryに対してよしなに操作する
- Entryのstorageへの保存・復元はretainEntry, restoreEntryで行う


[Chrome拡張の開発方法まとめ　その1：概念編](http://qiita.com/edit-mode/items/26d7a22233ecdf48fed8)のほうもどうぞ。（その2いい加減書かなきゃな…）