1週間でFlexファイラを作ろうプロジェクト 2日目

昨日に引き続き、2日目。今日の成果はこのあたり (ソースコード)に転がってます。(アップロード機能が無効になっており、全部Access Deniedになるので注意)

今日はダウンロード機能とアップロード機能を作成。それから見た目を多少修正。

サーバー側

ダウンロードとアップロードに対応するコードを追加。アップロードの時は、成功したらディレクトリ一覧を返すようにしてみました。クライアント側からリロードを要求しなくても、新しいディレクトリ一覧を取得できます。

  def command_upload(params)
    # return query_error("Access denied")  # ←アップロード機能を無効化
    dir = pathCheck( params["path"][0] )
    if dir.nil?
      return query_error("Access denied")
    end
    stream = params["file"][0]
    File.open("#{dir}/#{stream.original_filename}", "wb") {|file|
      file << stream.read
    }
    document = REXML::Document.new
    return command_list(params)
  rescue
    return query_error($!, $!.backtrace)
  end

  def command_download(params)
    path = pathCheck( params["path"][0] )
    if path.nil?
      return query_error("Access denied")
    end
    return IO.read(path)
  end

クライアント側

mxmlでは、テキストエリアの大きさを変更。

ActionScriptでは、ダウンロードとアップロードに対応するコードを追加。upload*()メソッドとdownload*()メソッド。だんだん長くなってまいりました。

// vim:syntax=javascript

import mx.controls.Alert;
import mx.rpc.events.*;
import mx.rpc.http.HTTPService;
import mx.utils.ObjectUtil;
import flash.net.FileReference;
import mx.collections.ArrayCollection;
import mx.utils.StringUtil;

private var _currentPath:String;
private var _uploadFile:FileReference;
private var _downloadFile:FileReference;

private function init():void
{
        sendRequest("/");

        _uploadFile = new FileReference();
        _uploadFile.addEventListener(Event.SELECT, uploadFileSelected);
        _uploadFile.addEventListener(Event.COMPLETE, uploadFileComplete);
        _uploadFile.addEventListener(DataEvent.UPLOAD_COMPLETE_DATA, uploadFileCompleteAndResponse);
        _uploadFile.addEventListener(IOErrorEvent.IO_ERROR, uploadIOError);
        _uploadFile.addEventListener(SecurityErrorEvent.SECURITY_ERROR, uploadSecurityError);

        _downloadFile = new FileReference();
        _downloadFile.addEventListener(Event.SELECT, downloadFileSelected);
        _downloadFile.addEventListener(Event.COMPLETE, downloadFileComplete);
        _downloadFile.addEventListener(IOErrorEvent.IO_ERROR, downloadIOError);
        _downloadFile.addEventListener(SecurityErrorEvent.SECURITY_ERROR, downloadSecurityError);
}

private function sendRequest(path:String):void
{
        fileService.method = "GET";
        fileService.request.command = "list";
        fileService.request.path = path;
        fileService.send();
}

private function refreshFileList(entries:Object):void
{
        grid.dataProvider = entries;
}

private function onResult(e:ResultEvent):void
{
        refreshFileList(fileService.lastResult.DirectoryEntryList.DirectoryEntries.entry);
        _currentPath = fileService.lastResult.DirectoryEntryList.path;
        pathInput.text = _currentPath;
}

private function onFault(e:FaultEvent):void
{
        Alert.show(e.fault.faultString);
}

private function sizeSortCompareFunction(obj1:Object, obj2:Object):int
{
        var num1:Number = obj1.size;
        var num2:Number = obj2.size;
        if( isNaN(num1) && isNaN(num2) ) {
                return 0;
        } else if(  isNaN(num1) && !isNaN(num2) ) {
                return 1;
        } else if( !isNaN(num1) &&  isNaN(num2) ) {
                return -1;
        } else if( num1 < num2 ) {
                return 1;
        } else if( num1 > num2 ) {
                return -1;
        }
        return 0;
}

private function onDoubleClickData(event:MouseEvent):void
{
        var item:Object = event.currentTarget.selectedItem;
        if( item.type == "directory" ) {

                area.text = "list: " + _currentPath + "/" + item.name;
                sendRequest( _currentPath + "/" + item.name );

        } else {

                area.text = "download: " + _currentPath + "/" + item.name;

                var req:URLRequest = new URLRequest(fileService.url);
                req.method = URLRequestMethod.GET;
                var cmd:URLVariables = new URLVariables();
                cmd.command = "download";
                cmd.path = _currentPath + "/" + item.name;
                req.data = cmd;
                _downloadFile.download(req, item.name);

        }
}

private function onUpload(event:MouseEvent):void
{
        _uploadFile.browse();
}

private function uploadFileSelected(event:Event):void
{
        var file:FileReference = FileReference(event.target);
        var req:URLRequest = new URLRequest(fileService.url);
        req.method = URLRequestMethod.GET;
        var cmd:URLVariables = new URLVariables();
        cmd.command = "upload";
        cmd.path = _currentPath;
        req.data = cmd;
        file.upload(req, "file");
}

private function uploadFileComplete(event:Event):void
{
}

private function uploadIOError(event:IOErrorEvent):void
{
        Alert.show("Upload failed: " + event.target.name + ":\n" + event.text );
}

private function uploadSecurityError(event:SecurityErrorEvent):void
{
        Alert.show("Upload failed: " + event.target.name + ":\n" + event.text );
}

private function uploadFileCompleteAndResponse(event:DataEvent):void
{
        var xml:XML = XML(event.text.toString());
        if( xml.name().localName == "QueryError" ) {
                Alert.show("Upload failed: " + event.target.name + ":\n" + xml.Message );
        } else {
                Alert.show(event.target.name + " is successfully uploaded.");
                refreshFileList(xml.DirectoryEntries.entry);
        }
}


private function downloadFileSelected(event:Event):void
{
}

private function downloadFileComplete(event:Event):void
{
        //Alert.show(event.target.name + " is successfully downloaded.");
}

private function downloadIOError(event:IOErrorEvent):void
{
        Alert.show("Download failed: " + event.target.name + ":\n" + event.text );
}

private function downloadSecurityError(event:SecurityErrorEvent):void
{
        Alert.show("Download failed: " + event.target.name + ":\n" + event.text );
}


ダウンロード/アップロード周りの要は、FileReferenceクラス。

ダウンロードはFileReferenceクラスのdownload()メソッドを使ってカンタンにできます。必要な情報は、ダウンロード元URL、ダウンロード元に渡すパラメータ、パラメータをGETで渡すのかPOSTで渡すのか、ダウンロードダイアログで使われるデフォルトのファイル名。ダウンロード周りのコードを抜き出すと、↓こんな感じです。

// fileService.urlはダウンロード元のURL
var req:URLRequest = new URLRequest(fileService.url);

// ダウンロード元にGETで ?command=download&path={_currentPath + "/" + item.name} を渡す
req.method = URLRequestMethod.GET;
var cmd:URLVariables = new URLVariables();
cmd.command = "download";
cmd.path = _currentPath + "/" + item.name;
req.data = cmd;

// ダウンロード開始
_downloadFile.download(req, item.name);


ダウンロードが終わると、init()メソッドの中で登録しておいたdownloadFileComplete()メソッドが呼ばれます。ちなみに、init()メソッドはmxmlの↓ここの部分で登録。

<mx:Application xmlns:mx="http://www.adobe.com/2006/mxml" creationComplete="init()">

アップロードは、まずFileReferenceクラスのbrowse()を呼ぶと、OSのファイル選択ダイアログが表示されます。ユーザーがファイルを選択すると、init()メソッドの中で登録しておいたuploadFileSelected()メソッドが呼ばれます。続いてuploadFileSelected()の中で、ダウンロードのときと同じようにURLRequestを作成し、FileReferenceのupload()メソッドでファイルのアップロードを開始しています。


アップロードが終わると、ダウンロードの場合と同じく、init()メソッドの中で登録しておいてuploadFileComplete()メソッドが呼ばれるのですが、リファレンスによれば、uploadFileComplete()が呼ばれるのはサーバーからステータスコード200を受け取った時点で、まだデータは受け取れていません。サーバー側で作成したファイル一覧を受け取れるのは、uploadFileCompleteAndResponse()の段階です。
というわけで、uploadFileCompleteAndResponse()でサーバーからファイル一覧を受け取り、表に反映したら、アップロード処理完了。



デバッグ

Flex SDKに入っているfdbコマンドで、swfのデバッグができることが判明。うーむ。


デバッグするためには、まずコンパイルの段階で-debug=trueオプションを付けてコンパイルしておきます。

$ mxmlc -debug=true flexfile.mxml


それから、デバッグ機能付きFlashPlayerが必要です。ブラウザのプラグインタイプのものはAdobe Flash Player - DownloadsAdobe Flash Player Support Center - Adobeから、スタンドアロンのものはFlex SDKのplayer/debug/ディレクトリに入ってます

デバッグ方法1 fdbコマンドでswfを指定

$ fdb http://localhost:48002/flexfile.swf

こうするとブラウザが起動して、ターミナルでswfのデバッグが始まります。localhost:48002は、私がローカルにたてているWebサーバーです。CGIと連携するFlexアプリケーションを作るときは、もはやローカルWebサーバーは必須。WEBrickがお手軽ですが、私はLighttpdを入れてます。

デバッグ方法2 リモートデバッグ

まずオプションなしでfdbを起動しておきます。

$ fdb

続いて、ブラウザかスタンドアロンのFlashPlayerでswfを開きます。なにやらダイアログが出てくるので、「ローカルホスト」を選択してOK。起動しておいたfdbでデバッグが始まります。