ファイルリストを書き出してくれるWebアプリを作りました

HTML5の機能調査がてら、フォルダのドラッグ＆ドロップで、ファイル一覧をテキスト書き出ししてくれるWebアプリを作りました。

[FilelistMaker](http://filelistmaker.herokuapp.com/) [Chromeでお試しください]


以下、散漫とした内容ですが、制作メモになります。

##1.ドロップしたフォルダの内容を読み取る

[ドラッグ＆ドロップ](http://www.w3.org/TR/html5/editing.html#dnd)のAPIは、ほとんどの主要なブラウザで[実装されています](http://caniuse.com/#feat=dragndrop)。このAPIを使うと、（ブラウザによっては？）単一のファイルだけではなく、複数のファイルを操作することも可能なようです。

ただ、ドロップされたデータが「ファイル」なのか「ディレクトリ」なのかを取得したり、さらにディレクトリ内のデータを読み取ったりするには、[File API (Directories and System)](http://dev.w3.org/2009/dap/file-system/file-dir-sys.html) が必要になります。このAPIは現在では[まだChromeでしか実装されていません](http://caniuse.com/#feat=filesystem)。

このような理由から、このアプリも残念ながら現在はChromeでしか動作しません。

### 参考
[HTML5: Drag and Drop a Folder] (http://www.rajeshsegu.com/2012/08/html5-drag-and-drop-a-folder/comment-page-1/)

### キーワード
+ [File API (Directories and System)](http://dev.w3.org/2009/dap/file-system/file-dir-sys.html) 
+ [DataTransfer](http://html5index.org/Drag%20and%20Drop%20-%20DataTransfer.html)/[DataTransferItemList](http://html5index.org/Drag%20and%20Drop%20-%20DataTransferItemList.html)/[DataTransferItem](http://html5index.org/Drag%20and%20Drop%20-%20DataTransferItem.html)
+ [FileReader](http://html5index.org/File%20API%20-%20FileReader.html)/[DirectoryReader](http://html5index.org/File%20System%20API%20-%20DirectoryReader.html)
+ [Entry](http://html5index.org/File%20System%20API%20-%20Entry.html)/[FileEntry](http://html5index.org/File%20System%20API%20-%20FileEntry.html)/[DirectoryEntry](http://html5index.org/File%20System%20API%20-%20DirectoryEntry.html)



### ざっくりとした流れ

```javascript
/**
 * 'drop'イベントハンドラ
 */
function onDataDrop(event) {
	event.preventDefault();

	var dataTransfer = event.dataTransfer;
    // DataTransferオブジェクトからDataTransferItemListを参照
	if (dataTransfer && dataTransfer.items){
		var items = dataTransfer.items,
			len = items.length;
        // ドロップされたファイルが一つかチェック
		if (len === 1) {
			var item = items[0], entry;
			// HTML5標準
			if (item.getAsEntry) {
				entry = item.getAsEntry();
			// Webkit実装
			} else if (item.webkitGetAsEntry *1) {
				entry = item.webkitGetAsEntry();
			}
			
			// Entryをパース
			traverseEntry(entry);
		} else {
			alert("フォルダを1つだけドロップしてください！");
		}
	}
}

/**
 * Entryのパース
 */
function traverseEntry(entry) {
	switch (true) {
		// ファイルの場合
		case (entry.isFile):
			// entryのパスをリストに追加。
			// 改行コードはwindowsの場合は、"\r\n"にする必要あり
			listString += entry.fullPath + "\n";
			break;
		// ディレクトリの場合
		case (entry.isDirectory):
			var reader = entry.createReader();
			// ディレクトリ内容の読み取り
			reader.readEntries(
				// 読み取り成功
				function(results) {
					// 再帰処理
					for (var i = 0, len = results.length; i < len; i++) {
						traverseEntry(results[i]);
					}
				},
				// 読み取り失敗
				function(error) {
					alert("読み込みに失敗しました。");
				}
			);
			break;
	}
}
```


*1: このメソッドは、過去に[「同期でEntryを取得できるべきでしょ」](https://bugs.webkit.org/show_bug.cgi?id=87969)とか、[「非同期の方が便利でしょ」](http://lists.w3.org/Archives/Public/public-whatwg-archive/2012Apr/0102.html)とか、いろいろな議論があったようですが、現在は同期メソッドになっているようです。

##2. テキストファイルを作成しダウンロードさせる

ファイルを保存するには、[File API (File Writer)](http://www.w3.org/TR/file-writer-api/)の[BlobBuilder](http://html5index.org/File%20API%20-%20BlobBuilder.html)や[FileWriter](http://html5index.org/File%20API%20-%20FileWriter.html)を使う方法もあるようですが、今回はBlobを使ったシンプルな実装にしました。

### キーワード
+ [Blob](http://html5index.org/File%20API%20-%20Blob.html)
+ [URL](http://html5index.org/File%20API%20-%20URL.html)
+ `<a>`タグの[download](https://developer.mozilla.org/ja/docs/Web/HTML/Element/a)属性

### ざっくりとした流れ

```javascript:main.js
// listStringはファイルのリストを列挙した文字列
var blob = new Blob([listString], { type:'text/plain' });
// downloadButtonは<a>要素
downloadButton.href = URL.createObjectURL(blob);
downloadButton.download = "fileName.txt";
```

1. フォルダ内のすべてのファイルのパスを列挙した文字列で、Blobオブジェクトを生成。
2. `<a>`要素（ダウンロードボタン）の`href`属性をURL.[createObjectURL](http://www.w3.org/TR/FileAPI/#dfn-createObjectURL)メソッドで設定します。これがダウンロードするファイルのBlob URLになります。
3. `<a>`要素の`download`属性を設定します。これがダウンロードするファイルの名前になります。


##3. オフラインでも利用できるようにする
クライアントサイドJSだけで動いているアプリだということもあり、せっかくなのでオフラインでも使えるようにしました。これには[ApplicationCache](http://html5index.org/Offline%20-%20ApplicationCache.html)を利用します。とても便利な機能ですが、何かを更新する度に、マニフェストファイルの更新（バージョン番号を更新する等でOK）を忘れると、更新した内容が反映されない点を注意する必要があります。


### 参考

+ [アプリケーション キャッシュを初めて使う](http://www.html5rocks.com/ja/tutorials/appcache/beginner/)

### ざっくりとした流れ

+ マニフェストファイルを用意します。今回は[こんな感じ](http://filelistmaker.herokuapp.com/cache.appcache)で、アプリに必要なすべてのアセットをキャッシュするように設定しました。
+ htmlのmanifest属性を指定します。
`<html manifest="cache.appcache">`
+ マニフェストファイルはMIME typeとして`text/cache-manifest`を指定する必要があります。<br>
今回は[こちら](http://null.ly/post/56856650450/heroku-3)のやり方を参考に、Expressフレームワークを使ったNodeアプリとして[heroku](https://www.heroku.com/)上にデプロイしているため、以下のような1行を追加しました。

```javascript:server.js
express.static.mime.define({ 'text/cache-manifest': ['appcache'] });
```

+ クライアント側はほぼ参考サイトのままです。

```javascript:main.js
/**
 * マニフェストファイルが更新されているかをチェック
 */
function checkUpdate() {
	var appCache = window.applicationCache;
	if (appCache) {
		appCache.addEventListener('updateready', 
			function(event) {
				if (appCache.status === appCache.UPDATEREADY) {
					// 古いキャッシュを削除
					appCache.swapCache();
					// 更新を反映するために、ユーザにページをリロードしてもらいます
					if (window.confirm('新しいバージョンのアプリに更新しますか？')) {
						window.location.reload();
					}
				} 
			}
		);	
	}
}
```

##課題：フォルダ内容の読み取りをバックグラウンドで実行する

ディレクトリをパースするタスクを、[ウェブワーカー](http://html5index.org/Web%20Workers%20-%20Worker.html)で行おうとしてみたのですが、まだ実現できていません。これはワーカーに対しEntryオブジェクトをpostMessageで渡そうとすると、DataCloneErrorが発生してしまうためで、目下原因を調査中です！（ちなみにFileオブジェクトはpostMessageでウェブワーカーに渡せます。）

### 参考
+ [ウェブ ワーカーの基本](http://www.html5rocks.com/ja/tutorials/workers/basics/)
+ [Concurrency in JavaScript](http://typedarray.org/concurrency-in-javascript/)
+ [Transferable](https://developer.mozilla.org/en-US/docs/Web/API/Transferable)