FlashAirへのアップロード

第5回では、FlashAirへファイルをアップロードするアプリを作成します。upload.cgiを使用します。


事前準備

1. プロジェクトを作成する

まずは作成するアプリのプロジェクトを作成します。 前回までと同じく、プロジェクトを作成したいディレクトリへ移動し、以下のコマンドを実行します。

> cordova create cordovatutorial5 com.fixstars.flashair.tutorial CordovaTutorial5

また、プロジェクト作成後、browserプラットフォーム及び以下のプラグインの追加を行ってください。

2. 第4回で使用したファイルを追加する

第4回で使用したファイルを以下のようにコピーして追加します。

cordovatutorial5
├─ hooks
├─ platforms
├─ plugins
├─ www
│   ├─ css
│   │   └─ load.css(上書きコピー)
│   ├─ img
│   │   └─ logo.png
│   ├─ js
│   │   ├─ jquery-3.1.1.min.js(上書きコピー)
│   │   ├─ load.js(上書きコピー)
│   │   ├─ tutorial2.js(上書きコピー)
│   │   ├─ tutorial3.js(上書きコピー)
│   │   └─ tutorial4.js(上書きコピー)
│   ├─ index.html(上書きコピー)
│   ├─ tutorial2.html(上書きコピー)
│   ├─ tutorial3.html(上書きコピー)
│   └─ tutorial4.html(上書きコピー)
└─ config.xml

アプリのトップ画面作成

1. index.htmlを編集する

アプリのトップ画面であるindex.htmlを編集します。第4回の36~41行目を、以下のように変更してください。

/www/index.html
<p class="pos-center"><button type="button" onclick="location.href='tutorial2.html'">2.コンテンツリストの取得</button></p>
<p class="pos-center"><button type="button" onclick="location.href='tutorial3.html'">3.コンテンツのダウンロード</button></p>
<p class="pos-center"><button type="button" onclick="location.href='tutorial4.html'">4.サムネイルの表示</button></p>
<p class="pos-center"><button type="button" onclick="location.href='tutorial5.html'">5.FlashAirへのアップロード</button></p>
<script type="text/javascript" src="cordova.js"></script>
<script type="text/javascript" src="js/load.js"></script>
</body>
  • 39行目
    タップされたらtutorial5.htmlの画面へ遷移するボタンを追加しています。FlashAirへのアップロードはtutorial5.htmlの画面で行います。

2. 実行結果

上記の編集が完了したら、ビルドを行い、出来上がったアプリのインストールを行ってください。第4回と比べて、「5.FlashAirへのアップロード」ボタンを追加しています。

Androidデバイスの場合
Androidデバイスのトップ画面
iOSデバイスの場合
iOSデバイスのトップ画面

FlashAirへのアップロード画面の作成

1. tutorial5.htmlを作成する

続いて、FlashAirへのアップロード画面を作成します。tutorial4.htmlとの共通部分が多いので、コピーしたファイルをtutorial5.htmlとして保存して、9~24行目を以下のように変更してください。

/www/tutorial5.html
<title>Tutorial5</title>
<link rel="stylesheet" type="text/css" href="css/load.css">
</head>
<body onload="onLoad()">
<div id="header" class="pos-center">
<div id="head-left">&lt;<br>Back</div>
<h1 id="head-center">Upload</h1>
<div id="head-right"></div>
</div>
<div id="list"></div>
<hr>
<div id="upload">
<h3>Upload the file to the current directory</h3>
<button id="doUpload">Upload!</button>
</div>
<hr>
<script type="text/javascript" src="cordova.js"></script>
<script type="text/javascript" src="js/jquery-3.1.1.min.js"></script>
<script type="text/javascript" src="js/tutorial5.js"></script>
</body>
</html>
</html>
  • 9行目
    画面のタイトルをTutorial5に変更しています。
  • 15行目
    ヘッダーのタイトルをUploadに変更しています。
  • 20-23行目
    FlashAirへアプリ内に格納しているファイルをアップロードするボタン枠を追加しています。 アップロードするファイルについては後述します。
  • 27行目
    ロードするJavaScriptの内、tutorial4.jstutorial5.jsへ変更しています。

2. tutorial5.jsで使用するプラグインを追加する

次はtutorial5.jsの作成に移りますが、その前にアップロード処理時の確認用ダイアログを使えるようにするため、Dialogsプラグインを追加していきます。 追加コマンドは以下の通りです。

> cordova plugin add cordova-plugin-dialogs -save

3. tutorial5.jsを作成する

FlashAirへアップロード画面でロードするtutorial5.jsを作成していきます。tutorial4.jsとの共通部分が多いので、コピーしたファイルを tutorial5.jsとして保存して、まず9~21行目を以下のように変更してください。

/www/js/tutorial5.js
function onDeviceReady(){
  getFileList("");
  $(document).on("click", "a.dir", function(){
    getFileList(this.text);
  });
  $(document).on("click", "a.file", function(){
    downloadFile(this.text, currentPath);
  });
  $("#doUpload").click(function(){
    navigator.notification.confirm("Are you sure you upload 'upload.jpg'?", confirmCallback, "UPLOAD", "YES,NO");
    function confirmCallback(buttonIndex) {
      switch (buttonIndex) {
        // Dialog closed without tapped any buttons.
        case 0:
          break;
        // "YES" tapped
        case 1:
          uploadFile();
          break;
        // "NO" tapped
        case 2:
          break;
      }
    }
  });
  document.addEventListener("backbutton", onBackKeyDown, false);
  $("#head-left").click(function(){
    onBackKeyDown();
  });
}
  • 17-18行目
    「Upload!」ボタンをタップしたときに、まず確認用のダイアログを表示させます。 確認ダイアログの表示にはDialogsプラグインのnavigator.notification.confirmを使用します。 第1引数がメッセージ、第2引数がコールバック関数、第3引数がタイトル、第4引数がボタン名の配列になります。
  • 19-32行目
    コールバック関数の中身です。 今回の場合は「YES」ボタンをタップしたときのみ、uploadFile関数が呼び出されるように設定しています。

続いて以下のアップロード処理について、最後の行(179行目)から追加してください。 尚、コードは上級者向けチュートリアル - FlashAirへのアップロード を元にしていますが、inputタグを使ったファイルアップロードがAndroid 4.4(Kitkat)で使用できない為、マルチパートの構造を自作してPOSTする方法をとっています。

/www/js/tutorial5.js
// Upload the content.
function uploadFile(){
  var uppath = makePath(".");
  var cgi = "http://flashair/upload.cgi";
  var dt = new Date();
  // The range of years for FAT32 is from 1980 to 2107.
  var year = (dt.getFullYear() - 1980) << 9;
  var month = (dt.getMonth() + 1) << 5;
  var date = dt.getDate();
  var hours = dt.getHours() << 11;
  var minites = dt.getMinutes() << 5;
  var seconds = Math.floor(dt.getSeconds() / 2);
  var timestring = "0x" + (year + month + date).toString(16) + (hours + minites + seconds).toString(16);
  var param = cgi + "?WRITEPROTECT=ON&UPDIR=" + uppath + "&FTIME=" + timestring;
  // GET request upload.cgi parameters.
  $.get(param, function(){
    // Create boundary.
    var boundary = "--------------------FlashAir" + year.toString() + month.toString() + date.toString() + hours.toString() + minites.toString() + seconds.toString);
    var localReq = new XMLHttpRequest();
    var preReader = new FileReader();
    var sufReader = new FileReader();
    var localBuffer, faBuffer;
    // GET request upload file (as ArrayBuffer).
    localReq.open("GET", "img/upload.jpg", true);
    localReq.responseType = "arraybuffer";
    // POST request to FlashAir succeeds.
    var faReqSuccess = function(){
      navigator.notification.alert("Upload complete!", function(){
        // Update the content list.
        getFileList(".");
      }, "UPLOAD", "OK");
    };
    // Suffix loading succeeds.
    var sufReaderSuccess = function(){
      // Connect HTTP Body suffix.
      faBuffer = connectBuffer(faBuffer, sufReader.result);
      var faReq = new XMLHttpRequest();
      // Make a POST request to upload a file to FlashAir.
      faReq.open("POST", cgi, true);
      // Set HTTP Headers.
      faReq.setRequestHeader("Content-Type", "multipart/form-data; boundary=" + boundary);
      faReq.onload = faReqSuccess;
      faReq.send(faBuffer);
    };
    // Prefix loading succeeds.
    var preReaderSuccess = function(){
      // Connect HTTP Body prefix and upload file.
      faBuffer = connectBuffer(preReader.result, localBuffer);
      // HTTP Body suffix
      var suffix = "\r\n" + "--" + boundary + "--";
      sufReader.onload = sufReaderSuccess;
      // Load suffix (as ArrayBuffer).
      sufReader.readAsArrayBuffer(new Blob([suffix]));
    };
    // GET request upload file succeeds.
    var localReqSuccess = function(){
      localBuffer = localReq.response;
      // HTTP Body prefix
      var prefix = "--" + boundary + "\r\n" + "Content-Disposition: form-data; name=\"userfile\"; filename=\"upload.jpg\"\r\n" + "Content-Type: image/peg\r\n\r\n";
      preReader.onload = preReaderSuccess;
      // Load prefix (as ArrayBuffer).
      preReader.readAsArrayBuffer(new Blob([prefix]));
    };
    localReq.onload = localReqSuccess;
    localReq.send(null);
  });
  return false;
}
// Add "addBuffer" to "oriBuffer" (as ArrayBuffer).
function connectBuffer(oriBuffer, addBuffer){
  var uint8Array = new Uint8Array(oriBuffer.byteLength + addBuffer.byteLength);
  uint8Array.set(new Uint8Array(oriBuffer), 0);
  uint8Array.set(new Uint8Array(addBuffer), oriBuffer.byteLength);
  return uint8Array.buffer;
}
  • 181-194行目
    まずはupload.cgiのパラメータを確定させ、GETリクエストを行っています。 各パラメータについてはupload.cgiをご確認ください。
  • 202-203,242-243行目
    アプリ内に置いてあるファイル(img/upload.jpg)をGETリクエストしています。responseTypeにはarraybufferを指定して、バイナリデータとして取り扱います。 リクエスト成功時のコールバック関数はlocalReqSuccessです。
  • 205-210行目
    faReqSuccess(220行目)のコールバック関数の中身です。 Dialogsプラグインのnavigator.notification.alertを使用したアラートダイアログを表示します。第1引数がメッセージ、第2引数がアラートダイアログを閉じたときに呼ばれるコールバック関数、第3引数がタイトル、第4引数がボタン名になります。そしてコールバック関数の中で、getFileList(".")を呼び出すことでコンテンツリストが更新され、アップロードしたファイルが確認できるようになります。
  • 212-222行目
    sufReaderSuccess(229行目)のコールバック関数の中身です。upload.cgiを使用して、FlashAirへ「upload.jpg」をPOSTリクエストしています。 リクエスト成功時のコールバック関数はfaReqSuccessです。
  • 224-232行目
    preReaderSuccess(238行目)のコールバック関数の中身です。FileReader.readAsArrayBuffer()を利用して、HTTPボディ部のアップロードファイル本体の後につけるString(suffix)をArrayBufferとして読み出すようにしています。 読み出し成功時のコールバック関数はsufReaderSuccessです。
  • 234-241行目
    localReqSuccess(242行目)のコールバック関数の中身です。FileReader.readAsArrayBuffer()を利用して、HTTPボディ部のアップロードファイル本体の前につけるString(prefix)をArrayBufferとして読み出すようにしています。 読み出し成功時のコールバック関数はpreReaderSuccessです。
  • 248-253行目
    元のArrayBufferに追加したいArrayBufferを追加して返す関数です。

4. tutorial5.jsで指定している画像を追加する

tutorial5.jsで指定したアップロード用の画像を/www/img以下に保存します。

upload.jpg

upload.jpg

5. 実行結果

上記の編集が完了したら、ビルドを行い、アプリのインストールを行います。 アプリインストール後、FlashAirへ無線LAN接続していれば、トップ画面の「5.FlashAirへのアップロード」ボタンをタップしたときに、画面下部にファイルをアップロードするボタンの枠が表示されます。 そして、この中の「Upload!」ボタンをタップすると、表示しているカレントディレクトリに「upload.jpg」ファイルがアップロードされ、コンテンツリストの表示が更新されます。

Androidデバイスの場合
androidデバイスのルートディレクトリの表示画面 1.アップロードボタンをタップする前のコンテンツリスト
androidデバイスのアップロード確認ダイアログ 2.アップロードボタンタップ後の確認ダイアログ
androidデバイスのアップロード完了アラート 3.アップロード完了後のアラートダイアログ
androidデバイスのアップロード完了後の画面 4.アラートダイアログを閉じた後に更新して表示されるコンテンツリスト
iOSデバイスの場合
iOSデバイスのルートディレクトリの表示画面 1.アップロードボタンをタップする前のコンテンツリスト
iOSデバイスのアップロード確認ダイアログ 2.アップロードボタンタップ後の確認ダイアログ
iOSデバイスのアップロード完了アラート 3.アップロード完了後のアラートダイアログ
iOSデバイスのアップロード完了後の画面 4.アラートダイアログを閉じた後に更新して表示されるコンテンツリスト

サンプルコード

リポジトリを見る(GitHub)

  • このサンプルコードはApache License, Version 2.0で提供されています。
  • 「cordova_tutorial_05」ディレクトリは、zipファイルで圧縮してPhoneGap Buildへアップロードすることでビルドできます。 ビルド方法はHelloWorldアプリをビルドするをご確認ください。
  • Cordovaプロジェクトで確認する場合は、「cordova_tutorial_05」ディレクトリ内のデータをプロジェクト内へ上書きコピーしてください。