Implementing the IFilePromise interface

Adobe AIR 2 and later

To provide file promises for resources that cannot be accessed using a URLFilePromise object, you can implement the IFilePromise interface in a custom class. The IFilePromise interface defines the methods and properties used by the AIR runtime to access the data to be written to a file once the file promise is dropped.

An IFilePromise implementation passes another object to the AIR runtime that provides the data for the file promise. This object must implement the IDataInput interface, which the AIR runtime uses to read the data. For example, the URLFilePromise class, which implements IFilePromise, uses a URLStream object as the data provider.

AIR can read the data synchronously or asynchronously. The IFilePromise implementation reports which mode of access is supported by returning the appropriate value in the isAsync property. If asynchronous data access is provided, the data provider object must implement the IEventDispatcher interface and dispatch the necessary events, such as open , progress and complete .

You can use a custom class, or one of the following built-in classes, as a data provider for a file promise:

  • ByteArray (synchronous)

  • FileStream (synchronous or asynchronous)

  • Socket (asynchronous)

  • URLStream (asynchronous)

To implement the IFilePromise interface, you must provide code for the following functions and properties:

  • open():IDataInput — Returns the data provider object from which the data for the promised file is read. The object must implement the IDataInput interface. If the data is provided asynchronously, the object must also implement the IEventDispatcher interface and dispatch the necessary events (see Using an asynchronous data provider in a file promise ).

  • get relativePath():String — Provides the path, including file name, for the created file. The path is resolved relative to the drop location chosen by the user in the drag-and-drop operation. To make sure that the path uses the proper separator character for the host operating system, use the File.separator constant when specifying paths containing directories. You can add a setter function or use a constructor parameter to allow the path to be set at runtime.

  • get isAsync():Boolean — Informs the AIR runtime whether the data provider object provides it’s data asynchronously or synchronously.

  • close():void — Called by the runtime when the data is fully read (or an error prevents further reading). You can use this function to cleanup resources.

  • reportError( e:ErrorEvent ):void — Called by the runtime when an error reading the data occurs.

All of the IFilePromise methods are called by the runtime during a drag-and-drop operation involving the file promise. Typically, your application logic should not call any of these methods directly.

Using a synchronous data provider in a file promise

The simplest way to implement the IFilePromise interface is to use a synchronous data provider object, such as a ByteArray or a synchronous FileStream. In the following example, a ByteArray object is created, filled with data, and returned when the open() method is called.

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 ); 
        } 
    } 
}

In practice, synchronous file promises have limited utility. If the amount of data is small, you could just as easily create a file in a temporary directory and add a normal file list array to the drag-and-drop clipboard. On the other hand, if the amount of data is large or generating the data is computationally expensive, a long synchronous process is necessary. Long synchronous processes can block UI updates for a noticeable amount of time and make your application seem unresponsive. To avoid this problem, you can create an asynchronous data provider driven by a timer.

Using an asynchronous data provider in a file promise

When you use an asynchronous data provider object, the IFilePromise isAsync property must be true and the object returned by the open() method must implement the IEventDispatcher interface. The runtime listens for several alternative events so that different built-in objects can be used as a data provider. For example, progress events are dispatched by FileStream and URLStream objects, whereas socketData events are dispatched by Socket objects. The runtime listens for the appropriate events from all of these objects.

The following events drive the process of reading the data from the data provider object:

  • Event.OPEN — Informs the runtime that the data source is ready.

  • ProgressEvent.PROGRESS — Informs the runtime that data is available. The runtime will read the amount of available data from the data provider object.

  • ProgressEvent.SOCKET_DATA — Informs the runtime that data is available. The socketData event is dispatched by socket-based objects. For other object types, you should dispatch a progress event. (The runtime listens for both events to detect when data can be read.)

  • Event.COMPLETE — Informs the runtime that the data has all been read.

  • Event.CLOSE — Informs the runtime that the data has all been read. (The runtime listens for both close and complete for this purpose.)

  • IOErrorEvent.IOERROR — Informs the runtime that an error reading the data has occurred. The runtime aborts file creation and calls the IFilePromise close() method.

  • SecurityErrorEvent.SECURITY_ERROR — Informs the runtime that a security error has occurred. The runtime aborts file creation and calls the IFilePromise close() method.

  • HTTPStatusEvent.HTTP_STATUS — Used, along with httpResponseStatus , by the runtime to make sure that the data available represents the desired content, rather than an error message (such as a 404 page). Objects based on the HTTP protocol should dispatch this event.

  • HTTPStatusEvent.HTTP_RESPONSE_STATUS — Used, along with httpStatus , by the runtime to make sure that the data available represents the desired content. Objects based on the HTTP protocol should dispatch this event.

The data provider should dispatch these events in the following sequence:

  1. open event

  2. progress or socketData events

  3. complete or close event

Note: The built-in objects, FileStream, Socket, and URLStream, dispatch the appropriate events automatically.

The following example creates a file promise using a custom, asynchronous data provider. The data provider class extends ByteArray (for the IDataInput support) and implements the IEventDispatcher interface. At each timer event, the object generates a chunk of data and dispatches a progress event to inform the runtime that the data is available. When enough data has been produced, the object dispatches a complete event.

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 ); 
    } 
} 
}
Note: Because the AsyncDataProvider class in the example extends ByteArray, it cannot also extend EventDispatcher. To implement the IEventDispatcher interface, the class uses an internal EventDispatcher object and forwards the IEventDispatcher method calls to that internal object. You could also extend EventDispatcher and implement IDataInput (or implement both interfaces).

The asynchronous IFilePromise implementation is almost identical to the synchronous implementation. The main differences are that isAsync returns true and that the open() method returns an asynchronous data object:

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 ); 
        } 
    } 
}

// Ethnio survey code removed