Implementación de la interfaz IFilePromise

Adobe AIR 2 y posterior

Para proporcionar promesas de archivo a recursos a los que no se puede acceder utilizando un objeto URLFilePromise, es posible implementar la interfaz IFilePromise en una clase personalizada. La interfaz IFilePromise define los métodos y las propiedades utilizadas por el motor de ejecución de AIR para acceder a los datos que se escribirán en un archivo una vez colocada la promesa de archivo.

Una implementación de IFilePromise transmite otro objeto al motor de ejecución de AIR que proporciona los datos para la promesa de archivo. Este objeto debe implementar la interfaz IDataInput, que el motor de ejecución de AIR utiliza para leer los datos. Por ejemplo, la clase URLFilePromise, que implementa IFilePromise, usa un objeto URLStream como proveedor de datos.

AIR puede leer los datos de forma sincrónica o asíncrona. La implementación de IFilePromise indica qué modo de acceso se admite devolviendo el valor adecuado en la propiedad isAsync . Si se proporciona un acceso asíncrono a los datos, el objeto proveedor de datos debe implementar la interfaz IEventDispatcher y distribuir los eventos necesarios como, por ejemplo, open , progress y complete .

Se puede utilizar una clase personalizada o una de las siguientes clases incorporadas, como proveedor de datos para una promesa de archivo:

  • ByteArray (sincrónica)

  • FileStream (sincrónica o asíncrona)

  • Socket (asíncrona)

  • URLStream (asincrónica)

Para implementar la interfaz IFilePromise, es necesario proporcionar código para las siguientes funciones y propiedades:

  • open():IDataInput : devuelve el objeto proveedor de datos en el que se leen los datos para el archivo prometido. El objeto debe implementar la interfaz IDataInput. Si los datos se proporciona de forma asíncrona, el objeto también debe implementar la interfaz IEventDispatcher y distribuir los eventos necesarios (consulte Uso de un proveedor de datos asíncrono en una promesa de archivo ).

  • get relativePath():String : proporciona la ruta, incluyendo el nombre de archivo, para el archivo creado. La ruta se resuelve en función de la ubicación de destino seleccionada por el usuario en la operación de arrastrar y colocar. Para garantizar que la ruta utilice el carácter separador apropiado para el sistema operativo del ordenador host, utilice la constante File.separator al especificar las rutas que incluyen directorios. Se puede añadir una función captadora o emplear un parámetro constructor para permitir que la ruta se establezca en tiempo de ejecución.

  • get isAsync():Boolean : informa al motor de ejecución de AIR si el objeto proveedor de datos proporciona su información de forma sincrónica o asíncrona.

  • close():void : se llama mediante el motor de ejecución cuando los datos se han leído por completo (o un error detiene la lectura). Esta función se puede utilizar para limpiar recursos.

  • reportError( e:ErrorEvent ):void : se llama mediante el motor de ejecución cuando se produce un error al leer los datos.

Todos los métodos IFilePromise se llaman mediante el motor de ejecución durante una operación de arrastrar y colocar que implica la promesa de archivo. Generalmente la lógica de la aplicación no debe llamar a ninguno de estos métodos directamente.

Uso de un proveedor de datos sincrónico en una promesa de archivo

La forma más sencilla de implementar la interfaz IFilePromise consiste en utilizar un objeto proveedor de datos sincrónico como, por ejemplo, ByteArray o FileStream. En el siguiente ejemplo se crea un objeto ByteArray, relleno de datos, y se devuelve cuando se llama al método 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 ); 
        } 
    } 
}

En la práctica, las promesas de archivo sincrónicas presentan una utilidad limitada. Si la cantidad de datos es pequeña, se puede crear fácilmente un archivo en un directorio temporal y añadir un conjunto de lista de archivos normal al portapapeles de arrastrar y colocar. Por otra parte, si la cantidad de información es grande o la generación de datos requiere mucho procesamiento, es necesario contar un proceso sincrónico largo. Los procesos sincrónicos largos pueden bloquear las actualizaciones de la IU durante una cantidad considerable de tiempo y hacer que la aplicación no responda. Para evitar este problema, se puede crear un proveedor de datos asíncrono controlado por un temporizador.

Uso de un proveedor de datos asíncrono en una promesa de archivo

Cuando se utiliza un objeto proveedor de datos asíncrono, la propiedad isAsync de IFilePromise se debe establecer en true y el objeto devuelto por el método open() debe implementar la interfaz IEventDispatcher. El motor de ejecución detecta varios eventos alternativos de modo que los distintos objetos incorporados se puedan utilizar como proveedores de datos. Por ejemplo, los eventos progress se distribuyen mediante los objetos FileStream y URLStream, mientras que los eventos socketData se distribuyen a través de los objetos Socket. El motor de ejecución detecta los eventos apropiados a partir de todos estos objetos.

Los siguientes eventos controlan el proceso de lectura de datos desde el objeto proveedor:

  • Event.OPEN: informa al motor de ejecución de que el origen de datos está listo.

  • ProgressEvent.PROGRESS: indica al motor de ejecución que los datos están disponibles. El motor de ejecución leerá la cantidad de datos disponibles desde el objeto proveedor de datos.

  • ProgressEvent.SOCKET_DATA: indica al motor de ejecución que los datos están disponibles. El evento socketData se distribuye mediante los objetos basados en socket. Para otros tipos de objetos, se debe distribuir un evento progress . (El motor de ejecución detecta ambos eventos para percibir el momento en que se pueden leer los datos.)

  • Event.COMPLETE: indica al motor de ejecución que todos los datos se han leído.

  • Event.CLOSE: indica al motor de ejecución que todos los datos se han leído. (El motor de ejecución detecta close y complete para este propósito.)

  • IOErrorEvent.IOERROR: indica al motor de ejecución que se ha producido un error al leer los datos. El motor de ejecución anula la creación del archivo y llama al método close() de IFilePromise.

  • SecurityErrorEvent.SECURITY_ERROR: indica al motor de ejecución que se ha producido un error de seguridad. El motor de ejecución anula la creación del archivo y llama al método close() de IFilePromise.

  • HTTPStatusEvent.HTTP_STATUS: el motor de ejecución lo utiliza, junto con httpResponseStatus , para garantizar que los datos disponibles representan el contenido deseado, en lugar de un mensaje de error (por ejemplo, una página 404). Los objetos basados en el protocolo HTTP deben distribuir este evento.

  • HTTPStatusEvent.HTTP_RESPONSE_STATUS: el motor de ejecución lo utiliza, junto con httpStatus , para garantizar que los datos disponibles representan el contenido deseado. Los objetos basados en el protocolo HTTP deben distribuir este evento.

El proveedor de datos debe distribuir estos eventos en la siguiente secuencia:

  1. evento open

  2. eventos progress o socketData

  3. evento complete o close

Nota: los objetos incorporados, FileStream, Socket y URLStream distribuyen los eventos adecuados de forma automática.

En el siguiente ejemplo se crea una promesa de archivo utilizando un proveedor de datos asíncrono y personalizado. La clase del proveedor de datos amplía ByteArray (para la compatibilidad con IDataInput) e implementa la interfaz IEventDispatcher. En cada evento del temporizador, el objeto genera un campo de datos y distribuye un evento de progreso para indicar al motor de ejecución que los datos están disponibles. Una vez generados datos suficientes, el objeto distribuye un evento completo.

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 ); 
    } 
} 
}
Nota: debido a que la clase AsyncDataProvider del ejemplo amplía ByteArray, no puede ampliar también EventDispatcher. Para implementar la interfaz IEventDispatcher, la clase utiliza un objeto interno EventDispatcher y reenvía las llamadas del método IEventDispatcher a ese objeto interno. También se puede ampliar EventDispatcher e implementar IDataInput (o implementar ambas interfaces).

La implementación asíncrona de IFilePromise resulta casi idéntica a la implementación sincrónica. Las principales diferencias radican en que isAsync devuelve el valor true y que el método open() devuelve un objeto de datos asíncrono:

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