// The MIT License (MIT)
//
// Copyright (c) 2014 Tilman Griesel - <http://rocketengine.io> <http://github.com/TilmanGriesel>
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
//	The above copyright notice and this permission notice shall be included in all
//	copies or substantial portions of the Software.
//
//	THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
//	IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
//	FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
//	AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
//	LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
//	OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
//	SOFTWARE.

package io.rocketengine.pusheras
{
	import flash.events.Event;
	import flash.events.EventDispatcher;

	import io.rocketengine.loggeras.logger.LAS;
	import io.rocketengine.loggeras.logger.interfaces.ILASLogger;
	import io.rocketengine.pusheras.channel.PusherChannel;
	import io.rocketengine.pusheras.events.PusherChannelEvent;
	import io.rocketengine.pusheras.events.PusherConnectionStatusEvent;
	import io.rocketengine.pusheras.events.PusherEvent;
	import io.rocketengine.pusheras.utils.PusherConstants;
	import io.rocketengine.pusheras.vo.PusherOptions;
	import io.rocketengine.pusheras.vo.PusherStatus;
	import io.rocketengine.pusheras.vo.WebsocketStatus;
	import io.rocketengine.websocket.interfaces.IWSocket;
	import io.rocketengine.websocket.proxies.FlashWebsocketProxy;

	/**
	 * Pusher <http://pusher.com> ActionScript3 Client Library
	 * @author Tilman Griesel <https://github.com/TilmanGriesel>
	 */
	public class Pusher extends EventDispatcher
	{
		private static const logger:ILASLogger = LAS.getLogger(Pusher);

		private static const VERSION:String = '0.1.6';

		private var _verboseLogging:Boolean = false;

		// pusheras vars
		private var _pusherOptions:PusherOptions;
		private var _pusherStatus:PusherStatus;

		// websocket vars
		private var _websocket:FlashWebsocketProxy;
		private var _websocketStatus:WebsocketStatus;

		// channel bucket
		protected var _channelBucket:Vector.<PusherChannel>;

		/**
		 * @param options all required options for the pusher connection
		 * */
		public function Pusher(options:PusherOptions)
		{
			logger.info('construct');

			// parameter check
			if(options == null)
				throw new Error('Options cannot be null');

			// store options
			_pusherOptions = options;

			// create small storage object for the websocket and pusher status
			_websocketStatus = new WebsocketStatus();
			_pusherStatus = new PusherStatus();

			// create channel bucket
			_channelBucket = new Vector.<PusherChannel>;
			this.addEventListener(PusherEvent.CONNECTION_ESTABLISHED, this_CONNECTION_ESTABLISHED);
		}

		public function connect():void
		{
			logger.info('Connecting...');
			// connect to websocket server
			connectWebsocket();
		}

		/**
		 * inital websocket connection
		 * */
		private function connectWebsocket():void
		{
			// check for websocket status storage object
			if(_websocketStatus == null)
				throw new Error('websocket status cannot be null');

			// check for pusher status storage object
			if(_pusherStatus == null)
				throw new Error('pusher status cannot be null');

			// check if connection attempt is already in progress
			if(_websocketStatus.connecting)
			{
				logger.warn('Already attempting a connection. Aborting...');
				return;
			}

			// check if websocket is already connected
			if(_websocketStatus.connected)
			{
				logger.warn('Connection is already established. Aborting connection attempt...');
				return;
			}

			logger.info('Environment check successfully completed.');

			// update status
			_pusherStatus.connecting = true;
			_websocketStatus.connecting = true;

			// get pusher url
			var pusherURL:String;
			if(_pusherOptions.secure)
			{
				pusherURL = _pusherOptions.pusherSecureURL;
			}
			else
			{
				pusherURL = _pusherOptions.pusherURL;
			}

			// Initialize websocket
			_websocket = new FlashWebsocketProxy();
			_websocket.verboseLogging = _verboseLogging;
			_websocket.init(_pusherOptions.autoPing, _pusherOptions.pingPongBasedDisconnect, _pusherOptions.pingInterval, _pusherOptions.pingPongTimeout, _pusherOptions.interruptTimeout);
			_websocket.registerCallbacks(onWSInit, onWSOpen, onWSMessage, onWSClose, onFail, onWSInterrupt);
			_websocket.connect(pusherURL);
		}

		private function onWSInit(sender:IWSocket = null):void
		{
			logger.info("Websocket initialized");
		}

		private function onWSOpen(sender:IWSocket = null):void
		{
			logger.info("Websocket open");
			// store status
			_websocketStatus.connected = true;
		}

		private function onWSMessage(sender:IWSocket = null, msg:String = ''):void
		{
			if(_verboseLogging) logger.debug('receiving << [', msg, ']');

			// try to parse new pusher event from websocket message
			try
			{
				var pusherEvent:PusherEvent = PusherEvent.parse(unescape(msg));
			}
			catch(e:Error)
			{
				logger.error('Websocket message parsing error: ' + e.message + ' | message: ' + unescape(msg));
				return;
			}

			// look in the channel bucket if channel subscribed and dispatch event on it
			if(pusherEvent.channel != null)
			{
				for(var i:int = 0; i < _channelBucket.length; i++)
				{
					var channel:PusherChannel = _channelBucket[i] as PusherChannel;
					if(channel.name == pusherEvent.channel)
					{
						channel.dispatchEvent(pusherEvent);
					}
				}
			}
			else
			{
				// redispatch pusher event
				this.dispatchEvent(pusherEvent);
			}
		}

		private function onWSClose(sender:IWSocket = null, code:int = -1, msg:String = ''):void
		{
			logger.warn('Websocket closed', code, msg);

			_websocketStatus.connected = false;
			_websocketStatus.connecting = false;
			_websocketStatus.socketID = null;

			var evt:PusherConnectionStatusEvent = new PusherConnectionStatusEvent(PusherConnectionStatusEvent.WS_DISCONNECTED);
			evt.data.code = code;
			evt.data.msg = msg;
			this.dispatchEvent(evt);
		}

		private function onFail(sender:IWSocket = null, code:int = -1, msg:String = ''):void
		{
			logger.error('Websocket failed', code, msg);

			_websocketStatus.connected = false;
			_websocketStatus.connecting = false;
			_websocketStatus.socketID = null;

			var evt:PusherConnectionStatusEvent = new PusherConnectionStatusEvent(PusherConnectionStatusEvent.WS_FAILED);
			evt.data.code = code;
			evt.data.msg = msg;
			this.dispatchEvent(evt);
		}

		private function onWSInterrupt(sender:IWSocket = null, value:Boolean = false):void
		{
			if(value) logger.warn('Websocket interrupted!');
			else logger.info('Websocket ok!');

			var evt:PusherConnectionStatusEvent = new PusherConnectionStatusEvent(PusherConnectionStatusEvent.WS_INTERRUPTED);
			evt.data.interrupted = value;
			this.dispatchEvent(evt);
		}

		protected function this_CONNECTION_ESTABLISHED(event:PusherEvent):void
		{
			logger.info('Websocket connection established. socket id: ' + event.data.socket_id);

			this.dispatchEvent(new PusherConnectionStatusEvent(PusherConnectionStatusEvent.WS_ESTABLISHED));

			_pusherStatus.connected = true;
			if(event.data.hasOwnProperty('socket_id'))
			{
				_websocketStatus.socketID = event.data.socket_id;
			}
		}

		/**
		 * Subscribes a pusher channel with the given name.
		 * add native event listeners to it
		 * @param channelName The name of your channel
		 * @return a channel instance for event listening and dispatching
		 */
		public function subscribe(channelName:String):PusherChannel
		{
			// check the pusher connection
			if(_pusherStatus.connected == false)
				throw new Error('cannot subscribe "' + channelName + '" because the pusher service is not connected!');

			// pusher channel implentation
			var pusherChannel:PusherChannel;

			// define channel type
			if(channelName.indexOf(PusherConstants.CHANNEL_NAME_PRIVATE_PREFIX) != -1)
			{
				logger.info('subscribing private channel "' + channelName + '"...');
				pusherChannel = new PusherChannel(PusherChannel.PRIVATE, channelName, dispatchPusherEvent, true, _websocketStatus.socketID, _pusherOptions.auth_endpoint);
			}
			else
			{
				logger.info('subscribing public channel "' + channelName + '"...');
				pusherChannel = new PusherChannel(PusherChannel.PUBLIC, channelName, dispatchPusherEvent);
			}

			// add internal channel event listeners
			pusherChannel.addEventListener(PusherChannelEvent.SETUP_COMPLETE, pusherChannel_SETUP_COMPLETE);

			// initialize channel (perform auth request etc.)
			pusherChannel.init();
			return pusherChannel;
		}

		/**
		 * subscribe channel after setup complete event
		 * */
		protected function pusherChannel_SETUP_COMPLETE(event:Event):void
		{
			// get channel
			var pusherChannel:PusherChannel = event.target as PusherChannel;

			// create new channel object
			_channelBucket.push(pusherChannel);

			// create new pusher event
			var pusherEvent:PusherEvent = new PusherEvent(PusherEvent.SUBSCRIBE);
			pusherEvent.data.channel = pusherChannel.name;
			if (pusherChannel.authenticationSignature.indexOf(_pusherOptions.applicationKey + ':') == 0)
          pusherEvent.data.auth = pusherChannel.authenticationSignature;
      else
          pusherEvent.data.auth = _pusherOptions.applicationKey + ':' + pusherChannel.authenticationSignature;

			// dispatch event to pusher service
			dispatchPusherEvent(pusherEvent);
		}

		/**
		 * Remove and unsubscribe channel
		 * */
		public function unsubscribe(channelName:String):void
		{
			// create new pusher event
			var pusherEvent:PusherEvent = new PusherEvent(PusherEvent.UNSUBSCRIBE);
			pusherEvent.data.channel = channelName;

			// search for channel in bucket
			for(var i:int = 0; i < _channelBucket.length; i++)
			{
				var channel:PusherChannel = _channelBucket[i] as PusherChannel;
				if(channel.name == pusherEvent.channel)
				{
					// remove channel from bucket
					_channelBucket.splice(i, 1);
				}
			}
		}

		/**
		 * dispatch event to pusher service
		 * **/
		public function dispatchPusherEvent(event:PusherEvent):void
		{
			// check websocket connection
			if(_websocketStatus.connected == false)
			{
				logger.error('Websocket is not connected... Cannot dispatch event!');
			}

			if(_verboseLogging) logger.info('sending >> [', event.toJSON(), ']');
			_websocket.send(event.toJSON());
		}

		public function get pusherStatus():PusherStatus
		{
			return _pusherStatus;
		}

		public function get verboseLogging():Boolean
		{
			return _verboseLogging;
		}

		public function set verboseLogging(value:Boolean):void
		{
			_verboseLogging = value;
		}

	}
}

change space to tab

change space to tab


package com.kaltura.delegates {

	import com.kaltura.config.IKalturaConfig;
	import com.kaltura.config.KalturaConfig;
	import com.kaltura.core.KClassFactory;
	import com.kaltura.errors.KalturaError;
	import com.kaltura.events.KalturaEvent;
	import com.kaltura.net.KalturaCall;

	import flash.events.ErrorEvent;
	import flash.events.Event;
	import flash.events.EventDispatcher;
	import flash.events.HTTPStatusEvent;
	import flash.events.IOErrorEvent;
	import flash.events.SecurityErrorEvent;
	import flash.events.TimerEvent;
	import flash.net.FileReference;
	import flash.net.URLLoader;
	import flash.net.URLLoaderDataFormat;
	import flash.net.URLRequest;
	import flash.net.URLRequestMethod;
	import flash.utils.Timer;
	import flash.utils.getDefinitionByName;

	public class WebDelegateBase extends EventDispatcher implements IKalturaCallDelegate {

		public static var CONNECT_TIME : int = 60000; //60 secs
		public static var LOAD_TIME : int = 60000; //60 secs

		protected var connectTimer:Timer;
		protected var loadTimer:Timer;

		protected var _call:KalturaCall;
		protected var _config:KalturaConfig;

		protected var loader:URLLoader;
		protected var fileRef:FileReference;

		//Setters & getters
		public function get call():KalturaCall { return _call; }
		public function set call(newVal:KalturaCall):void { _call = newVal; }

		public function get config():IKalturaConfig { return _config; }
		public function set config(newVal:IKalturaConfig):void { _config = newVal as KalturaConfig; }

		public function WebDelegateBase(call:KalturaCall = null , config:KalturaConfig = null)
		{
			this.call = call;
			this.config = config;
			if(!call) return; //maybe a multi request
			if (call.useTimeout) {
				connectTimer = new Timer(CONNECT_TIME, 1);
				connectTimer.addEventListener(TimerEvent.TIMER_COMPLETE, onConnectTimeout);

				loadTimer = new Timer(LOAD_TIME, 1);
				loadTimer.addEventListener(TimerEvent.TIMER_COMPLETE, onLoadTimeOut);
			}
			execute();
		}

		public function close():void {
			try {
				loader.close();
			} catch (e:*) { }

			if (call.useTimeout) {
				connectTimer.stop();
				loadTimer.stop();
			}
		}

		protected function onConnectTimeout(event:TimerEvent):void {
			var kError:KalturaError = new KalturaError();
			//kError.errorCode =
			kError.errorMsg = "Connection Timeout: " + CONNECT_TIME/1000 + " sec with no post command from kaltura client.";
			_call.handleError(kError);
			dispatchEvent(new KalturaEvent(KalturaEvent.FAILED, false, false, false, null, kError));

			loadTimer.stop();
			close();
		}

		protected function onLoadTimeOut(event:TimerEvent):void {
			connectTimer.stop();

			close();

			var kError:KalturaError = new KalturaError();
			kError.errorMsg = "Post Timeout: "+ LOAD_TIME/1000 + " sec with no post result.";
			_call.handleError(kError);
			dispatchEvent(new KalturaEvent(KalturaEvent.FAILED, false, false, false, null, kError));
		}

		protected function execute():void {
			if (call == null) { throw new Error('No call defined.'); }
			post(); //post the call
		}

		/**
		 * Helper function for sending the call straight to the server
		 */
		protected function post():void {

			addOptionalArguments();

			formatRequest();

			sendRequest();

			if (call.useTimeout) {
				connectTimer.start();
			}
		}

		protected function formatRequest():void
		{
			//The configuration is stronger then the args
			if(_config.partnerId != null && _call.args["partnerId"] == -1)
				_call.setRequestArgument("partnerId", _config.partnerId);

			if (_config.ks != null)
				_call.setRequestArgument("ks", _config.ks);

			if(_config.clientTag != null)
				_call.setRequestArgument("clientTag", _config.clientTag);

			_call.setRequestArgument("ignoreNull", _config.ignoreNull);

			//Create signature hash.
			//call.setRequestArgument("kalsig", getMD5Checksum(call));
		}

		protected function sendRequest():void {

			//construct the loader
			createURLLoader();

			//create the service request for normal calls
			var url : String = _config.protocol + _config.domain +"/"+_config.srvUrl+"?service="+call.service+"&action="+call.action;

			if( _call.method == URLRequestMethod.GET )url += "&";

			var req:URLRequest = new URLRequest( url );
			req.contentType = "application/x-www-form-urlencoded";
			req.method = call.method;
			req.data = call.args;

			loader.dataFormat = URLLoaderDataFormat.TEXT;
			loader.load(req);
		}

		protected function createURLLoader():void {
			loader = new URLLoader();
			loader.addEventListener(Event.COMPLETE, onDataComplete);
			loader.addEventListener(HTTPStatusEvent.HTTP_STATUS, onHTTPStatus);
			loader.addEventListener(IOErrorEvent.IO_ERROR, onError);
			loader.addEventListener(SecurityErrorEvent.SECURITY_ERROR, onError);
			loader.addEventListener(Event.OPEN, onOpen);
		}

		protected function onHTTPStatus(event:HTTPStatusEvent):void { }

		protected function onOpen(event:Event):void {
			if (call.useTimeout) {
				connectTimer.stop();
				loadTimer.start();
			}
		}

		protected function addOptionalArguments():void {
			//add optional args here
		}

		// Event Handlers
		protected function onDataComplete(event:Event):void {
			try{
				handleResult( XML(event.target.data) );
			}
			catch( e:Error )
			{
				var kErr : KalturaError = new KalturaError();
				kErr.errorCode = String(e.errorID);
				kErr.errorMsg = e.message;
				_call.handleError( kErr );
			}
		}

		protected function onError( event:ErrorEvent ):void {
			clean();
			var kError:KalturaError = createKalturaError( event, loader.data);

			if(!kError)
			{
				kError.errorMsg = event.text;
				//kError.errorCode;
			}

			call.handleError(kError);

			dispatchEvent(new KalturaEvent(KalturaEvent.FAILED, false, false, false, null, kError));
		}

		/**
		 * parse the server's response and let the call process it.
		 * @param result	server's response
		 */
		protected function handleResult(result:XML):void {
			clean();

			var error:KalturaError = validateKalturaResponse(result);

			if (error == null) {
				var digestedResult : Object = parse(result);
				call.handleResult( digestedResult );
			} else {
				call.handleError(error);
			}
		}

		/**
		 * stop timers and clean event listeners
		 */
		protected function clean():void {
			if (call.useTimeout) {
				connectTimer.stop();
				loadTimer.stop();
			}

			if (loader == null) { return; }

			loader.removeEventListener(Event.COMPLETE, onDataComplete);
			loader.removeEventListener(IOErrorEvent.IO_ERROR, onError);
			loader.removeEventListener(SecurityErrorEvent.SECURITY_ERROR, onError);
			loader.removeEventListener(Event.OPEN, onOpen);
		}

		/**
		 * create the correct object and populate it with the given values. if the needed class is not found
		 * in the file, a generic object is created with attributes matching the XML attributes.
		 * Override this parssing function in the specific delegate to create the correct object.
		 * @param	result	instance attributes
		 * @return an instance of the class declared by the given XML.
		 * */
		public function parse( result : XML ) : *
		{
			//by defualt create the response object
			var cls : Class;
			try
			{
				cls = getDefinitionByName('com.kaltura.vo.'+ result.result.objectType) as Class;
			}
			catch( e : Error )
			{
				cls = Object;
			}
			var obj : * = (new KClassFactory( cls )).newInstanceFromXML( result.result );
			return obj;
		}

		/**
		 * If the result string holds an error, return a KalturaError object with
		 * relevant values. <br/>
		 * Overide this to create validation object and fill it.
		 * @param result	the string returned from the server.
		 * @return	matching error object
		 */
		protected function validateKalturaResponse(result:String) : KalturaError
		{
			var kError : KalturaError = null;
			var xml : XML = XML(result);
			if(xml.result.hasOwnProperty('error')){
				kError = new KalturaError();
				kError.errorCode = String(xml.result.error.code);
				kError.errorMsg = xml.result.error.message;
				dispatchEvent(new KalturaEvent(KalturaEvent.FAILED, false, false, false, null, kError));
			}

			return kError;
		}

		//Overide this to create error object and fill it
		protected function createKalturaError( event : ErrorEvent , loaderData : * ) : KalturaError
		{
			var ke : KalturaError = new KalturaError();
			return ke;
		}

		/**
		 * create the url that is used for serve actions
		 * @param call	the KalturaCall that defines the required parameters
		 * @return URLRequest with relevant parameters
		 * */
		public function getServeUrl(call:KalturaCall):URLRequest
		{
			var url : String = _config.protocol + _config.domain +"/"+_config.srvUrl+"?service="+call.service+"&action="+call.action;
			var req:URLRequest = new URLRequest( url );
			req.contentType = "application/x-www-form-urlencoded";
			req.method = call.method;
			req.data = call.args;
			return req;
		}
	}
}

package com.kaltura.delegates {

	import com.kaltura.config.IKalturaConfig;
	import com.kaltura.config.KalturaConfig;
	import com.kaltura.core.KClassFactory;
	import com.kaltura.errors.KalturaError;
	import com.kaltura.events.KalturaEvent;
	import com.kaltura.net.KalturaCall;

	import flash.events.ErrorEvent;
	import flash.events.Event;
	import flash.events.EventDispatcher;
	import flash.events.HTTPStatusEvent;
	import flash.events.IOErrorEvent;
	import flash.events.SecurityErrorEvent;
	import flash.events.TimerEvent;
	import flash.net.FileReference;
	import flash.net.URLLoader;
	import flash.net.URLLoaderDataFormat;
	import flash.net.URLRequest;
	import flash.net.URLRequestMethod;
	import flash.utils.Timer;
	import flash.utils.getDefinitionByName;

	public class WebDelegateBase extends EventDispatcher implements IKalturaCallDelegate {

		public static var CONNECT_TIME : int = 60000; //60 secs
		public static var LOAD_TIME : int = 60000; //60 secs

		protected var connectTimer:Timer;
		protected var loadTimer:Timer;

		protected var _call:KalturaCall;
		protected var _config:KalturaConfig;

		protected var loader:URLLoader;
		protected var fileRef:FileReference;

		//Setters & getters
		public function get call():KalturaCall { return _call; }
		public function set call(newVal:KalturaCall):void { _call = newVal; }

		public function get config():IKalturaConfig { return _config; }
		public function set config(newVal:IKalturaConfig):void { _config = newVal as KalturaConfig; }

		public function WebDelegateBase(call:KalturaCall = null , config:KalturaConfig = null)
		{
			this.call = call;
			this.config = config;
			if(!call) return; //maybe a multi request
			if (call.useTimeout) {
				connectTimer = new Timer(CONNECT_TIME, 1);
				connectTimer.addEventListener(TimerEvent.TIMER_COMPLETE, onConnectTimeout);

				loadTimer = new Timer(LOAD_TIME, 1);
				loadTimer.addEventListener(TimerEvent.TIMER_COMPLETE, onLoadTimeOut);
			}
			execute();
		}

		public function close():void {
			try {
				loader.close();
			} catch (e:*) { }

			if (call.useTimeout) {
				connectTimer.stop();
				loadTimer.stop();
			}
		}

		protected function onConnectTimeout(event:TimerEvent):void {
			var kError:KalturaError = new KalturaError();
			//kError.errorCode =
			kError.errorMsg = "Connection Timeout: " + CONNECT_TIME/1000 + " sec with no post command from kaltura client.";
			_call.handleError(kError);
			dispatchEvent(new KalturaEvent(KalturaEvent.FAILED, false, false, false, null, kError));

			loadTimer.stop();
			close();
		}

		protected function onLoadTimeOut(event:TimerEvent):void {
			connectTimer.stop();

			close();

			var kError:KalturaError = new KalturaError();
			kError.errorMsg = "Post Timeout: "+ LOAD_TIME/1000 + " sec with no post result.";
			_call.handleError(kError);
			dispatchEvent(new KalturaEvent(KalturaEvent.FAILED, false, false, false, null, kError));
		}

		protected function execute():void {
			if (call == null) { throw new Error('No call defined.'); }
			post(); //post the call
		}

		/**
		 * Helper function for sending the call straight to the server
		 */
		protected function post():void {

			addOptionalArguments();

			formatRequest();

			sendRequest();

			if (call.useTimeout) {
				connectTimer.start();
			}
		}

		protected function formatRequest():void
		{
			//The configuration is stronger then the args
			if(_config.partnerId != null && _call.args["partnerId"] == -1)
				_call.setRequestArgument("partnerId", _config.partnerId);

			if (_config.ks != null)
				_call.setRequestArgument("ks", _config.ks);

			if(_config.clientTag != null)
				_call.setRequestArgument("clientTag", _config.clientTag);

			_call.setRequestArgument("ignoreNull", _config.ignoreNull);

			//Create signature hash.
			//call.setRequestArgument("kalsig", getMD5Checksum(call));
		}

		protected function sendRequest():void {

			//construct the loader
			createURLLoader();

			//create the service request for normal calls
			var url : String = _config.protocol + _config.domain +"/"+_config.srvUrl+"?service="+call.service+"&action="+call.action;

			if( _call.method == URLRequestMethod.GET )url += "&";

			var req:URLRequest = new URLRequest( url );
			req.contentType = "application/x-www-form-urlencoded";
			req.method = call.method;
			req.data = call.args;

			loader.dataFormat = URLLoaderDataFormat.TEXT;
			loader.load(req);
		}

		protected function createURLLoader():void {
			loader = new URLLoader();
			loader.addEventListener(Event.COMPLETE, onDataComplete);
			loader.addEventListener(HTTPStatusEvent.HTTP_STATUS, onHTTPStatus);
			loader.addEventListener(IOErrorEvent.IO_ERROR, onError);
			loader.addEventListener(SecurityErrorEvent.SECURITY_ERROR, onError);
			loader.addEventListener(Event.OPEN, onOpen);
		}

		protected function onHTTPStatus(event:HTTPStatusEvent):void { }

		protected function onOpen(event:Event):void {
			if (call.useTimeout) {
				connectTimer.stop();
				loadTimer.start();
			}
		}

		protected function addOptionalArguments():void {
			//add optional args here
		}

		// Event Handlers
		protected function onDataComplete(event:Event):void {
			try{
				handleResult( XML(event.target.data) );
			}
			catch( e:Error )
			{
				var kErr : KalturaError = new KalturaError();
				kErr.errorCode = String(e.errorID);
				kErr.errorMsg = e.message;
				_call.handleError( kErr );
			}
		}

		protected function onError( event:ErrorEvent ):void {
			clean();
			var kError:KalturaError = createKalturaError( event, loader.data);

			if(!kError)
			{
				kError.errorMsg = event.text;
				//kError.errorCode;
			}

			call.handleError(kError);

			dispatchEvent(new KalturaEvent(KalturaEvent.FAILED, false, false, false, null, kError));
		}

		/**
		 * parse the server's response and let the call process it.
		 * @param result	server's response
		 */
		protected function handleResult(result:XML):void {
			clean();

			var error:KalturaError = validateKalturaResponse(result);

			if (error == null) {
				var digestedResult : Object = parse(result);
				call.handleResult( digestedResult );
			} else {
				call.handleError(error);
			}
		}

		/**
		 * stop timers and clean event listeners
		 */
		protected function clean():void {
			if (call.useTimeout) {
				connectTimer.stop();
				loadTimer.stop();
			}

			if (loader == null) { return; }

			loader.removeEventListener(Event.COMPLETE, onDataComplete);
			loader.removeEventListener(IOErrorEvent.IO_ERROR, onError);
			loader.removeEventListener(SecurityErrorEvent.SECURITY_ERROR, onError);
			loader.removeEventListener(Event.OPEN, onOpen);
		}

		/**
		 * create the correct object and populate it with the given values. if the needed class is not found
		 * in the file, a generic object is created with attributes matching the XML attributes.
		 * Override this parssing function in the specific delegate to create the correct object.
		 * @param	result	instance attributes
		 * @return an instance of the class declared by the given XML.
		 * */
		public function parse( result : XML ) : *
		{
			//by defualt create the response object
			var cls : Class;
			try
			{
				cls = getDefinitionByName('com.kaltura.vo.'+ result.result.objectType) as Class;
			}
			catch( e : Error )
			{
				cls = Object;
			}
			var obj : * = (new KClassFactory( cls )).newInstanceFromXML( result.result );
			return obj;
		}

		/**
		 * If the result string holds an error, return a KalturaError object with
		 * relevant values. <br/>
		 * Overide this to create validation object and fill it.
		 * @param result	the string returned from the server.
		 * @return	matching error object
		 */
		protected function validateKalturaResponse(result:String) : KalturaError
		{
			var kError : KalturaError = null;
			var xml : XML = XML(result);
			if(xml.result.hasOwnProperty('error')){
				kError = new KalturaError();
				kError.errorCode = String(xml.result.error.code);
				kError.errorMsg = xml.result.error.message;
				dispatchEvent(new KalturaEvent(KalturaEvent.FAILED, false, false, false, null, kError));
			}

			return kError;
		}

		//Overide this to create error object and fill it
		protected function createKalturaError( event : ErrorEvent , loaderData : * ) : KalturaError
		{
			var ke : KalturaError = new KalturaError();
			return ke;
		}

		/**
		 * create the url that is used for serve actions
		 * @param call	the KalturaCall that defines the required parameters
		 * @return URLRequest with relevant parameters
		 * */
		public function getServeUrl(call:KalturaCall):URLRequest
		{
			var url : String = _config.protocol + _config.domain +"/"+_config.srvUrl+"?service="+call.service+"&action="+call.action;
			for (var key:String in call.args) {
				url += "&" + key + "=" + call.args[key];
			}
			var req:URLRequest = new URLRequest( url );
			req.contentType = "application/x-www-form-urlencoded";
			req.method = call.method;
//			req.data = call.args;
			return req;
		}
	}
}

fix for getServeUrl - add params to url

fwr: fix for getServeUrl - add params to url

git-svn-id: 8a2ccb88241e16c78017770bc38d91d6d5396a5a@70978 6b8eccd3-e8c5-4e7d-8186-e12b5326b719


package aerys.minko.scene.controller.light
{
	import aerys.minko.render.Viewport;
	import aerys.minko.scene.controller.AbstractController;
	import aerys.minko.scene.data.LightDataProvider;
	import aerys.minko.scene.node.Group;
	import aerys.minko.scene.node.ISceneNode;
	import aerys.minko.scene.node.Scene;
	import aerys.minko.scene.node.light.AbstractLight;
	import aerys.minko.type.binding.DataBindings;
	import aerys.minko.type.math.Matrix4x4;

	import flash.display.BitmapData;

	public class LightController extends AbstractController
	{
		private var _light 		    : AbstractLight;
		private var _lightData	    : LightDataProvider;
        private var _localToWorld   : Matrix4x4;
        private var _worldToLocal   : Matrix4x4;

		public function get lightData() : LightDataProvider
		{
			return _lightData;
		}

		protected function get light() : AbstractLight
		{
			return _light;
		}

		public function LightController(lightClass : Class)
		{
			super(lightClass);

			initialize();
		}

		private function initialize() : void
		{
			targetAdded.add(targetAddedHandler);
			targetRemoved.add(targetRemovedHandler);
		}

		protected function targetAddedHandler(ctrl	: LightController,
											 light	: AbstractLight) : void
		{
            _localToWorld = light.getLocalToWorldTransform();
            _worldToLocal = light.getWorldToLocalTransform();

			_lightData = new LightDataProvider(-1);
			_lightData.setLightProperty('type', light.type);
			_lightData.setLightProperty('localToWorld', _localToWorld);
			_lightData.setLightProperty('worldToLocal', _worldToLocal);
			_lightData.setLightProperty('enabled', true);
			_lightData.changed.add(lightDataChangedHandler);

			light.added.add(addedHandler);
			light.removed.add(removedHandler);

			_light = light;
		}

		protected function targetRemovedHandler(ctrl	: LightController,
											   light	: AbstractLight) : void
		{
			throw new Error();
		}

		protected function addedHandler(light		: AbstractLight,
										ancestor	: Group) : void
		{
//			_lightData.changed.add(lightDataChangedHandler);

			var scene : Scene = light.scene;

			if (scene)
				lightAddedToScene(scene);
//			if (!scene.enterFrame.hasCallback(sceneEnterFrameHandler))
//				scene.enterFrame.add(sceneEnterFrameHandler);
		}

		protected function lightAddedToScene(scene : Scene) : void
		{
            _light.localToWorldTransformChanged.add(lightLocalToWorldTransformChangedHandler);

			sortLights(scene);
		}

		protected function removedHandler(light		: AbstractLight,
										  ancestor	: Group) : void
		{
//			_lightData.changed.remove(lightDataChangedHandler);

			var scene : Scene = ancestor.scene;

			if (scene)
				lightRemovedFromScene(scene);
//			if (!scene.enterFrame.hasCallback(sceneEnterFrameHandler))
//				scene.enterFrame.add(sceneEnterFrameHandler);
		}

		protected function lightRemovedFromScene(scene : Scene) : void
		{
            lightLocalToWorldTransformChangedHandler(_light, null);
            _light.localToWorldTransformChanged.remove(lightLocalToWorldTransformChangedHandler);

			sortLights(scene);
		}

		protected function lightDataChangedHandler(lightData	: LightDataProvider,
												   propertyName	: String) : void
		{
			// nothing
		}

		private function updateDataProvider(lightId : int) : void
		{
			var newDataProvider : LightDataProvider = new LightDataProvider(lightId);

			for (var propertyName : String in _lightData.dataDescriptor)
			{
				propertyName = LightDataProvider.getPropertyName(propertyName);

				newDataProvider.setLightProperty(
					propertyName,
					_lightData.getLightProperty(propertyName)
				);
			}

            if (_lightData.changed.hasCallback(lightDataChangedHandler))
            {
    			_lightData.changed.remove(lightDataChangedHandler);
    			_lightData = newDataProvider;
    			_lightData.changed.add(lightDataChangedHandler);
            }
		}

        protected function lightLocalToWorldTransformChangedHandler(light         : AbstractLight,
                                                                    localToWorld  : Matrix4x4) : void
        {
            _localToWorld.copyFrom(localToWorld);

            // use getWorldToLocalTransform() in order to update the cache and save
            // possible future computations of world to local
            light.getWorldToLocalTransform(false, _worldToLocal);
        }

		private static function sceneEnterFrameHandler(scene		: Scene,
													   viewport		: Viewport,
													   destination	: BitmapData,
													   time			: uint) : void
		{
			scene.enterFrame.remove(sceneEnterFrameHandler);

			sortLights(scene);
		}

		private static function sortLights(scene : Scene) : void
		{
			var sceneBindings	: DataBindings			= scene.bindings;
			var lights			: Vector.<ISceneNode>	= scene.getDescendantsByType(AbstractLight);
			var numLights		: uint					= lights.length;
			var lightId			: uint;

			// remove all lights from scene bindings.
			var numProviders	: uint					= sceneBindings.numProviders;
			for (var providerId : int = numProviders - 1; providerId >= 0; --providerId)
			{
				var provider : LightDataProvider = sceneBindings.getProviderAt(providerId)
					as LightDataProvider;

				if (provider)
					sceneBindings.removeProvider(provider);
			}

			// sorting allows to limit the number of shaders that will be generated
			// if (add|remov)ing many lights all the time (add order won't matter anymore).
			lights.sort(compare);

			// update all descriptors.
			for (lightId = 0; lightId < numLights; ++lightId)
			{
				var light 	: AbstractLight 	= lights[lightId] as AbstractLight;
				var ctrl 	: LightController 	= light.getControllersByType(LightController)[0]
					as LightController;

				ctrl.updateDataProvider(lightId);
				sceneBindings.addProvider(ctrl._lightData);
			}
		}

		private static function compare(light1 : AbstractLight, light2 : AbstractLight) : int
		{
			return light1.type - light2.type;
		}

		override public function clone() : AbstractController
		{
			return new LightController(null);
		}
	}
}

package aerys.minko.scene.controller.light
{
	import aerys.minko.render.Viewport;
	import aerys.minko.scene.controller.AbstractController;
	import aerys.minko.scene.data.LightDataProvider;
	import aerys.minko.scene.node.Group;
	import aerys.minko.scene.node.ISceneNode;
	import aerys.minko.scene.node.Scene;
	import aerys.minko.scene.node.light.AbstractLight;
	import aerys.minko.type.binding.DataBindings;
	import aerys.minko.type.math.Matrix4x4;

	import flash.display.BitmapData;

	public class LightController extends AbstractController
	{
		private var _light 		    : AbstractLight;
		private var _lightData	    : LightDataProvider;
        private var _localToWorld   : Matrix4x4;
        private var _worldToLocal   : Matrix4x4;

		public function get lightData() : LightDataProvider
		{
			return _lightData;
		}

		protected function get light() : AbstractLight
		{
			return _light;
		}

		public function LightController(lightClass : Class)
		{
			super(lightClass);

			initialize();
		}

		private function initialize() : void
		{
			targetAdded.add(targetAddedHandler);
			targetRemoved.add(targetRemovedHandler);
		}

		protected function targetAddedHandler(ctrl	: LightController,
											 light	: AbstractLight) : void
		{
            _localToWorld = light.getLocalToWorldTransform();
            _worldToLocal = light.getWorldToLocalTransform();

			_lightData = new LightDataProvider(-1);
			_lightData.setLightProperty('type', light.type);
			_lightData.setLightProperty('localToWorld', _localToWorld);
			_lightData.setLightProperty('worldToLocal', _worldToLocal);
			_lightData.setLightProperty('enabled', true);
			_lightData.changed.add(lightDataChangedHandler);

			light.added.add(addedHandler);
			light.removed.add(removedHandler);

			_light = light;
		}

		protected function targetRemovedHandler(ctrl	: LightController,
											   light	: AbstractLight) : void
		{
			throw new Error();
		}

		protected function addedHandler(light		: AbstractLight,
										ancestor	: Group) : void
		{
//			_lightData.changed.add(lightDataChangedHandler);

			var scene : Scene = light.scene;

			if (scene)
				lightAddedToScene(scene);
//			if (!scene.enterFrame.hasCallback(sceneEnterFrameHandler))
//				scene.enterFrame.add(sceneEnterFrameHandler);
		}

		protected function lightAddedToScene(scene : Scene) : void
		{
            _light.localToWorldTransformChanged.add(lightLocalToWorldTransformChangedHandler);

			sortLights(scene);
		}

		protected function removedHandler(light		: AbstractLight,
										  ancestor	: Group) : void
		{
//			_lightData.changed.remove(lightDataChangedHandler);

			var scene : Scene = ancestor.scene;

			if (scene)
				lightRemovedFromScene(scene);
//			if (!scene.enterFrame.hasCallback(sceneEnterFrameHandler))
//				scene.enterFrame.add(sceneEnterFrameHandler);
		}

		protected function lightRemovedFromScene(scene : Scene) : void
		{
            _light.localToWorldTransformChanged.remove(lightLocalToWorldTransformChangedHandler);

			sortLights(scene);
		}

		protected function lightDataChangedHandler(lightData	: LightDataProvider,
												   propertyName	: String) : void
		{
			// nothing
		}

		private function updateDataProvider(lightId : int) : void
		{
			var newDataProvider : LightDataProvider = new LightDataProvider(lightId);

			for (var propertyName : String in _lightData.dataDescriptor)
			{
				propertyName = LightDataProvider.getPropertyName(propertyName);

				newDataProvider.setLightProperty(
					propertyName,
					_lightData.getLightProperty(propertyName)
				);
			}

            if (_lightData.changed.hasCallback(lightDataChangedHandler))
            {
    			_lightData.changed.remove(lightDataChangedHandler);
    			_lightData = newDataProvider;
    			_lightData.changed.add(lightDataChangedHandler);
            }
		}

        protected function lightLocalToWorldTransformChangedHandler(light         : AbstractLight,
                                                                    localToWorld  : Matrix4x4) : void
        {
            _localToWorld.copyFrom(localToWorld);

            // use getWorldToLocalTransform() in order to update the cache and save
            // possible future computations of world to local
            light.getWorldToLocalTransform(false, _worldToLocal);
        }

		private static function sceneEnterFrameHandler(scene		: Scene,
													   viewport		: Viewport,
													   destination	: BitmapData,
													   time			: uint) : void
		{
			scene.enterFrame.remove(sceneEnterFrameHandler);

			sortLights(scene);
		}

		private static function sortLights(scene : Scene) : void
		{
			var sceneBindings	: DataBindings			= scene.bindings;
			var lights			: Vector.<ISceneNode>	= scene.getDescendantsByType(AbstractLight);
			var numLights		: uint					= lights.length;
			var lightId			: uint;

			// remove all lights from scene bindings.
			var numProviders	: uint					= sceneBindings.numProviders;
			for (var providerId : int = numProviders - 1; providerId >= 0; --providerId)
			{
				var provider : LightDataProvider = sceneBindings.getProviderAt(providerId)
					as LightDataProvider;

				if (provider)
					sceneBindings.removeProvider(provider);
			}

			// sorting allows to limit the number of shaders that will be generated
			// if (add|remov)ing many lights all the time (add order won't matter anymore).
			lights.sort(compare);

			// update all descriptors.
			for (lightId = 0; lightId < numLights; ++lightId)
			{
				var light 	: AbstractLight 	= lights[lightId] as AbstractLight;
				var ctrl 	: LightController 	= light.getControllersByType(LightController)[0]
					as LightController;

				ctrl.updateDataProvider(lightId);
				sceneBindings.addProvider(ctrl._lightData);
			}
		}

		private static function compare(light1 : AbstractLight, light2 : AbstractLight) : int
		{
			return light1.type - light2.type;
		}

		override public function clone() : AbstractController
		{
			return new LightController(null);
		}
	}
}

remove useless (and broken) call to lightLocalToWorldTransformChangedHandler() in LightController.lightRemovedFromScene()


/**
 WvNetConnection
 version 1.1
 03/08/2010
 Widevine extension to the NetConnection class.  Required to stream encrypted content.
**/

package com.widevine
{
	import flash.events.*;
	import flash.external.ExternalInterface;
	import flash.net.NetConnection;
	import flash.net.Responder;

	public class WvNetConnection extends NetConnection
	{
		// URL passed in by swf
		private var myOrigURL:String;
		// URL used during connect() call.
		private var myNewURL:String;
		private var myMovie:String;
		private var myErrorText:String;
		private var myIsConnected:Boolean;
		private var myMediaTime:Number;
		private var myPlayScale:Number;
		// bypass Widevine client
		private var myIsBypassMode:Boolean;
		// progressive download
		private var myIsPdl:Boolean;

		public static const DO_CONNECT_FAILED:String = "doConnectFailed";

		public function WvNetConnection():void
		{
			this.objectEncoding = flash.net.ObjectEncoding.AMF0;
			myIsConnected 	= false;
			myIsBypassMode 	= false;
			myIsPdl	   		= true;
			myMediaTime 	= 0;
			myPlayScale 	= 1;
		}
		///////////////////////////////////////////////////////////////////////////
		public override function connect(command:String, ... arguments):void
		{
			// handle RTMP streaming, not supported in Widevine yet
			if (command.substr(0, 4) == "rtmp") {
				myIsPdl = false;
			}

			if (IsBypassMode()) {
				try {
					if (IsPdl()) {
						//trace("(bypass) Handling HTTP stream:" + command);
						super.connect(null);
						myNewURL = command;
					}
					else {
						// RTMP streaming
						//trace("(bypass) Handling RTMP stream:" + command);
						myNewURL = command.substring(0, command.lastIndexOf("/")+1);
						super.connect(myNewURL);
					}
				}
				catch (e:Error) {
					//dispatchEvent(new NetStatusEvent("onNetStatus", obj));
					//trace("WvNetStream.connect() error:" + e.message);
					throw new Error("connect() failed");
				}
				return;
			}

			// Widevine encrypted stream only
			if (IsPdl()) {
				//trace("(Handling Wv HTTP stream:" + command);
			}
			else {
				command = command.substring(0, command.lastIndexOf("/")+1);
				//trace("(Handling Wv RTMP stream:" + command);
			}
			if (doConnect(command) != 0) {
				//dispatchEvent(new NetStatusEvent("netStatus", obj));
				//throw new Error("doConnect() failed");
				dispatchEvent(new Event(DO_CONNECT_FAILED));
			}
		}
		///////////////////////////////////////////////////////////////////////////
		private function doConnect(theURL:String):Number
		{
			myOrigURL = theURL;
			if (myOrigURL == null) {
				myErrorText = "url passed in connect() is null";
				return 1;
			}

			try {
				myMovie = theURL.substr(myOrigURL.lastIndexOf("/")+1);
				myNewURL = String(ExternalInterface.call("WVGetURL", myOrigURL));

				if (myNewURL.toLowerCase().indexOf("error:") != -1) {
					myErrorText = myNewURL;
					return 2;
				}
			}
			catch (errObject:Error) {
				myErrorText = "WVGetURL() failed. " + errObject.message;
			}
			try {
				//trace("Calling super.connect()");
				super.connect(myNewURL);
			}
			catch (errObject:Error) {
				myErrorText = "super.connect() failed. " + errObject.message;
			}
			myIsConnected = true;
			return 0;
		}
		///////////////////////////////////////////////////////////////////////////
		public override function close():void
		{
			trace("WvNetConnection.close()");
			myIsConnected = false;
			super.close();
		}
		///////////////////////////////////////////////////////////////////////////
		public function getErrorText():String
		{
			return myErrorText;
		}
		///////////////////////////////////////////////////////////////////////////
		public function getNewURL():String
		{
			return myNewURL;
		}
		///////////////////////////////////////////////////////////////////////////
		public function isConnected():Boolean
		{
			return myIsConnected;
		}
		///////////////////////////////////////////////////////////////////////////
		public function setBypassMode(flag:Boolean):void
		{
			myIsBypassMode = flag;
		}
		///////////////////////////////////////////////////////////////////////////
		public function IsBypassMode():Boolean
		{
			return myIsBypassMode;
		}
		///////////////////////////////////////////////////////////////////////////
		public function IsPdl():Boolean
		{
			return myIsPdl;
		}
	}  // class
}  // package

/**
 WvNetConnection
 version 1.1
 03/08/2010
 Widevine extension to the NetConnection class.  Required to stream encrypted content.
**/

package com.widevine
{
	import com.kaltura.kdpfl.view.controls.KTrace;

	import flash.events.*;
	import flash.external.ExternalInterface;
	import flash.net.NetConnection;
	import flash.net.Responder;
	import flash.utils.setTimeout;

	public class WvNetConnection extends NetConnection
	{
		// URL passed in by swf
		private var myOrigURL:String;
		// URL used during connect() call.
		private var myNewURL:String;
		private var myMovie:String;
		private var myErrorText:String;
		private var myIsConnected:Boolean;
		private var myMediaTime:Number;
		private var myPlayScale:Number;
		// bypass Widevine client
		private var myIsBypassMode:Boolean;
		// progressive download
		private var myIsPdl:Boolean;

		public static const DO_CONNECT_FAILED:String = "doConnectFailed";

		public function WvNetConnection():void
		{
			this.objectEncoding = flash.net.ObjectEncoding.AMF0;
			myIsConnected 	= false;
			myIsBypassMode 	= false;
			myIsPdl	   		= true;
			myMediaTime 	= 0;
			myPlayScale 	= 1;
		}
		///////////////////////////////////////////////////////////////////////////
		public override function connect(command:String, ... arguments):void
		{
			// handle RTMP streaming, not supported in Widevine yet
			if (command.substr(0, 4) == "rtmp") {
				myIsPdl = false;
			}

			if (IsBypassMode()) {
				try {
					if (IsPdl()) {
						//trace("(bypass) Handling HTTP stream:" + command);
						super.connect(null);
						myNewURL = command;
					}
					else {
						// RTMP streaming
						//trace("(bypass) Handling RTMP stream:" + command);
						myNewURL = command.substring(0, command.lastIndexOf("/")+1);
						super.connect(myNewURL);
					}
				}
				catch (e:Error) {
					//dispatchEvent(new NetStatusEvent("onNetStatus", obj));
					//trace("WvNetStream.connect() error:" + e.message);
					throw new Error("connect() failed");
				}
				return;
			}

			// Widevine encrypted stream only
			if (IsPdl()) {
				//trace("(Handling Wv HTTP stream:" + command);
			}
			else {
				command = command.substring(0, command.lastIndexOf("/")+1);
				//trace("(Handling Wv RTMP stream:" + command);
			}

			if (doConnect(command) != 0) {
				setTimeout(function():void{
					if (doConnect(command) != 0) {
						//dispatchEvent(new NetStatusEvent("netStatus", obj));
						//throw new Error("doConnect() failed");
						dispatchEvent(new Event(DO_CONNECT_FAILED));
						}
					},1500);
			}

		}

		//private function

		///////////////////////////////////////////////////////////////////////////
		private function doConnect(theURL:String):Number
		{
			myOrigURL = theURL;
			if (myOrigURL == null) {
				myErrorText = "url passed in connect() is null";
				KTrace.getInstance().log("doConnect" + myErrorText);
				return 1;
			}

			try {
				myMovie = theURL.substr(myOrigURL.lastIndexOf("/")+1);
				myNewURL = String(ExternalInterface.call("WVGetURL", myOrigURL));

				if (myNewURL.toLowerCase().indexOf("error:") != -1) {
					myErrorText = myNewURL;
					KTrace.getInstance().log("doConnect"  + myErrorText);

					return 2;
				}
			}
			catch (errObject:Error) {

				myErrorText = "WVGetURL() failed. " + errObject.message;
				KTrace.getInstance().log("doConnect"  + myErrorText);

			}
			try {
				//trace("Calling super.connect()");
				super.connect(myNewURL);
			}
			catch (errObject:Error) {

				myErrorText = "super.connect() failed. " + errObject.message;
				KTrace.getInstance().log("doConnect" + myErrorText);
			}
			myIsConnected = true;
			return 0;
		}
		///////////////////////////////////////////////////////////////////////////
		public override function close():void
		{
			trace("WvNetConnection.close()");
			myIsConnected = false;
			super.close();
		}
		///////////////////////////////////////////////////////////////////////////
		public function getErrorText():String
		{
			return myErrorText;
		}
		///////////////////////////////////////////////////////////////////////////
		public function getNewURL():String
		{
			return myNewURL;
		}
		///////////////////////////////////////////////////////////////////////////
		public function isConnected():Boolean
		{
			return myIsConnected;
		}
		///////////////////////////////////////////////////////////////////////////
		public function setBypassMode(flag:Boolean):void
		{
			myIsBypassMode = flag;
		}
		///////////////////////////////////////////////////////////////////////////
		public function IsBypassMode():Boolean
		{
			return myIsBypassMode;
		}
		///////////////////////////////////////////////////////////////////////////
		public function IsPdl():Boolean
		{
			return myIsPdl;
		}
	}  // class
}  // package

fix widevine on mac - add timeout on doconnect if falis(retry)


//
// $Id$
//
// Vilya library - tools for developing networked games
// Copyright (C) 2002-2007 Three Rings Design, Inc., All Rights Reserved
// http://www.threerings.net/code/vilya/
//
// This library is free software; you can redistribute it and/or modify it
// under the terms of the GNU Lesser General Public License as published
// by the Free Software Foundation; either version 2.1 of the License, or
// (at your option) any later version.
//
// This library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
// Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public
// License along with this library; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA

package com.threerings.ezgame.client {

import mx.core.UIComponent;
import mx.controls.CheckBox;
import mx.controls.ComboBox;
import mx.controls.HSlider;
import mx.controls.Label;

import com.threerings.util.StringUtil;

import com.threerings.flex.LabeledSlider;

import com.threerings.parlor.game.client.FlexGameConfigurator;

import com.threerings.ezgame.data.EZGameConfig;

/**
 * Adds custom configuration of options specified in XML.
 */
public class EZGameConfigurator extends FlexGameConfigurator
{
    /**
     * Set a String of configData, which is hopefully XML formatted with
     * some game configuration options.
     */
    public function setXMLConfig (configData :String) :void
    {
        var log :Log = Log.getLog(this);

        var config :XML = XML(configData);
        for each (var param :XML in config..params.children()) {
            if (StringUtil.isBlank(param.@ident)) {
                log.warning("Bad configuration option: no 'ident' in " +
                    param + ", ignoring.");
                continue;
            }
            switch (param.localName().toLowerCase()) {
            case "range":
                addRangeOption(param, log);
                break;

            case "choice":
                addChoiceOption(param, log);
                break;

            case "toggle":
                addToggleOption(param, log);
                break;

            default:
                log.warning("Unknown XML configuration option: " + param +
                    ", ignoring.");
                break;
            }
        }
    }

    /**
     * Add a control that came from parsing our custom option XML.
     */
    protected function addXMLControl (ident :String, control :UIComponent) :void
    {
        var name :String = ident.replace("_", " ");
        var lbl :Label = new Label();
        lbl.text = name + ":";
        addControl(lbl, control);

        _customConfigs.push(ident, control);
    }

    /**
     * Adds a 'range' option specified in XML
     */
    protected function addRangeOption (spec :XML, log :Log) :void
    {
        var min :Number = Number(spec.@minimum);
        var max :Number = Number(spec.@maximum);
        var start :Number = Number(spec.@start);
        if (isNaN(min) || isNaN(max) || isNaN(start)) {
            log.warning("Unable to parse range specification. " +
                "Required numeric values: minimum, maximum, start " +
                "[xml=" + spec + "].");
            return;
        }

        var slider :HSlider = new HSlider();
        slider.minimum = min;
        slider.maximum = max;
        slider.value = start;
        slider.liveDragging = true;
        slider.snapInterval = 1;

        addXMLControl(spec.@ident, new LabeledSlider(slider));
    }

    /**
     * Adds a 'choice' option specified in XML
     */
    protected function addChoiceOption (spec :XML, log :Log) :void
    {
        if (spec.@choices.length == 0 || spec.@start.length == 0) {
            log.warning("Unable to parse choice specification. " +
                "Required 'choices' or 'start' is missing. " +
                "[xml=" + spec + "].");
            return;
        }

        var choiceString :String = String(spec.@choices);
        var start :String = String(spec.@start);

        var choices :Array = choiceString.split(",");
        var startDex :int = choices.indexOf(start);
        if (startDex == -1) {
            log.warning("Choice start value does not appear in list of choices "+
                "[xml=" + spec + "].");
            return;
        }

        var box :ComboBox = new ComboBox();
        box.dataProvider = choices;
        box.selectedIndex = startDex;

        addXMLControl(spec.@ident, box);
    }

    /**
     * Adds a 'toggle' option specified in XML
     */
    protected function addToggleOption (spec :XML, log :Log) :void
    {
        if (spec.@start.length == 0) {
            log.warning("Unable to parse toggle specification. " +
                "Required 'start' is missing. " +
                "[xml=" + spec + "].");
            return;
        }

        var startStr :String = String(spec.@start).toLowerCase();

        var box :CheckBox = new CheckBox();
        box.selected = ("true" == startStr);

        addXMLControl(spec.@ident, box);
    }

    override protected function flushGameConfig () :void
    {
        super.flushGameConfig();

        // if there were any custom XML configs, flush those as well.
        if (_customConfigs.length > 0) {
            var options :Object = {};

            for (var ii :int = 0; ii < _customConfigs.length; ii += 2) {
                var ident :String = String(_customConfigs[ii]);
                var control :UIComponent = (_customConfigs[ii + 1] as UIComponent);
                if (control is LabeledSlider) {
                    options[ident] = (control as LabeledSlider).slider.value;

                } else if (control is CheckBox) {
                    options[ident] = (control as CheckBox).selected;

                } else if (control is ComboBox) {
                    options[ident] = (control as ComboBox).value;

                } else {
                    Log.getLog(this).warning("Unknow custom config type " + control);
                }
            }

            (_config as EZGameConfig).customConfig = options;
        }
    }

    /** Contains pairs of identString, control, identString, control.. */
    protected var _customConfigs :Array = [];
}
}

//
// $Id$
//
// Vilya library - tools for developing networked games
// Copyright (C) 2002-2007 Three Rings Design, Inc., All Rights Reserved
// http://www.threerings.net/code/vilya/
//
// This library is free software; you can redistribute it and/or modify it
// under the terms of the GNU Lesser General Public License as published
// by the Free Software Foundation; either version 2.1 of the License, or
// (at your option) any later version.
//
// This library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
// Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public
// License along with this library; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA

package com.threerings.ezgame.client {

import mx.core.UIComponent;
import mx.controls.CheckBox;
import mx.controls.ComboBox;
import mx.controls.HSlider;
import mx.controls.Label;

import com.threerings.util.StringUtil;

import com.threerings.flex.LabeledSlider;

import com.threerings.parlor.game.client.FlexGameConfigurator;

import com.threerings.ezgame.data.EZGameConfig;

/**
 * Adds custom configuration of options specified in XML.
 */
public class EZGameConfigurator extends FlexGameConfigurator
{
    /**
     * Set a String of configData, which is hopefully XML formatted with
     * some game configuration options.
     */
    public function setXMLConfig (configData :String) :void
    {
        var log :Log = Log.getLog(this);

        var config :XML = XML(configData);
        for each (var param :XML in config..params.children()) {
            if (StringUtil.isBlank(param.@ident)) {
                log.warning("Bad configuration option: no 'ident' in " +
                    param + ", ignoring.");
                continue;
            }
            switch (param.localName().toLowerCase()) {
            case "range":
                addRangeOption(param, log);
                break;

            case "choice":
                addChoiceOption(param, log);
                break;

            case "toggle":
                addToggleOption(param, log);
                break;

            default:
                log.warning("Unknown XML configuration option: " + param +
                    ", ignoring.");
                break;
            }
        }
    }

    /**
     * Add a control that came from parsing our custom option XML.
     */
    protected function addXMLControl (spec :XML, control :UIComponent) :void
    {
        var ident :String = String(spec.@ident);
        var tip :String = StringUtil.trim(String(spec.text()));

        var lbl :Label = new Label();
        lbl.text = ident + ":";
        lbl.toolTip = tip;
        control.toolTip = tip;

        addControl(lbl, control);
        _customConfigs.push(ident, control);
    }

    /**
     * Adds a 'range' option specified in XML
     */
    protected function addRangeOption (spec :XML, log :Log) :void
    {
        var min :Number = Number(spec.@minimum);
        var max :Number = Number(spec.@maximum);
        var start :Number = Number(spec.@start);
        if (isNaN(min) || isNaN(max) || isNaN(start)) {
            log.warning("Unable to parse range specification. " +
                "Required numeric values: minimum, maximum, start " +
                "[xml=" + spec + "].");
            return;
        }

        var slider :HSlider = new HSlider();
        slider.minimum = min;
        slider.maximum = max;
        slider.value = start;
        slider.liveDragging = true;
        slider.snapInterval = 1;

        addXMLControl(spec, new LabeledSlider(slider));
    }

    /**
     * Adds a 'choice' option specified in XML
     */
    protected function addChoiceOption (spec :XML, log :Log) :void
    {
        if (spec.@choices.length == 0 || spec.@start.length == 0) {
            log.warning("Unable to parse choice specification. " +
                "Required 'choices' or 'start' is missing. " +
                "[xml=" + spec + "].");
            return;
        }

        var choiceString :String = String(spec.@choices);
        var start :String = String(spec.@start);

        var choices :Array = choiceString.split(",");
        var startDex :int = choices.indexOf(start);
        if (startDex == -1) {
            log.warning("Choice start value does not appear in list of choices "+
                "[xml=" + spec + "].");
            return;
        }

        var box :ComboBox = new ComboBox();
        box.dataProvider = choices;
        box.selectedIndex = startDex;

        addXMLControl(spec, box);
    }

    /**
     * Adds a 'toggle' option specified in XML
     */
    protected function addToggleOption (spec :XML, log :Log) :void
    {
        if (spec.@start.length == 0) {
            log.warning("Unable to parse toggle specification. " +
                "Required 'start' is missing. " +
                "[xml=" + spec + "].");
            return;
        }

        var startStr :String = String(spec.@start).toLowerCase();

        var box :CheckBox = new CheckBox();
        box.selected = ("true" == startStr);

        addXMLControl(spec, box);
    }

    override protected function flushGameConfig () :void
    {
        super.flushGameConfig();

        // if there were any custom XML configs, flush those as well.
        if (_customConfigs.length > 0) {
            var options :Object = {};

            for (var ii :int = 0; ii < _customConfigs.length; ii += 2) {
                var ident :String = String(_customConfigs[ii]);
                var control :UIComponent = (_customConfigs[ii + 1] as UIComponent);
                if (control is LabeledSlider) {
                    options[ident] = (control as LabeledSlider).slider.value;

                } else if (control is CheckBox) {
                    options[ident] = (control as CheckBox).selected;

                } else if (control is ComboBox) {
                    options[ident] = (control as ComboBox).value;

                } else {
                    Log.getLog(this).warning("Unknow custom config type " + control);
                }
            }

            (_config as EZGameConfig).customConfig = options;
        }
    }

    /** Contains pairs of identString, control, identString, control.. */
    protected var _customConfigs :Array = [];
}
}

Allow text inside the <range><choice> and <toggle> tags to contain
the tooltip for a game configuration.
- Removed ident translation of _ to ' ' for display purposes. Just use
 a nice name.

git-svn-id: a3e1eb16dde062992de22c830ed8045c8013209a@246 c613c5cb-e716-0410-b11b-feb51c14d237


////////////////////////////////////////////////////////////////////////////////
// Copyright (c) 2008-2013 Esri. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//    http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
////////////////////////////////////////////////////////////////////////////////
package com.esri.builder.controllers
{

import com.esri.ags.components.IdentityManager;
import com.esri.ags.components.supportClasses.Credential;
import com.esri.ags.events.IdentityManagerEvent;
import com.esri.ags.events.PortalEvent;
import com.esri.ags.portal.Portal;
import com.esri.builder.eventbus.AppEvent;
import com.esri.builder.model.Model;
import com.esri.builder.model.PortalModel;
import com.esri.builder.supportClasses.LogUtil;
import com.esri.builder.supportClasses.URLUtil;

import mx.logging.ILogger;
import mx.logging.Log;
import mx.resources.ResourceManager;
import mx.rpc.Fault;
import mx.rpc.events.FaultEvent;

public class PortalController
{
    private static const LOG:ILogger = LogUtil.createLogger(PortalController);

    private var lastUsedCultureCode:String;

    private var portal:Portal = PortalModel.getInstance().portal;

    public function PortalController()
    {
        AppEvent.addListener(AppEvent.SETTINGS_SAVED, settingsChangeHandler);
    }

    private function identityManager_signInHandler(event:IdentityManagerEvent):void
    {
        if (PortalModel.getInstance().hasSameOrigin(event.credential.server)
            && !PortalModel.getInstance().portal.signedIn)
        {
            signIntoPortalInternally();
        }
    }

    private function portalSignOutHandler(event:AppEvent):void
    {
        if (Log.isInfo())
        {
            LOG.info("Portal sign out requested");
        }

        IdentityManager.instance.removeEventListener(IdentityManagerEvent.SIGN_IN, identityManager_signInHandler);
        portal.addEventListener(PortalEvent.LOAD, portal_loadHandler);
        portal.addEventListener(FaultEvent.FAULT, portal_faultHandler);
        portal.signOut();
    }

    private function portalSignInHandler(event:AppEvent):void
    {
        if (Log.isInfo())
        {
            LOG.info("Portal sign in requested");
        }

        signIntoPortalInternally();
    }

    private function signIntoPortalInternally():void
    {
        IdentityManager.instance.removeEventListener(IdentityManagerEvent.SIGN_IN, identityManager_signInHandler);
        portal.addEventListener(PortalEvent.LOAD, portal_loadHandler);
        portal.addEventListener(FaultEvent.FAULT, portal_faultHandler);
        portal.signIn();
    }

    private function settingsChangeHandler(event:AppEvent):void
    {
        //optimization: AGO uses OAuth, so we register the OAuth info upfront
        var portalModel:PortalModel = PortalModel.getInstance();
        var portalURL:String = portalModel.portalURL;
        if (portalModel.isAGO(portalURL))
        {
            portalModel.registerOAuthPortal(portalURL);
        }

        loadPortal(portalURL, Model.instance.cultureCode);
    }

    private function loadPortal(url:String, cultureCode:String):void
    {
        if (Log.isInfo())
        {
            LOG.info("Loading Portal");
        }

        if (url)
        {
            var hasPortalURLChanged:Boolean = (portal.url != url);
            var hasCultureCodeChanged:Boolean = (lastUsedCultureCode != cultureCode);
            if (hasPortalURLChanged || hasCultureCodeChanged)
            {
                if (Log.isDebug())
                {
                    LOG.debug("Reloading Portal - URL: {0}, culture code: {1}", url, cultureCode);
                }

                if (hasPortalURLChanged)
                {
                    portal.unload();
                }

                lastUsedCultureCode = cultureCode;
                portal.culture = cultureCode;
                portal.url = URLUtil.removeToken(url);

                IdentityManager.instance.removeEventListener(IdentityManagerEvent.SIGN_IN, identityManager_signInHandler);
                portal.addEventListener(PortalEvent.LOAD, portal_loadHandler);
                portal.addEventListener(FaultEvent.FAULT, portal_faultHandler);
                portal.load();
            }
        }
        else
        {
            unloadPortal();
            dispatchPortalStatusUpdate();
        }
    }

    private function unloadPortal():void
    {
        if (Log.isDebug())
        {
            LOG.debug("Unloading Portal");
        }

        portal.url = null;
        portal.unload();
    }

    private function dispatchPortalStatusUpdate():void
    {
        updatePortalHandlersBasedOnStatus();
        AppEvent.dispatch(AppEvent.PORTAL_STATUS_UPDATED);
    }

    private function updatePortalHandlersBasedOnStatus():void
    {
        if (portal.signedIn)
        {
            AppEvent.addListener(AppEvent.PORTAL_SIGN_OUT, portalSignOutHandler);
            AppEvent.removeListener(AppEvent.PORTAL_SIGN_IN, portalSignInHandler);
            IdentityManager.instance.removeEventListener(IdentityManagerEvent.SIGN_IN, identityManager_signInHandler);
        }
        else
        {
            AppEvent.addListener(AppEvent.PORTAL_SIGN_IN, portalSignInHandler);
            IdentityManager.instance.addEventListener(IdentityManagerEvent.SIGN_IN, identityManager_signInHandler);
            AppEvent.removeListener(AppEvent.PORTAL_SIGN_OUT, portalSignOutHandler);
        }
    }

    private function portal_loadHandler(event:PortalEvent):void
    {
        if (Log.isDebug())
        {
            LOG.debug("Portal load success");
        }

        //do nothing, load successful
        portal.removeEventListener(PortalEvent.LOAD, portal_loadHandler);
        portal.removeEventListener(FaultEvent.FAULT, portal_faultHandler);

        dispatchPortalStatusUpdate();
    }

    private function portal_faultHandler(event:FaultEvent):void
    {
        if (Log.isDebug())
        {
            LOG.debug("Portal load fault");
        }

        portal.removeEventListener(PortalEvent.LOAD, portal_loadHandler);
        portal.removeEventListener(FaultEvent.FAULT, portal_faultHandler);

        var fault:Fault = event.fault;
        if (fault.faultCode == "403" && fault.faultString == "User not part of this account")
        {
            var credential:Credential = IdentityManager.instance.findCredential(portal.url);
            if (credential)
            {
                credential.destroy();
            }

            AppEvent.dispatch(
                AppEvent.SHOW_ERROR,
                ResourceManager.getInstance().getString("BuilderStrings", "notOrgMember"));
        }
        else
        {
            dispatchPortalStatusUpdate();
        }
    }
}
}

////////////////////////////////////////////////////////////////////////////////
// Copyright (c) 2008-2013 Esri. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//    http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
////////////////////////////////////////////////////////////////////////////////
package com.esri.builder.controllers
{

import com.esri.ags.components.IdentityManager;
import com.esri.ags.components.supportClasses.Credential;
import com.esri.ags.events.IdentityManagerEvent;
import com.esri.ags.events.PortalEvent;
import com.esri.ags.portal.Portal;
import com.esri.builder.eventbus.AppEvent;
import com.esri.builder.model.Model;
import com.esri.builder.model.PortalModel;
import com.esri.builder.supportClasses.LogUtil;
import com.esri.builder.supportClasses.URLUtil;

import mx.logging.ILogger;
import mx.logging.Log;
import mx.resources.ResourceManager;
import mx.rpc.Fault;
import mx.rpc.events.FaultEvent;

public class PortalController
{
    private static const LOG:ILogger = LogUtil.createLogger(PortalController);

    private var lastUsedCultureCode:String;

    private var portal:Portal = PortalModel.getInstance().portal;

    public function PortalController()
    {
        AppEvent.addListener(AppEvent.SETTINGS_SAVED, settingsChangeHandler);
    }

    private function settingsChangeHandler(event:AppEvent):void
    {
        //optimization: AGO uses OAuth, so we register the OAuth info upfront
        var portalModel:PortalModel = PortalModel.getInstance();
        var portalURL:String = portalModel.portalURL;
        if (portalModel.isAGO(portalURL))
        {
            portalModel.registerOAuthPortal(portalURL);
        }

        loadPortal(portalURL, Model.instance.cultureCode);
    }

    private function loadPortal(url:String, cultureCode:String):void
    {
        if (Log.isInfo())
        {
            LOG.info("Loading Portal");
        }

        if (url)
        {
            var hasPortalURLChanged:Boolean = (portal.url != url);
            var hasCultureCodeChanged:Boolean = (lastUsedCultureCode != cultureCode);
            if (hasPortalURLChanged || hasCultureCodeChanged)
            {
                if (Log.isDebug())
                {
                    LOG.debug("Reloading Portal - URL: {0}, culture code: {1}", url, cultureCode);
                }

                if (hasPortalURLChanged)
                {
                    portal.unload();
                }

                lastUsedCultureCode = cultureCode;
                portal.culture = cultureCode;
                portal.url = URLUtil.removeToken(url);

                IdentityManager.instance.removeEventListener(IdentityManagerEvent.SIGN_IN, identityManager_signInHandler);
                portal.addEventListener(PortalEvent.LOAD, portal_loadHandler);
                portal.addEventListener(FaultEvent.FAULT, portal_faultHandler);
                portal.load();
            }
        }
        else
        {
            unloadPortal();
            dispatchPortalStatusUpdate();
        }
    }

    private function identityManager_signInHandler(event:IdentityManagerEvent):void
    {
        if (PortalModel.getInstance().hasSameOrigin(event.credential.server)
            && !PortalModel.getInstance().portal.signedIn)
        {
            signIntoPortalInternally();
        }
    }

    private function signIntoPortalInternally():void
    {
        IdentityManager.instance.removeEventListener(IdentityManagerEvent.SIGN_IN, identityManager_signInHandler);
        portal.addEventListener(PortalEvent.LOAD, portal_loadHandler);
        portal.addEventListener(FaultEvent.FAULT, portal_faultHandler);
        portal.signIn();
    }

    private function portal_loadHandler(event:PortalEvent):void
    {
        if (Log.isDebug())
        {
            LOG.debug("Portal load success");
        }

        //do nothing, load successful
        portal.removeEventListener(PortalEvent.LOAD, portal_loadHandler);
        portal.removeEventListener(FaultEvent.FAULT, portal_faultHandler);

        dispatchPortalStatusUpdate();
    }

    private function portal_faultHandler(event:FaultEvent):void
    {
        if (Log.isDebug())
        {
            LOG.debug("Portal load fault");
        }

        portal.removeEventListener(PortalEvent.LOAD, portal_loadHandler);
        portal.removeEventListener(FaultEvent.FAULT, portal_faultHandler);

        var fault:Fault = event.fault;
        if (fault.faultCode == "403" && fault.faultString == "User not part of this account")
        {
            var credential:Credential = IdentityManager.instance.findCredential(portal.url);
            if (credential)
            {
                credential.destroy();
            }

            AppEvent.dispatch(
                AppEvent.SHOW_ERROR,
                ResourceManager.getInstance().getString("BuilderStrings", "notOrgMember"));
        }
        else
        {
            dispatchPortalStatusUpdate();
        }
    }

    private function unloadPortal():void
    {
        if (Log.isDebug())
        {
            LOG.debug("Unloading Portal");
        }

        portal.url = null;
        portal.unload();
    }

    private function dispatchPortalStatusUpdate():void
    {
        updatePortalHandlersBasedOnStatus();
        AppEvent.dispatch(AppEvent.PORTAL_STATUS_UPDATED);
    }

    private function updatePortalHandlersBasedOnStatus():void
    {
        if (portal.signedIn)
        {
            AppEvent.addListener(AppEvent.PORTAL_SIGN_OUT, portalSignOutHandler);
            AppEvent.removeListener(AppEvent.PORTAL_SIGN_IN, portalSignInHandler);
            IdentityManager.instance.removeEventListener(IdentityManagerEvent.SIGN_IN, identityManager_signInHandler);
        }
        else
        {
            AppEvent.addListener(AppEvent.PORTAL_SIGN_IN, portalSignInHandler);
            IdentityManager.instance.addEventListener(IdentityManagerEvent.SIGN_IN, identityManager_signInHandler);
            AppEvent.removeListener(AppEvent.PORTAL_SIGN_OUT, portalSignOutHandler);
        }
    }

    private function portalSignOutHandler(event:AppEvent):void
    {
        if (Log.isInfo())
        {
            LOG.info("Portal sign out requested");
        }

        IdentityManager.instance.removeEventListener(IdentityManagerEvent.SIGN_IN, identityManager_signInHandler);
        portal.addEventListener(PortalEvent.LOAD, portal_loadHandler);
        portal.addEventListener(FaultEvent.FAULT, portal_faultHandler);
        portal.signOut();
    }

    private function portalSignInHandler(event:AppEvent):void
    {
        if (Log.isInfo())
        {
            LOG.info("Portal sign in requested");
        }

        signIntoPortalInternally();
    }
}
}

Move methods closer to their usage.

Move methods closer to their usage.


			import flash.events.MouseEvent;
			import com.example.programmingas3.playlist.PlayList;
			import com.example.programmingas3.playlist.Song;
			import com.example.programmingas3.playlist.SortProperty;

			// constants for the different "states" of the song form
			private static const ADD_SONG:uint = 1;
			private static const SONG_DETAIL:uint = 2;

			private var playList:PlayList = new PlayList();

			private function initApp():void
			{
				// set the initial state of the song form, for adding a new song
				setFormState(ADD_SONG);

				// prepopulate the list with a few songs
				playList.addSong(new Song("Nessun Dorma", "Luciano Pavarotti", 1990, "nessundorma.mp3", ["90's", "Opera"]));
				playList.addSong(new Song("Come Undone", "Duran Duran", 1993, "comeundone.mp3", ["90's", "Pop"]));
				playList.addSong(new Song("Think of Me", "Sarah Brightman", 1987, "thinkofme.mp3", ["Showtunes"]));
				playList.addSong(new Song("Unbelievable", "EMF", 1991, "unbelievable.mp3", ["90's", "Pop"]));

				songList.dataProvider = playList.songList;
			}

			private function sortList(sortField:SortProperty):void
			{
				// Make all the sort type buttons enabled.
				// The active one will be grayed-out below
				sortByTitle.selected = false;
				sortByArtist.selected = false;
				sortByYear.selected = false;

				switch (sortField)
				{
					case SortProperty.TITLE:
						sortByTitle.selected = true;
						break;
					case SortProperty.ARTIST:
						sortByArtist.selected = true;
						break;
					case SortProperty.YEAR:
						sortByYear.selected = true;
						break;
				}

				playList.sortList(sortField);

				refreshList();
			}

			private function refreshList():void
			{
				// remember which song was selected
				var selectedSong:Song = Song(songList.selectedItem);

				// re-assign the song list as the dataprovider to get the newly sorted list
				// and force the List control to refresh itself
				songList.dataProvider = playList.songList;

				// reset the song selection
				if (selectedSong != null)
				{
					songList.selectedItem = selectedSong;
				}
			}

			private function songSelectionChange():void
			{
				if (songList.selectedIndex != -1)
				{
					setFormState(SONG_DETAIL);
				}
				else
				{
					setFormState(ADD_SONG);
				}
			}

			private function addNewSong():void
			{
				// gather the values from the form and add the new song
				var title:String = newSongTitle.text;
				var artist:String = newSongArtist.text;
				var year:uint = newSongYear.value;
				var filename:String = newSongFilename.text;
				var genres:Array = newSongGenres.selectedItems;

				playList.addSong(new Song(title, artist, year, filename, genres));

				refreshList();

				// clear out the "add song" form fields
				setFormState(ADD_SONG);
			}

			private function songListLabel(item:Object):String
			{
				return item.toString();
			}

			private function setFormState(state:uint):void
			{
				// set the form title and control state
				switch (state)
				{
					case ADD_SONG:
						formTitle.text = "Add New Song";
						// show the submit button
						submitSongData.visible = true;
						showAddControlsBtn.visible = false;
						// clear the form fields
						newSongTitle.text = "";
						newSongArtist.text = "";
						newSongYear.value = (new Date()).fullYear;
						newSongFilename.text = "";
						newSongGenres.selectedIndex = -1;
						// deselect the currently selected song (if any)
						songList.selectedIndex = -1;
						break;

					case SONG_DETAIL:
						formTitle.text = "Song Details";
						// populate the form with the selected item's data
						var selectedSong:Song = Song(songList.selectedItem);
						newSongTitle.text = selectedSong.title;
						newSongArtist.text = selectedSong.artist;
						newSongYear.value = selectedSong.year;
						newSongFilename.text = selectedSong.filename;
						newSongGenres.selectedItems = selectedSong.genres;
						// hide the submit button
						submitSongData.visible = false;
						showAddControlsBtn.visible = true;
						break;
				}
			}

			import flash.events.MouseEvent;
			import com.example.programmingas3.playlist.PlayList;
			import com.example.programmingas3.playlist.Song;
			import com.example.programmingas3.playlist.SortProperty;

			// constants for the different "states" of the song form
			private static const ADD_SONG:uint = 1;
			private static const SONG_DETAIL:uint = 2;

			private var playList:PlayList = new PlayList.<T>();

			private function initApp():void
			{
				// set the initial state of the song form, for adding a new song
				setFormState(ADD_SONG);

				// prepopulate the list with a few songs
				playList.addSong(new Song("Nessun Dorma", "Luciano Pavarotti", 1990, "nessundorma.mp3", ["90's", "Opera"]));
				playList.addSong(new Song("Come Undone", "Duran Duran", 1993, "comeundone.mp3", ["90's", "Pop"]));
				playList.addSong(new Song("Think of Me", "Sarah Brightman", 1987, "thinkofme.mp3", ["Showtunes"]));
				playList.addSong(new Song("Unbelievable", "EMF", 1991, "unbelievable.mp3", ["90's", "Pop"]));

				songList.dataProvider = playList.songList;
			}

			private function sortList(sortField:SortProperty.<T>):void
			{
				// Make all the sort type buttons enabled.
				// The active one will be grayed-out below
				sortByTitle.selected = false;
				sortByArtist.selected = false;
				sortByYear.selected = false;

				switch (sortField)
				{
					case SortProperty.TITLE:
						sortByTitle.selected = true;
						break;
					case SortProperty.ARTIST:
						sortByArtist.selected = true;
						break;
					case SortProperty.YEAR:
						sortByYear.selected = true;
						break;
				}

				playList.sortList(sortField);

				refreshList();
			}

			private function refreshList():void
			{
				// remember which song was selected
				var selectedSong:Song = Song(songList.selectedItem);

				// re-assign the song list as the dataprovider to get the newly sorted list
				// and force the List control to refresh itself
				songList.dataProvider = playList.songList;

				// reset the song selection
				if (selectedSong != null)
				{
					songList.selectedItem = selectedSong;
				}
			}

			private function songSelectionChange():void
			{
				if (songList.selectedIndex != -1)
				{
					setFormState(SONG_DETAIL);
				}
				else
				{
					setFormState(ADD_SONG);
				}
			}

			private function addNewSong():void
			{
				// gather the values from the form and add the new song
				var title:String = newSongTitle.text;
				var artist:String = newSongArtist.text;
				var year:uint = newSongYear.value;
				var filename:String = newSongFilename.text;
				var genres:Array = newSongGenres.selectedItems;

				playList.addSong(new Song(title, artist, year, filename, genres));

				refreshList();

				// clear out the "add song" form fields
				setFormState(ADD_SONG);
			}

			private function songListLabel(item:Object):String
			{
				return item.toString();
			}

			private function setFormState(state:uint):void
			{
				// set the form title and control state
				switch (state)
				{
					case ADD_SONG:
						formTitle.text = "Add New Song";
						// show the submit button
						submitSongData.visible = true;
						showAddControlsBtn.visible = false;
						// clear the form fields
						newSongTitle.text = "";
						newSongArtist.text = "";
						newSongYear.value = (new Date()).fullYear;
						newSongFilename.text = "";
						newSongGenres.selectedIndex = -1;
						// deselect the currently selected song (if any)
						songList.selectedIndex = -1;
						break;

					case SONG_DETAIL:
						formTitle.text = "Song Details";
						// populate the form with the selected item's data
						var selectedSong:Song = Song(songList.selectedItem);
						newSongTitle.text = selectedSong.title;
						newSongArtist.text = selectedSong.artist;
						newSongYear.value = selectedSong.year;
						newSongFilename.text = selectedSong.filename;
						newSongGenres.selectedItems = selectedSong.genres;
						// hide the submit button
						submitSongData.visible = false;
						showAddControlsBtn.visible = true;
						break;
				}
			}

Add type generics to AS3 test file.

Add type generics to AS3 test file.