IFilePromise インターフェイスの実装

Adobe AIR 2 以降

URLFilePromise オブジェクトを使用してアクセスできないリソースにファイルプロミスを提供するために、IFilePromise インターフェイスをカスタムクラスに実装できます。IFilePromise インターフェイスは、ファイルプロミスがドロップされたときにファイルに書き込まれるデータにアクセスするために AIR ランタイムで使用するメソッドおよびプロパティを定義します。

IFilePromise 実装は、ファイルプロミスにデータを提供する別のオブジェクトを AIR ランタイムに渡します。このオブジェクトは IDataInput インターフェイスを実装する必要があります。AIR ランタイムでは、これを使用してデータを読み取ります。例えば、IFilePromise を実装する URLFilePromise クラスでは、URLStream オブジェクトをデータプロバイダーとして使用します。

AIR では、データを同期でも非同期でも読み取ることができます。IFilePromise 実装では、 isAsync プロパティで対応する値を返すことにより、どのアクセスモードがサポートされているかを報告します。非同期データアクセスが提供されている場合、データプロバイダーオブジェクトは IEventDispatcher インターフェイスを実装し、 open progress complete などの必要なイベントを送出しなければなりません。

ファイルプロミスのデータプロバイダーとして使用できるのは、カスタムクラス、または次のいずれかのビルトインクラスです。

  • ByteArray(同期)

  • FileStream(同期または非同期)

  • Socket(非同期)

  • URLStream(非同期)

IFilePromise インターフェイスを実装するには、次の関数およびプロパティにコードを指定する必要があります。

  • open():IDataInput — プロミスファイルのデータの読み取り元であるデータプロバイダーオブジェクトを返します。このオブジェクトは、IDataInput インターフェイスを実装する必要があります。データが非同期で提供されている場合、オブジェクトでは IEventDispatcher インターフェイスも実装し、必要なイベントを送出する必要があります( ファイルプロミスでの非同期データプロバイダーの使用 を参照)。

  • get relativePath():String — 作成されたファイルのパスを、ファイル名も含めて指定します。パスは、ユーザーがドラッグ&ドロップ操作で選択したドロップ位置と相対的に解決されます。ホストオペレーティングシステムに適切な区切り文字がパスで使用されていることを確認するには、ディレクトリを含むパスを指定するときに File.separator 定数を使用します。setter 関数を追加するか、コンストラクターパラメーターを使用すると、実行時にパスを設定できます。

  • get isAsync():Boolean — データプロバイダーオブジェクトが非同期、同期のどちらでデータを提供するかを AIR ランタイムに通知します。

  • close():void — データが完全に読み込まれたとき(または、エラーにより読み取りを続行できない場合)に、ランタイムによって呼び出されます。この関数を使用するとリソースをクリーンアップできます。

  • reportError( e:ErrorEvent ):void — データの読み取りエラーが発生したときに、ランタイムによって呼び出されます。

ファイルプロミスを含むドラッグ&ドロップ操作中に、IFilePromise のすべてのメソッドがランタイムによって呼び出されます。通常は、アプリケーションロジックがこれらのメソッドを直接呼び出すことはありません。

ファイルプロミスでの同期データプロバイダーの使用

IFilePromise インターフェイスを実装する最も簡単な方法は、ByteArray や同期 FileStream など、同期のデータプロバイダーオブジェクトを使用することです。次の例では、ByteArray オブジェクトが作成され、データが指定されて、 open() メソッドを呼び出したときに返されます。

package 
{ 
    import flash.desktop.IFilePromise; 
    import flash.events.ErrorEvent; 
    import flash.utils.ByteArray; 
    import flash.utils.IDataInput; 
     
    public class SynchronousFilePromise implements IFilePromise 
    { 
        private const fileSize:int = 5000; //size of file data 
        private var filePath:String = "SynchronousFile.txt"; 
         
        public function get relativePath():String 
        { 
            return filePath; 
        } 
         
        public function get isAsync():Boolean 
        { 
            return false; 
        } 
         
        public function open():IDataInput 
        { 
            var fileContents:ByteArray = new ByteArray(); 
             
            //Create some arbitrary data for the file 
            for( var i:int = 0; i < fileSize; i++ ) 
            { 
                fileContents.writeUTFBytes( 'S' ); 
            } 
             
            //Important: the ByteArray is read from the current position             
            fileContents.position = 0; 
            return fileContents; 
        } 
         
        public function close():void 
        { 
            //Nothing needs to be closed in this case. 
        } 
         
        public function reportError(e:ErrorEvent):void 
        { 
            trace("Something went wrong: " + e.errorID + " - " + e.type + ", " + e.text ); 
        } 
    } 
}

実際には、同期ファイルプロミスの用途は限られています。データ量が少ない場合は、一時ディレクトリに同じくらい簡単にファイルを作成し、通常のファイルリスト配列をドラッグ&ドロップクリップボードに追加できます。一方で、データ量が多い場合や、データを作成するとコンピューター処理上の負荷が高い場合は、同期プロセスに長時間かかります。同期プロセスが長引くと、UI の更新がかなり長時間に渡ってブロックされ、アプリケーションが応答していないように見える場合があります。この問題を回避するために、タイマーによって制御される非同期データプロバイダーを作成できます。

ファイルプロミスでの非同期データプロバイダーの使用

非同期データプロバイダーオブジェクトを使用するときは、IFilePromise の isAsync プロパティを true に設定し、 open() メソッドによって返されるオブジェクトに IEventDispatcher インターフェイスを実装する必要があります。様々なビルトインオブジェクトをデータプロバイダーとして使用できるように、ランタイムは複数の代替イベントを監視します。例えば、 progress イベントは FileStream オブジェクトおよび URLStream オブジェクトによって送出され、 socketData イベントは Socket オブジェクトによって送出されます。ランタイムは、これらすべてのオブジェクトから適切なイベントを監視します。

次の各イベントは、データプロバイダーオブジェクトからのデータの読み取りプロセスを制御します。

  • Event.OPEN — データソースの準備が完了していることをランタイムに通知します。

  • ProgressEvent.PROGRESS — データが使用可能であることをランタイムに通知します。ランタイムは、使用可能なデータの量をデータプロバイダーオブジェクトから読み取ります。

  • ProgressEvent.SOCKET_DATA — データが使用可能であることをランタイムに通知します。 socketData イベントはソケットベースのオブジェクトによって送出されます。その他のオブジェクトタイプについては、 progress イベントを送出する必要があります(ランタイムは両方のイベントを監視してデータの読み取りが可能なタイミングを検出します)。

  • Event.COMPLETE — データがすべて読み込まれたことをランタイムに通知します。

  • Event.CLOSE — データがすべて読み込まれたことをランタイムに通知します(この場合、ランタイムは close complete の両方を監視します)。

  • IOErrorEvent.IOERROR — データの読み取りエラーが発生したことをランタイムに通知します。ランタイムはファイル作成を中止し、IFilePromise の close() メソッドを呼び出します。

  • SecurityErrorEvent.SECURITY_ERROR — セキュリティエラーが発生したことをランタイムに通知します。ランタイムはファイル作成を中止し、IFilePromise の close() メソッドを呼び出します。

  • HTTPStatusEvent.HTTP_STATUS — 使用可能なデータがエラーメッセージ(404 ページなど)ではなく必要なコンテンツを表していることを確認するために、 httpResponseStatus と共にランタイムによって使用されます。このイベントは HTTP プロトコルに基づくオブジェクトによって送出されます。

  • HTTPStatusEvent.HTTP_RESPONSE_STATUS — 使用可能なデータが必要なコンテンツを表していることを確認するために、 httpStatus と共にランタイムによって使用されます。このイベントは HTTP プロトコルに基づくオブジェクトによって送出されます。

データプロバイダーでは、これらのイベントが次の順序で送出されます。

  1. open イベント

  2. progress イベントまたは socketData イベント

  3. complete イベントまたは close イベント

注意: ビルトインオブジェクト、FileStream、Socket、および URLStream では、該当するイベントが自動的に送出されます。

次の例では、カスタムの非同期データプロバイダーを使用してファイルプロミスを作成します。データプロバイダークラスによって ByteArray が拡張され(IDataInput サポート用)、IEventDispatcher インターフェイスが実装されます。タイマーイベントごとに、オブジェクトではデータのグループを生成し、データが使用可能であることをランタイムに通知する progress イベントを送出します。十分なデータが生成されたら、オブジェクトは complete イベントを送出します。

package 
{ 
import flash.events.Event; 
import flash.events.EventDispatcher; 
import flash.events.IEventDispatcher; 
import flash.events.ProgressEvent; 
import flash.events.TimerEvent; 
import flash.utils.ByteArray; 
import flash.utils.Timer; 
 
[Event(name="open", type="flash.events.Event.OPEN")] 
[Event(name="complete",  type="flash.events.Event.COMPLETE")] 
[Event(name="progress", type="flash.events.ProgressEvent")] 
[Event(name="ioError", type="flash.events.IOErrorEvent")] 
[Event(name="securityError", type="flash.events.SecurityErrorEvent")] 
public class AsyncDataProvider extends ByteArray implements IEventDispatcher 
{ 
    private var dispatcher:EventDispatcher = new EventDispatcher(); 
    public var fileSize:int = 0; //The number of characters in the file 
    private const chunkSize:int = 1000; //Amount of data written per event 
    private var dispatchDataTimer:Timer = new Timer( 100 ); 
    private var opened:Boolean = false; 
     
    public function AsyncDataProvider() 
    { 
        super(); 
        dispatchDataTimer.addEventListener( TimerEvent.TIMER, generateData ); 
    } 
 
    public function begin():void{ 
        dispatchDataTimer.start(); 
    } 
     
    public function end():void 
    { 
        dispatchDataTimer.stop(); 
    } 
    private function generateData( event:Event ):void 
    { 
        if( !opened ) 
        { 
            var open:Event = new Event( Event.OPEN ); 
            dispatchEvent( open );     
            opened = true; 
        } 
        else if( position + chunkSize < fileSize ) 
        { 
            for( var i:int = 0; i <= chunkSize; i++ ) 
            { 
                writeUTFBytes( 'A' ); 
            } 
            //Set position back to the start of the new data 
            this.position -= chunkSize; 
            var progress:ProgressEvent = 
                new ProgressEvent( ProgressEvent.PROGRESS, false, false, bytesAvailable, bytesAvailable + chunkSize); 
            dispatchEvent( progress ) 
        } 
        else 
        { 
            var complete:Event = new Event( Event.COMPLETE ); 
            dispatchEvent( complete );         
        } 
    } 
     //IEventDispatcher implementation 
    public function addEventListener(type:String, listener:Function, useCapture:Boolean=false, priority:int=0, useWeakReference:Boolean=false):void 
    { 
        dispatcher.addEventListener( type, listener, useCapture, priority, useWeakReference ); 
    } 
     
    public function removeEventListener(type:String, listener:Function, useCapture:Boolean=false):void 
    { 
        dispatcher.removeEventListener( type, listener, useCapture ); 
    } 
     
    public function dispatchEvent(event:Event):Boolean 
    { 
        return dispatcher.dispatchEvent( event ); 
    } 
     
    public function hasEventListener(type:String):Boolean 
    { 
        return dispatcher.hasEventListener( type ); 
    } 
     
    public function willTrigger(type:String):Boolean 
    { 
        return dispatcher.willTrigger( type ); 
    } 
} 
}
注意: この例の AsyncDataProvider クラスでは、ByteArray が拡張されるため、EventDispatcher も同時に拡張することはできません。IEventDispatcher インターフェイスを実装するには、内部の EventDispatcher オブジェクトを使用して、その内部オブジェクトに対して IEventDispatcher メソッド呼び出しを転送します。また、EventDispatcher を拡張して IDataInput を実装(または両方のインターフェイスを実装)することもできます。

非同期の IFilePromise 実装は、同期の実装とほぼ同じです。主な違いは、 isAsync true を返し、 open() メソッドが非同期データオブジェクトを返すことです。

package 
{ 
    import flash.desktop.IFilePromise; 
    import flash.events.ErrorEvent; 
    import flash.events.EventDispatcher; 
    import flash.utils.IDataInput; 
     
    public class AsynchronousFilePromise extends EventDispatcher implements IFilePromise 
    { 
        private var fileGenerator:AsyncDataProvider; 
        private const fileSize:int = 5000; //size of file data 
        private var filePath:String = "AsynchronousFile.txt"; 
                 
        public function get relativePath():String 
        { 
            return filePath; 
        } 
         
        public function get isAsync():Boolean 
        { 
            return true; 
        } 
         
        public function open():IDataInput 
        { 
            fileGenerator = new AsyncDataProvider(); 
            fileGenerator.fileSize = fileSize; 
            fileGenerator.begin(); 
            return fileGenerator; 
        } 
         
        public function close():void 
        { 
            fileGenerator.end(); 
        } 
         
        public function reportError(e:ErrorEvent):void 
        { 
            trace("Something went wrong: " + e.errorID + " - " + e.type + ", " + e.text ); 
        } 
    } 
}