もっと使おうPhoneGap/Cordova 2.0.0

第8回File APIを使ったiOS/Androidアプリケーション作成その5]

File API紹介の最終回となる今回は、FileTransferオブジェクトを使って、ファイルのダウンロード・アップロードを行う手順を紹介していきます。性質上、Webサーバとファイルを受信するプログラムが別途必要になりますのであらかじめご準備ください。

FileTransferオブジェクトを使ったファイルダウンロード・アップロード

FileTransferオブジェクトを使用して、ファイルをダウンロード・アップロードするアプリケーションを作成してみましょう。

新たに登場するオブジェクトとメソッドは次のとおりです。

  • FileTransferオブジェクト
  • FileTransferErrorオブジェクト
  • FileUploadOptionsオブジェクト
  • FileUploadResultオブジェクト
  • downloadメソッド
  • uploadメソッド

FileTransfer/FileTransferError/FileUploadOptions/ FileUploadResultオブジェクトとは

選択したファイルを外部サーバに送信するサンプルプログラムを紹介する前に、ファイル転送に関係する4種類のオブジェクトについて確認しておきましょう。

FileTransferオブジェクトは、サーバからファイルをダウンロード、またはファイルをサーバにアップロードする際に使用するオブジェクトです。

FileTransferオブジェクトにプロパティはありません。用意されているメソッドは次のとおりです(使い方のfileTransferは、FileTransferオブジェクトを指します⁠⁠。

メソッド名内容
使い方
download指定したサーバからファイルをダウンロードします。
  • ダウンロードしたいファイルのURLをsourceに指定します。encodeURI()メソッドでエンコードされている必要があります。
  • ファイルの保存先をtargetにフルパス表記で指定します。
  • targetに存在しないディレクトリを指定した場合は、ダウンロード成功時に自動的に作成されます。
  • ダウンロードしたいファイルのホストを、あらかじめホワイトリストに定義しておく必要があります。
  • 成功時、successCallbackに、取得したファイルの情報をFileEntryオブジェクトして渡します。
  • 失敗時、errorCallbackに、FileTransferErrorオブジェクトを渡します。
fileTransfer.download(source, target, successCallback, errorCallback);
upload指定したサーバにファイルをアップロードします。
  • アップロードしたいファイルのフルパスをfilePathに指定します。
  • ファイルの送信先URLをserverに指定します。encodeURI()メソッドでエンコードされている必要があります。
  • ファイル名やMIME、パラメータなどの情報をFileUploadOptionsオブジェクトとして定義し、optionsに指定します。
  • すべてのホスト(自己署名の証明書を使用しているホストなど)を信頼する場合は、trustAllHostsにtrueを指定します。デフォルト値はfalseです(執筆時点で、この指定は無視される模様です)
  • 成功時、successCallbackに、FileUploadResultオブジェクトして渡します。
  • 失敗時、errorCallbackに、FileTransferErrorオブジェクトを渡します。
fileTransfer.upload(filePath, server, successCallback, errorCallback, options, trustAllHosts);

FileTransferErrorオブジェクトは、FileTransferオブジェクトでダウンロードまたはアップロードの際にエラーが発生した際に渡されるエラーオブジェクトです。メソッドはありません。

FileTransferErrorオブジェクトのプロパティは次のとおりです。

code
対応するエラーコードを返します
source
ソースのURIを返します
target
ターゲットのURIを返します
http_status
HTTPステータスコードを返します(レスポンスコードがHTTP接続から返されたときのみ有効です)

対応する定数とエラーコードは次のとおりです。

FileTransferError.FILE_NOT_FOUND_ERR
ダウンロード時の第2引数に指定したパスがファイルURLでない場合や、アップロード時の第1引数に指定したファイルが見つからない場合に返ります
FileTransferError.INVALID_URL_ERR
ダウンロード時・アップロード時ともに第2引数に不正なファイルURLを指定した場合に返ります
FileTransferError.CONNECTION_ERR
ネットワーク接続がない場合や、処理完了時のレスポンスコードが200~300の範囲でなかった場合に返ります
リスト1 FileTransferErrorオブジェクトの定数とエラーコード
// cordova-2.0.0.js 2578-2580
FileTransferError.FILE_NOT_FOUND_ERR = 1;
FileTransferError.INVALID_URL_ERR = 2;
FileTransferError.CONNECTION_ERR = 3;

// CDVFileTransfer.h 24-28
enum CDVFileTransferError {
	FILE_NOT_FOUND_ERR = 1,
    INVALID_URL_ERR = 2,
    CONNECTION_ERR = 3
};

// CDVFileTransfer.m 80-103
// ファイルアップロード実装の一部
if (!url) {
    errorCode = INVALID_URL_ERR;
    NSLog(@"File Transfer Error: Invalid server URL %@", server);
} else if(![file isFileURL]) {
    errorCode = FILE_NOT_FOUND_ERR;
    NSLog(@"File Transfer Error: Invalid file path or URL %@", filePath);
} else {
    // check that file is valid
    NSFileManager* fileMgr = [[NSFileManager alloc] init];
    BOOL bIsDirectory = NO;
    BOOL bExists = [fileMgr fileExistsAtPath:[file path] isDirectory:&bIsDirectory];
    if (!bExists || bIsDirectory) {
        errorCode = FILE_NOT_FOUND_ERR;
    } else {
        // file exists, make sure we can get the data
        fileData = [NSData dataWithContentsOfURL:file];
        
        if(!fileData) {
            errorCode =  FILE_NOT_FOUND_ERR;
            NSLog(@"File Transfer Error: Could not read file data %@", filePath);
        }
    }
    [fileMgr release];
}

// CDVFileTransfer.m 224-230
// ファイルダウンロード実装の一部
if (!url) {
    errorCode = INVALID_URL_ERR;
    NSLog(@"File Transfer Error: Invalid server URL %@", sourceUrl);
} else if(![file isFileURL]) {
    errorCode = FILE_NOT_FOUND_ERR;
    NSLog(@"File Transfer Error: Invalid file path or URL %@", filePath);
}

FileTransferオブジェクトのcodeプロパティと、http_statusプロパティは連動していません。たとえばサーバ側で404エラーが返るURLをdownloadメソッドで指定した場合、codeプロパティはFileTransferError.FILE_NOT_FOUND_ERRではなくFileTransferError.CONNECTION_ERRを、http_statusは404の値を取ります。若干分かりづらいので、ご自身の手で後述のサンプルをカスタマイズし、エラーの値を確認してみてください。

FileUploadOptionsは、FileTransferオブジェクトのuploadメソッド時に指定するオプションをまとめたオブジェクトです。メソッドはありません。

FileUploadOptionsのプロパティは次のとおりです。

fileKey
Content-Dispositionヘッダのname属性に指定する名前を指定します。デフォルト値はnullです
fileName
Content-Dispositionヘッダのfilename属性に指定する名前を指定します。デフォルト値はnullです
mimeType
Content-Typeヘッダに指定するMIMEタイプを指定します。デフォルト値はnullです。指定しなかった場合、Content-Typeヘッダは送信されません
params
HTTPリクエストに含めたいデータをkey/value形式で指定します
chunkedMode
チャンク・ストリーミングでファイルを送信したい場合はtrueを指定します(執筆時点で、この指定は無視される模様です)
FileUploadOptionsの初期値を確認(iOS)
FileUploadOptionsの初期値を確認(iOS)
FileUploadOptionsの初期値を確認(Android)
FileUploadOptionsの初期値を確認(Android)

FileUploadResultオブジェクトは、FileTransferオブジェクトのuploadメソッドでファイルアップロードに成功した場合に渡されるオブジェクトです。メソッドはありません。

FileUploadResultオブジェクトのプロパティは次のとおりです。

bytesSent
送信されたバイト数を示します
responseCode
サーバから返されたHTTPレスポンスコードを示します
response
サーバから返されたHTTPレスポンスを示します

iOSの場合、responseにはstringByAddingPercentEscapesUsingEncodingでエンコードされた文字列が格納されます。JSONをやりとりするアプリケーションを作成する場合、そのままではJSON.parseが動作しないので注意しましょう。Androidの場合、エンコードは行われません。

また、現在の最新バージョンであるPhoneGap 2.1.0では、ファイル転送に関係するオブジェクトにおいてFileWriterオブジェクトやFileReaderオブジェクトにあるような「ファイルのダウン/アップロード中」「ファイルの送信を中止させる」に相当するイベント・メソッドが用意されていません。そのため、ダウンロード・アップロードの進捗率を画面に表示するといったことは難しいでしょう。

ダウンロード・アップロードの進捗イベントを捕捉するprogressイベントや送信・受信を中止するabortメソッドについては、PhoneGap 2.2.0にてサポートされる予定です。これらの動向について詳しく確認したい方は、Apache JIRAの[#CB-622]FileTransfer interface should provide progress monitoringをチェックしておきましょう。

ファイル転送に関係するオブジェクトをまとめると、次のようになります。

オブジェクト名内容
FileTransferファイルのダウンロード・アップロードを行う。
  • アップロード成功時に、FileUploadResultオブジェクトを返す
  • ダウンロード成功時に、ダウンロードしたファイルのFileEntryオブジェクトを返す
  • ダウンロード/アップロード失敗時にFileTransferErrorオブジェクトを返す
FileTransferErrorFileTransferオブジェクトのdownload/uploadメソッド使用時、失敗した際に返るエラーオブジェクト
FileUploadOptionsFileTransferオブジェクトのuploadメソッド使用時、アップロード時のHTTPヘッダに付加したい情報を定義するためのオブジェクト
FileUploadResultFileTransferオブジェクトのuploadメソッド使用時、成功した際の送信バイト数/サーバから返されたレスポンスをまとめたオブジェクト

ファイルのダウンロード・アップロード(iOS/Android)

ファイルのダウンロード・アップロードを行うアプリケーションを作成します。要件は次のとおりです。

  • ダウンロードするファイルは、gihyo.jpのロゴ。PERSISTENTファイルシステムに保存
  • PERSISTENTファイルシステムからファイルを選択し、アップロード
  • アップロード先には、ファイルを受信してカレントディレクトリに保存するプログラムを指定する。PHPで実装
リスト2 ソースコード: File API Test (6)
  1: <!DOCTYPE html>
  2: <html>
  3:   <head>
  4:     <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
  5:     <meta name="viewport" content="user-scalable=no, initial-scale=1, maximum-scale=1, minimum-scale=1, width=device-width;" />
  6:     <title>File API Test (6)</title>
  7:   </head>
  8:   <body>
  9:     <div class="app">
 10:       <h1>File API Test</h1>
 11:       <p>
 12:         <input id="download" type="button" value="download gihyo logo">
 13:       </p>
 14:       <ul id="fileList"></ul>
 15:     </div>
 16:     <script type="text/javascript" src="cordova-2.0.0.js"></script>
 17:     <script type="text/javascript">
 18:     var directoryEntry;
 19: 
 20:     document.addEventListener('deviceready', init, false);
 21:     function init() {
 22:       window.requestFileSystem(LocalFileSystem.PERSISTENT, 0, getFilesFromDirectory, fail);
 23:       document.getElementById('download').addEventListener('click', downloadFile, false);
 24:     }
 25: 
 26:     function getFilesFromDirectory(fileSystem) {
 27:       directoryEntry = fileSystem.root;
 28:       var directoryReader = directoryEntry.createReader();
 29:       directoryReader.readEntries(putFileName, fail);
 30:     }
 31: 
 32:     function putFileName(entries) {
 33:       for ( index = 0; index < entries.length; index++ ) {
 34:         if ( entries[index].isFile ) {
 35:           var listItem = document.createElement('li');
 36:           listItem.textContent = entries[index].name;
 37:           listItem.title = entries[index].fullPath;
 38:           listItem.addEventListener('click', uploadFile, false);
 39:           document.getElementById('fileList').appendChild(listItem);
 40:         }
 41:       }
 42:     }
 43: 
 44:     function downloadFile() {
 45:       // FileTransferオブジェクトを作成
 46:       var fileTransfer = new FileTransfer();
 47: 
 48:       // ダウンロード先のURIをencodeURIでエンコード
 49:       var uri = encodeURI('http://image.gihyo.co.jp/assets/templates/gihyojp2007/image/gihyojp_logo.png');
 50: 
 51:       // 保存するファイルの名前
 52:       var fileName = 'gihyojp_logo.png';
 53: 
 54:       // 保存先をフルパスで指定
 55:       var path = directoryEntry.fullPath + '/' + fileName ;
 56: 
 57:       // ファイルのダウンロードを実行
 58:       fileTransfer.download(uri, path, downloadSuccess, downloadFail);
 59:     }
 60: 
 61:     function downloadSuccess(fileEntry) {
 62:       alert('ファイルを' + fileEntry.fullPath + 'に保存しました。');
 63:       location.reload();
 64:     }
 65: 
 66:     function downloadFail(fileTransferError) {
 67:       var error = '';
 68:       
 69:       switch(fileTransferError.code) {
 70:         case FileTransferError.FILE_NOT_FOUND_ERR:
 71:           error = '第2引数にファイルURLが指定されていません';
 72:           break;
 73:         case FileTransferError.INVALID_URL_ERR:
 74:           error = '不正なファイルパスが第2引数で指定されています';
 75:           break;
 76:         case FileTransferError.CONNECTION_ERR:
 77:           error = '接続エラー';
 78:           break;
 79:         default: 
 80:           error = '未定義のエラー';
 81:           break;
 82:       }
 83:       
 84:       alert(error + '\nhttp_status: ' + fileTransferError.http_status);
 85:     }
 86: 
 87:     function uploadFile(event) {
 88:       // FileTransferオブジェクトを作成
 89:       var fileTransfer = new FileTransfer();
 90: 
 91:       // アップロード先URIをencodeURIでエンコード
 92:       var uri = encodeURI('http://(ホスト名)/receive.php');
 93: 
 94:       // FileUploadOptionsオブジェクトを作成し、送信時の情報を指定
 95:       var uploadOptions = new FileUploadOptions();
 96:       uploadOptions.fileKey = 'file';
 97:       uploadOptions.fileName = event.target.textContent;
 98: 
 99:       // ファイルのアップロードを実行
100:       fileTransfer.upload(event.target.title, uri, uploadSuccess, uploadFail, uploadOptions);
101:     }
102: 
103:     function uploadSuccess(uploadResult) {
104:       var result;
105: 
106:       // アップロード先(receive.php)から返るJSONをパース
107:       if ( 'Android' === device.platform ) {
108:         result = JSON.parse(uploadResult.response);
109:       } else { 
110:         // iOSの場合はエンコードされた文字列になるため、decodeURIでデコード
111:         result = JSON.parse(decodeURI(uploadResult.response));
112:       }
113:  
114:       if ( 0 === result.errorCode ) {
115:         alert('ファイルのアップロードに成功しました\nbytesSent: ' + uploadResult.bytesSent + '\nresponseCode: ' + uploadResult.responseCode);
116:       } else {
117:         alert('ファイルのアップロードに失敗しました\n' + result.message + '\nbytesSent: ' + uploadResult.bytesSent + '\nresponseCode: ' + uploadResult.responseCode);
118:       }
119:     }
120: 
121:     function uploadFail(fileTransferError) {
122:       var error = '';
123:       
124:       switch(fileTransferError.code) {
125:         case FileTransferError.FILE_NOT_FOUND_ERR:
126:           error = '第1引数に指定したファイルが見つかりません';
127:           break;
128:         case FileTransferError.INVALID_URL_ERR:
129:           error = '不正なURIが第2引数で指定されています';
130:           break;
131:         case FileTransferError.CONNECTION_ERR:
132:           error = '接続エラー';
133:           break;
134:         default: 
135:           error = '未定義のエラー';
136:           break;
137:       }
138:       
139:       alert(error + '\nhttp_status: ' + fileTransferError.http_status);
140:     }
141: 
142:     function fail(error) {
143:       alert('エラーが発生しました。エラーコード: ' + error.code);
144:     }
145:     </script>
146:   </body>
147: </html>
リスト3 ファイル受信用のプログラム receive.php
<?php

// ファイルを受信し、JSONで結果を出力
// { errorCode : (エラーコード) , message : (メッセージ) }

// 定義したエラーコードは次のとおり
// -1: ファイルがアップロードされていない
// 0 : エラーなし
// 1 : move_uploaded_fileに失敗
// 2 : ファイル名の制限により失敗

// ファイルがアップロードされているか
if (!is_uploaded_file($_FILES['file']['tmp_name'])) {
  echo json_encode(array('errorCode' => -1, 'message' => 'ファイルがアップロードされていません'));
  exit;
}

// 拡張子が jpg, png, txt のみのファイルを受け付ける
if ( !preg_match('/\.jpg$|\.png$|\.txt$/i', $_FILES['file']['name']) ) {
  echo json_encode(array('errorCode' => 2, 'message' => '受信できるファイルの拡張子はjpg/png/txtの3種類のみです'));
  exit;
}

// アップロードされたファイルを、カレントディレクトリに移動
if (move_uploaded_file($_FILES['file']['tmp_name'], './' . str_replace('/', '', $_FILES['file']['name']))) {
  echo json_encode(array('errorCode' => 0, 'message' => 'エラーなし'));
  exit;
}
  
// エラー処理
echo json_encode(array('errorCode' => 1, 'message' => 'move_upload_fileに失敗しました。パーミッションの設定を確認してください'));
exit;

?>

前述のとおり、ダウンロード先のURLは、あらかじめCordova.plistのExternalHostsに追記を行う必要があります。ホワイトリストに定義されていないホストからファイルをダウンロードしようとした場合、FileTransferErrorオブジェクトのcodeプロパティにFileTransferError.CONNECTION_ERRが、http_statusプロパティに401が格納されます。

まずはiOSで実行してみましょう。アプリケーションを起動すると、PERSISTENTファイルシステムにファイルが格納されている場合ファイルの一覧が表示されます。また、画像をダウンロードするボタンが配置されています。

起動画面
起動画面

[download gihyo logo]ボタンをクリックすると、gihyo.jpのロゴ画像をダウンロードし、PERSISTENTファイルシステムに保存します。

ファイルをダウンロードすると、保存先のパスが表示される
ファイルをダウンロードすると、保存先のパスが表示される
PERSISTENTファイルシステムに画像ファイルが保存された
PERSISTENTファイルシステムに画像ファイルが保存された

列挙されているファイル名をクリックすると、そのファイルをreceive.phpにアップロードします。

ダウンロードしたgihyojp_logo.pngをアップロード
ダウンロードしたgihyojp_logo.pngをアップロード
receive.phpを配置したディレクトリに、ファイルがアップロードできていることが確認できた
receive.phpを配置したディレクトリに、ファイルがアップロードできていることが確認できた

PHPではファイルを受信し、move_uploaded_fileを使ってカレントディレクトリにファイルを移動しています。そのため、設置するディレクトリに書き込み権限を付加する必要があります。

JSONで結果をやり取りし、エラーコードに対応するメッセージをアラートで表示
JSONで結果をやり取りし、エラーコードに対応するメッセージをアラートで表示

存在しないサーバURLや、アップロードしたいファイルパスを指定した場合はFileTransferErrorオブジェクトが返ります。

FileTransferErrorオブジェクトの結果を利用して、エラー表示
FileTransferErrorオブジェクトの結果を利用して、エラー表示

続いて、Androidで実行してみます。

起動画面
起動画面
gihyo.jpのロゴ画像をPERSISTENTファイルシステムに保存
gihyo.jpのロゴ画像をPERSISTENTファイルシステムに保存
ファイル名をクリックしてファイルをアップロード
ファイル名をクリックしてファイルをアップロード
問題無くアップロードできていることを確認
問題無くアップロードできていることを確認

このサンプルコードでは、前回のPERSISTENTファイルシステムのファイル一覧を表示。ファイル名をクリックして、ファイル内のデータを表示するサンプル」をベースに、ファイルのダウンロードとアップロードを行います。行っている処理の流れは次のとおりです。

  1. PERSISTENTファイルシステムに格納されているファイル一覧を表示
  2. [download gihyo logo]ボタンをクリックすると、指定したURIから画像ファイルをダウンロードする
  3. ファイル名をクリックすると、そのファイルを指定したURIにアップロードする

まずはファイルのダウンロードを行っているコードから確認してみましょう。

44: function downloadFile() {
45:   // FileTransferオブジェクトを作成
46:   var fileTransfer = new FileTransfer();
47: 
48:   // ダウンロード先のURIをencodeURIでエンコード
49:   var uri = encodeURI('http://image.gihyo.co.jp/assets/templates/gihyojp2007/image/gihyojp_logo.png');
50: 
51:   // 保存するファイルの名前
52:   var fileName = 'gihyojp_logo.png';
53: 
54:   // 保存先をフルパスで指定
55:   var path = directoryEntry.fullPath + '/' + fileName ;
56: 
57:   // ファイルのダウンロードを実行
58:   fileTransfer.download(uri, path, downloadSuccess, downloadFail);
59: }

46行目でFileTransferオブジェクトをあたらしく作成します。ダウンロードしたいURIをencodeURIメソッドを使ってエンコードし、ファイル名を決めておきます。ファイルのダウンロード先はファイル名を含めたフルパスで指定する必要があります。55行目でdirectoryEntryオブジェクトのfullPathプロパティと、52行目で宣言したfileName変数を連結します。

58行目でファイルのダウンロードを行います。あらかじめ、PhoneGapプロジェクトにおいてダウンロード先のホストをホワイトリスト(ExternalHosts)に追加しておく必要があります。本連載の第3回目にてiOS/Android環境下におけるExternalHostsの追記方法を紹介していますので、併せてご参照ください。

ダウンロードに成功した場合は、第3引数に指定した関数にダウンロードしたファイルのFileEntryオブジェクトが渡されます。

61: function downloadSuccess(fileEntry) {
62:  alert('ファイルを' + fileEntry.fullPath + 'に保存しました。');
63:  location.reload();
64: }

fileEntryオブジェクトのfullPathプロパティに保存したファイルのフルパスが格納されていますので、アラートで表示し、画面を再描画します。

ダウンロードに失敗した場合は、第4引数に指定した関数にFileTransferErrorオブジェクトが渡されます。

66:  function downloadFail(fileTransferError) {
67:    var error = '';
68:    
69:    switch(fileTransferError.code) {
70:      case FileTransferError.FILE_NOT_FOUND_ERR:
71:        error = '第2引数にファイルURLが指定されていません';
72:        break;
73:      case FileTransferError.INVALID_URL_ERR:
74:        error = '不正なファイルパスが第2引数で指定されています';
75:        break;
76:      case FileTransferError.CONNECTION_ERR:
77:        error = '接続エラー';
78:        break;
79:      default: 
80:        error = '未定義のエラー';
81:        break;
82:    }
83:    
84:    alert(error + '\nhttp_status: ' + fileTransferError.http_status);
85:  }

続いてアップロードを行っているコードを確認してみましょう。

 87: function uploadFile(event) {
 88:   // FileTransferオブジェクトを作成
 89:   var fileTransfer = new FileTransfer();
 90: 
 91:   // アップロード先URIをencodeURIでエンコード
 92:   var uri = encodeURI('http://(ホスト名)/receive.php');
 93: 
 94:   // FileUploadOptionsオブジェクトを作成し、送信時の情報を指定
 95:   var uploadOptions = new FileUploadOptions();
 96:   uploadOptions.fileKey = 'file';
 97:   uploadOptions.fileName = event.target.textContent;
 98: 
 99:   // ファイルのアップロードを実行
100:   fileTransfer.upload(event.target.title, uri, uploadSuccess, uploadFail, uploadOptions);
101: }

FileTransferオブジェクトを作成し、アップロード先URIをencodeURIでエンコードします。95~97行目でFileUploadOptionsオブジェクトを作成し、ファイル送信時の情報を指定します。fileKeyにfileを、fileNameに<li>要素のtextContentプロパティを指定しました。

たとえばファイル一覧においてgihyojp_logo.pngをクリックしてアップロードを行った場合、アップロード先には次の情報が送信されます。

Content-Disposition: form-data; name="file"; filename="gihyojp_logo.png"

100行目でファイルのアップロードを行います。アップロード先のホストは、ホワイトリスト(ExternalHosts)に追記する必要はありません。

アップロードに成功した場合は、第3引数に指定した関数にアップロードの結果を格納したFileUploadResultオブジェクトが渡されます。

103: function uploadSuccess(uploadResult) {
104:   var result;
105: 
106:   // アップロード先(receive.php)から返るJSONをパース
107:   if ( 'Android' === device.platform ) {
108:     result = JSON.parse(uploadResult.response);
109:   } else { 
110:     // iOSの場合はエンコードされた文字列になるため、decodeURIでデコード
111:     result = JSON.parse(decodeURI(uploadResult.response));
112:   }
113: 
114:   if ( 0 === result.errorCode ) {
115:     alert('ファイルのアップロードに成功しました\nbytesSent: ' + uploadResult.bytesSent + '\nresponseCode: ' + uploadResult.responseCode);
116:   } else {
117:     alert('ファイルのアップロードに失敗しました\n' + result.message + '\nbytesSent: ' + uploadResult.bytesSent + '\nresponseCode: ' + uploadResult.responseCode);
118:   }
119: }

receive.phpからはJSON形式の文字列で結果が返るようになっています。FileUploadResultオブジェクトのresponseプロパティに、サーバから受信したデータが格納されますので、このデータをJSON.parseでパースします。

繰り返しになりますが、iOSの場合、responseにはstringByAddingPercentEscapesUsingEncodingでエンコードされた文字列が格納されます。そこでDevice APIをもちいて、Androidの場合は返り値をそのまま、iOSの場合はいったんdecodeURIでデコードした上でパースを行うようにしています。

JSONで渡されたエラーコードを読み取り、結果をアラートで表示します。

アップロードに失敗した場合は、第4引数に指定した関数にFileTransferErrorオブジェクトが渡されます。

121: function uploadFail(fileTransferError) {
122:   var error = '';
123:   
124:   switch(fileTransferError.code) {
125:     case FileTransferError.FILE_NOT_FOUND_ERR:
126:       error = '第1引数に指定したファイルが見つかりません';
127:       break;
128:     case FileTransferError.INVALID_URL_ERR:
129:       error = '不正なURIが第2引数で指定されています';
130:       break;
131:     case FileTransferError.CONNECTION_ERR:
132:       error = '接続エラー';
133:       break;
134:     default: 
135:       error = '未定義のエラー';
136:       break;
137:   }
138:   
139:   alert(error + '\nhttp_status: ' + fileTransferError.http_status);
140: }

FileTransferErrorオブジェクトのエラー内容ついて、ダウンロード時・アップロード時においてそれぞれエラーの内容が区別されていることに注意してください。サンプルコードを改変し、⁠実際には存在しないサーバURL」⁠実際には存在しないファイルパス」⁠サンドボックス外のファイルパス」⁠間違ったファイルパス表記」などを指定してみて、実際のエラー表示を確かめておくことをおすすめします。

また、作成するアプリケーション上でFile APIのdownloadメソッドを取り扱う際は注意してください。ExternalHostsですべてのホストからのリソース取得を許可する「*」を指定し、任意の場所にファイルを保存できるようなユーザインタフェースの場合、重大なセキュリティホールになり得ます。アプリケーションの動作確認時でも、ExternalHostsには信頼できるホスト名のみを記述しておくべきです。


以上、5回にわたってFile APIについての紹介をしてきました。ローカルデバイス内のファイル操作や、リモートから・リモートへのファイル転送が要求される場面は多岐に渡ります。ファイル転送中のイベントハンドラが現時点で未サポートなのは痛いところですが、JavaScriptのみで各スマートフォンOSのファイルシステムを操作できるのは魅力的です。

メソッドの返り値や挙動など、公式ドキュメントと併せてW3Cの仕様書File API, File API: Directories and SystemやPhoneGap(Cordova)の実装コードを見ながら開発を行わないと思いがけないところでハマる可能性もあります。本連載がPhoneGap開発の助けになれば幸いです。

おすすめ記事

記事・ニュース一覧