Implémentation de l’interface IFilePromise

Adobe AIR 2 et ultérieur

Pour associer des fichiers promis aux ressources auxquelles il est impossible d’accéder par le biais d’un objet URLFilePromise, vous pouvez implémenter l’interface IFilePromise dans une classe personnalisée. L’interface IFilePromise définit les méthodes et propriétés utilisées par le moteur d’exécution AIR pour accéder aux données à écrire dans un fichier une fois le fichier promis déposé.

Une implémentation d’IFilePromise transmet un autre objet au moteur d’exécution AIR qui fournit les données associées au fichier promis. Cet objet doit implémenter l’interface IDataInput, utilisée par le moteur d’exécution AIR pour lire les données. Par exemple, la classe URLFilePromise, qui implémente IFilePromise, fait appel à un objet URLStream en tant que fournisseur de données.

AIR peut lire les données en mode synchrone et asynchrone. L’implémentation d’IFilePromise indique le mode d’accès pris en charge en renvoyant la valeur correspondante dans la propriété isAsync . Si un accès asynchrone aux données est proposé, l’objet fournisseur de données doit implémenter l’interface IEventDispatcher et distribuer les événements requis, tels que open , progress et complete .

Vous pouvez utiliser une classe personnalisée ou l’une des classes intégrées suivantes en tant que fournisseur de données associé à un fichier promis :

  • ByteArray (accès synchrone)

  • FileStream (accès synchrone ou asynchrone)

  • Socket (accès asynchrone)

  • URLStream (accès asynchrone)

Pour implémenter l’interface IFilePromise, vous devez fournir le code des fonctions et propriétés suivantes :

  • open():IDataInput — Renvoie l’objet fournisseur de données à partir duquel sont lues les données associées au fichier promis. L’objet doit implémenter l’interface IDataInput. Si les données sont proposées en mode asynchrone, l’objet doit également implémenter l’interface IEventDispatcher et distribuer les événements requis (voir Utilisation d’un fournisseur de données asynchrone dans un fichier promis ).

  • get relativePath():String — Fournit le chemin d’accès, nom inclus, du fichier créé. Le chemin est résolu relativement à l’emplacement de dépôt choisi par l’utilisateur dans le cadre de l’opération de glisser-déposer. Pour vous assurer que le chemin utilise le caractère de séparation adapté au système d’exploitation hôte, faites appel à la constante File.separator lorsque vous définissez des chemins contenant des répertoires. Vous pouvez ajouter une fonction de définition (setter) ou utiliser un paramètre constructeur pour autoriser la définition du chemin à l’exécution.

  • get isAsync():Boolean — Indique au moteur d’exécution AIR si l’objet fournisseur de données fournit des données en mode asynchrone ou synchrone.

  • close():void — Fonction appelée par le moteur d’exécution une fois les données entièrement lues ou si une erreur empêche leur lecture complète. Vous pouvez faire appel à cette fonction pour nettoyer les ressources.

  • reportError( e:ErrorEvent ):void — Fonction appelée par le moteur d’exécution s’il se produit une erreur lors de la lecture des données.

Toutes les méthodes IFilePromise sont appelées par le moteur d’exécution dans le cadre d’une opération de glisser-déposer qui concerne le fichier promis. En règle générale, la logique de l’application ne devrait pas appeler directement ces méthodes.

Utilisation d’un fournisseur de données synchrone dans un fichier promis

La technique d’implémentation de l’interface IFilePromise la plus simple consiste à utiliser un objet fournisseur de données synchrone tel que ByteArray ou un objet FileStream synchrone. Dans l’exemple suivant, un objet ByteArray est créé, rempli de données et renvoyé lorsque la méthode open() est appelée.

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 pratique, les fichiers promis synchrones sont d’une utilité limitée. Si le volume de données est réduit, il est tout aussi simple de créer un fichier dans un répertoire temporaire et d’ajouter un tableau contenant une liste de fichiers standard au Presse-papiers glisser-déposer. En revanche, si le volume de données est élevé ou que la génération de données sollicite d’importantes ressources informatiques, un long processus synchrone s’impose. Les longs processus synchrones risquent de bloquer les mises à jour de l’interface utilisateur pendant une durée prolongée. L’application semble alors non réactive. Pour parer à ce problème, vous pouvez créer un fournisseur de données asynchrone piloté par une horloge.

Utilisation d’un fournisseur de données asynchrone dans un fichier promis

Lorsque vous utilisez un objet fournisseur de données asynchrone, la propriété isAsync d’IFilePromise doit être définie sur true et l’objet renvoyé par la méthode open() doit implémenter l’interface IEventDispatcher. Le moteur d’exécution écoute plusieurs événements, permettant ainsi d’utiliser divers objets intégrés en tant que fournisseurs de données. Les événements progress sont, par exemple, distribués par les objets FileStream et URLStream, alors que les événements socketData sont distribués par les objets Socket. Le moteur d’exécution écoute les événements appropriés qui émanent de tous ces objets.

Les événements suivants étayent le processus de lecture des données à partir de l’objet fournisseur de données :

  • Event.OPEN — Indique au moteur d’exécution que la source de données est prête.

  • ProgressEvent.PROGRESS — Indique au moteur d’exécution que les données sont disponibles. Le moteur d’exécution lit le volume de données disponibles dans l’objet fournisseur de données.

  • ProgressEvent.SOCKET_DATA — Indique au moteur d’exécution que les données sont disponibles. L’événement socketData est distribué par les objets basés sur un socket. Pour les autres types d’objets, distribuez un événement progress . (Le moteur d’exécution écoute les deux événements pour savoir quand les données peuvent être lues.)

  • Event.COMPLETE — Indique au moteur d’exécution que la lecture des données est terminée.

  • Event.CLOSE — Indique au moteur d’exécution que la lecture des données est terminée. (A ce titre, le moteur d’exécution écoute les événements close et complete .)

  • IOErrorEvent.IOERROR — Indique au moteur d’exécution qu’il s’est produit une erreur lors de la lecture des données. Le moteur d’exécution abandonne la création de fichier et appelle la méthode close() d’IFilePromise.

  • SecurityErrorEvent.SECURITY_ERROR — Indique au moteur d’exécution qu’il s’est produit une erreur liée à la sécurité. Le moteur d’exécution abandonne la création de fichier et appelle la méthode close() d’IFilePromise.

  • HTTPStatusEvent.HTTP_STATUS — Utilisé en conjonction avec httpResponseStatus par le moteur d’exécution pour s’assurer que les données disponibles représentent le contenu requis, plutôt qu’un message d’erreur (une page 404, par exemple). Les objets basés sur le protocole HTTP doivent distribuer cet événement.

  • HTTPStatusEvent.HTTP_RESPONSE_STATUS — Utilisé en conjonction avec httpStatus par le moteur d’exécution pour s’assurer que les données disponibles représentent le contenu requis. Les objets basés sur le protocole HTTP doivent distribuer cet événement.

Le fournisseur de données doit distribuer ces événements dans l’ordre suivant :

  1. événement open

  2. événements progress ou socketData

  3. événement complete ou close

Remarque : les objets intégrés, FileStream, Socket et URLStream, distribuent automatiquement les événements appropriés.

L’exemple suivant crée un fichier promis en faisant appel à un fournisseur de données asynchrone personnalisé. La classe du fournisseur de données constitue une extension de ByteArray (pour la prise en charge d’IDataInput) et implémente l’interface IEventDispatcher. A chaque événement associé à l’horloge, l’objet génère un bloc de données et distribue un événement « progress » pour avertir le moteur d’exécution que les données sont disponibles. Lorsque le volume de données produit est suffisant, l’objet distribue un événement « 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 ); 
    } 
} 
}
Remarque : puisque la classe AsyncDataProvider utilisée dans l’exemple constitue une extension de ByteArray, elle ne peut pas étendre également EventDispatcher. Pour implémenter l’interface IEventDispatcher, la classe fait appel à un objet EventDispatcher interne et transmet à ce dernier les appels de la méthode IEventDispatcher. Vous pouvez également étendre EventDispatcher et implémenter IDataInput (voire implémenter les deux interfaces).

L’implémentation asynchrone d’IFilePromise est quasiment identique à l’implémentation synchrone, à quelques différences près : isAsync renvoie true et la méthode open() renvoie un objet de données asynchrone :

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