/* ***** BEGIN LICENSE BLOCK *****
 * Version: MPL 1.1
 *
 * The contents of this file are subject to the Mozilla Public License Version
 * 1.1 (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.mozilla.org/MPL/
 *
 * Software distributed under the License is distributed on an "AS IS" basis,
 * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
 * for the specific language governing rights and limitations under the
 * License.
 *
 * The Original Code is the at.matthew.httpstreaming package.
 *
 * The Initial Developer of the Original Code is
 * Matthew Kaufman.
 * Portions created by the Initial Developer are Copyright (C) 2011
 * the Initial Developer. All Rights Reserved.
 *
 * Contributor(s):
 *
 * ***** END LICENSE BLOCK ***** */

package org.denivip.osmf.net.httpstreaming.hls
{
	import flash.utils.ByteArray;
	import flash.utils.IDataInput;

	import org.denivip.osmf.utility.Decrypt;
	import org.osmf.logging.Log;
	import org.osmf.logging.Logger;
	import org.osmf.net.httpstreaming.HTTPStreamingFileHandlerBase;

	[Event(name="notifySegmentDuration", type="org.osmf.events.HTTPStreamingFileHandlerEvent")]
	[Event(name="notifyTimeBias", type="org.osmf.events.HTTPStreamingFileHandlerEvent")]

	public class HTTPStreamingMP2TSFileHandler extends HTTPStreamingFileHandlerBase
	{
		private var _syncFound:Boolean;
		private var _pmtPID:uint;
		private var _audioPID:uint;
		private var _videoPID:uint;
		private var _mp3AudioPID:uint;
		private var _audioPES:HTTPStreamingMP2PESAudio;
		private var _videoPES:HTTPStreamingMP2PESVideo;
		private var _mp3audioPES:HTTPStreamingMp3Audio2ToPESAudio;

		private var _cachedOutputBytes:ByteArray;
		private var alternatingYieldCounter:int = 0;

		private var _key:HTTPStreamingM3U8IndexKey = null;
		private var _iv:ByteArray = null;
		private var _decryptBuffer:ByteArray = new ByteArray;

		public function HTTPStreamingMP2TSFileHandler()
		{
			_audioPES = new HTTPStreamingMP2PESAudio;
			_videoPES = new HTTPStreamingMP2PESVideo;
			_mp3audioPES = new HTTPStreamingMp3Audio2ToPESAudio;
		}

		override public function beginProcessFile(seek:Boolean, seekTime:Number):void
		{
			_syncFound = false;
		}

		override public function get inputBytesNeeded():Number
		{
			return _syncFound ? 187 : 1;
		}

		override public function processFileSegment(input:IDataInput):ByteArray
		{
			var bytesAvailableStart:uint = input.bytesAvailable;
			var output:ByteArray;

			if (_cachedOutputBytes !== null) {
				output = _cachedOutputBytes;
				_cachedOutputBytes = null;
			}
			else {
				output = new ByteArray();
			}

			while (true) {
				if(!_syncFound)
				{
					if (_key) {
						if (_key.type == "AES-128") {
							if (input.bytesAvailable < 16) {
								if (_decryptBuffer.bytesAvailable < 1) {
									break;
								}
							} else {
								decryptToBuffer(input, 16);
							}
							if (_decryptBuffer.readByte() == 0x47) {
								_syncFound = true;
							}
						}
					} else {
						if(input.bytesAvailable < 1)
							break;

						if(input.readByte() == 0x47)
							_syncFound = true;
					}
				}
				else
				{
					_syncFound = false;
					var packet:ByteArray = new ByteArray();

					if (_key) {
						if (_key.type == "AES-128") {
							if (input.bytesAvailable < 176) {
								if (_decryptBuffer.bytesAvailable < 187) {
									break;
								}
							} else {
								var bytesLeft:uint = input.bytesAvailable - 176;
								if (bytesLeft > 0 && bytesLeft < 15) {
									decryptToBuffer(input, input.bytesAvailable);
								} else {
									decryptToBuffer(input, 176);
								}
							}
							_decryptBuffer.readBytes(packet, 0, 187);
						}
					} else {
						if(input.bytesAvailable < 187)
							break;

						input.readBytes(packet, 0, 187);
					}

					var result:ByteArray = processPacket(packet);
					if (result !== null) {
						output.writeBytes(result);
					}

					if (bytesAvailableStart - input.bytesAvailable > 10000) {
						alternatingYieldCounter = (alternatingYieldCounter + 1) & 0x03;
						if (alternatingYieldCounter /*& 0x01 === 1*/) {
							_cachedOutputBytes = output;
							return null;
						}
						break;
					}
				}
			}
			output.position = 0;

			return output.length === 0 ? null : output;
		}

		private function decryptToBuffer(input:IDataInput, blockSize:int):void{
			if (_key) {
				// Clear buffer
				if (_decryptBuffer.bytesAvailable == 0) {
					_decryptBuffer.clear();
				}
				if (_key.type == "AES-128" && blockSize % 16 == 0) {
					// Save buffer position
					var currentPosition:uint = _decryptBuffer.position;
					_decryptBuffer.position += _decryptBuffer.bytesAvailable;

					// Save block to decrypt
					var decrypt:ByteArray = new ByteArray;
					input.readBytes(decrypt, 0, blockSize);
					// Save new IV from ciphertext
					var newIv:ByteArray = new ByteArray;
					decrypt.position += (decrypt.bytesAvailable-16);
					decrypt.readBytes(newIv, 0, 16);
					decrypt.position = 0;
					// Decrypt
					if (input.bytesAvailable == 0) {
						Decrypt.decryptAES128(decrypt, _key.key, _iv, "pkcs7");
					} else {
						Decrypt.decryptAES128(decrypt, _key.key, _iv);
					}
					decrypt.position = 0;
					// Write into buffer
					_decryptBuffer.writeBytes(decrypt);
					_decryptBuffer.position = currentPosition;
					// Update iv
					_iv = newIv;
				}
			}
		}

		override public function endProcessFile(input:IDataInput):ByteArray
		{
			_decryptBuffer.clear();
			return null;
		}

		public function resetCache():void{
			_cachedOutputBytes = null;
			alternatingYieldCounter = 0;
			_decryptBuffer.clear();
		}

		public function set initialOffset(offset:Number):void{
			offset *= 1000; // convert to ms
			_videoPES.initialTimestamp = offset;
			_audioPES.initialTimestamp = offset;
			_mp3audioPES.initialTimestamp = offset;
		}

		public function set key(key:HTTPStreamingM3U8IndexKey):void {
			_key = key;
		}

		public function set iv(iv:String):void {
			if (iv) {
				_iv = Decrypt.hexToByteArray(iv);
			}
		}

		private function processPacket(packet:ByteArray):ByteArray
		{
			// decode rest of transport stream prefix (after the 0x47 flag byte)

			// top of second byte
			var value:uint = packet.readUnsignedByte();

			//var tei:Boolean = Boolean(value & 0x80);	// error indicator
			var pusi:Boolean = Boolean(value & 0x40);	// payload unit start indication
			//var tpri:Boolean = Boolean(value & 0x20);	// transport priority indication

			// bottom of second byte and all of third
			value <<= 8;
			value += packet.readUnsignedByte();

			var pid:uint = value & 0x1fff;	// packet ID

			// fourth byte
			value = packet.readUnsignedByte();
			//var scramblingControl:uint = (value >> 6) & 0x03;	// scrambling control bits
			var hasAF:Boolean = Boolean(value & 0x20);	// has adaptation field
			var hasPD:Boolean = Boolean(value & 0x10);	// has payload data
			//var ccount:uint = value & 0x0f;		// continuty count

			// technically hasPD without hasAF is an error, see spec

			if(hasAF)
			{
				// process adaptation field
				// don't care about flags
				// don't care about clocks here
                var af:uint = packet.readUnsignedByte();
				packet.position += af;	// skip to end
			}

            return hasPD ? processES(pid, pusi, packet) : null;
		}

		private function processES(pid:uint, pusi:Boolean, packet:ByteArray):ByteArray
		{
			var output:ByteArray = null;
			if(pid == 0)	// PAT
			{
				if(pusi)
					processPAT(packet);
			}
			else if(pid == _pmtPID)
			{
				if(pusi)
					processPMT(packet);
			}
			else if(pid == _audioPID)
			{
				output = _audioPES.processES(pusi, packet);
			}
			else if(pid == _videoPID)
			{
				output = _videoPES.processES(pusi, packet);
			}
			else if(pid == _mp3AudioPID)
			{
				output = _mp3audioPES.processES(pusi, packet);
			}

			return output;
		}

		private function processPAT(packet:ByteArray):void
		{
			packet.readUnsignedByte();   // pointer:uint
			packet.readUnsignedByte();   // tableID:uint
			var remaining:uint = packet.readUnsignedShort() & 0x03ff; // ignoring misc and reserved bits

			packet.position += 5; // skip tsid + version/cni + sec# + last sec#
			remaining -= 5;

			while(remaining > 4)
			{
				packet.readUnsignedShort(); // program number
				_pmtPID = packet.readUnsignedShort() & 0x1fff; // 13 bits
				remaining -= 4;

				//return; // immediately after reading the first pmt ID, if we don't we get the LAST one
			}

			// and ignore the CRC (4 bytes)
		}

		private function processPMT(packet:ByteArray):void
		{
			packet.readUnsignedByte();  // pointer:uint
			var tableID:uint = packet.readUnsignedByte();

			if (tableID != 0x02)
			{
				CONFIG::LOGGING
				{
					logger.warn("PAT pointed to PMT that isn't PMT");
				}
				return; // don't try to parse it
			}

			var remaining:uint = packet.readUnsignedShort() & 0x03ff; // ignoring section syntax and reserved

			packet.position += 7; // skip program num, rserved, version, cni, section num, last section num, reserved, PCR PID
			remaining -= 7;

			var piLen:uint = packet.readUnsignedShort() & 0x0fff;
			remaining -= 2;

			packet.position += piLen; // skip program info
			remaining -= piLen;

			while(remaining > 4)
			{
				var type:uint = packet.readUnsignedByte();
				var pid:uint = packet.readUnsignedShort() & 0x1fff;
				var esiLen:uint = packet.readUnsignedShort() & 0x0fff;
				remaining -= 5;

				packet.position += esiLen;
				remaining -= esiLen;

				switch(type)
				{
					case 0x1b: // H.264 video
						_videoPID = pid;
						break;
					case 0x0f: // AAC Audio / ADTS
						_audioPID = pid;
						break;

					case 0x03: // MP3 Audio  (3 & 4)
					case 0x04:
						_mp3AudioPID = pid;
						break;

					default:
						CONFIG::LOGGING
						{
							logger.error("unsupported type "+type.toString(16)+" in PMT");
						}
						break;
				}
			}

			// and ignore CRC
		}

		override public function flushFileSegment(input:IDataInput):ByteArray
		{
			var flvBytes:ByteArray = new ByteArray();
			var flvBytesVideo:ByteArray = _videoPES.processES(false, null, true);
			var flvBytesAudio:ByteArray = _audioPES.processES(false, null, true);

			if(flvBytesVideo)
				flvBytes.readBytes(flvBytesVideo);
			if(flvBytesAudio)
				flvBytes.readBytes(flvBytesAudio);

			return flvBytes;
		}

		CONFIG::LOGGING
		{
			private var logger:Logger = Log.getLogger('org.denivip.osmf.net.httpstreaming.hls.HTTPStreamingMP2TSFileHandler') as Logger;
		}
	}
}

/* ***** BEGIN LICENSE BLOCK *****
 * Version: MPL 1.1
 *
 * The contents of this file are subject to the Mozilla Public License Version
 * 1.1 (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.mozilla.org/MPL/
 *
 * Software distributed under the License is distributed on an "AS IS" basis,
 * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
 * for the specific language governing rights and limitations under the
 * License.
 *
 * The Original Code is the at.matthew.httpstreaming package.
 *
 * The Initial Developer of the Original Code is
 * Matthew Kaufman.
 * Portions created by the Initial Developer are Copyright (C) 2011
 * the Initial Developer. All Rights Reserved.
 *
 * Contributor(s):
 *
 * ***** END LICENSE BLOCK ***** */

package org.denivip.osmf.net.httpstreaming.hls
{
	import flash.utils.ByteArray;
	import flash.utils.IDataInput;

	import org.denivip.osmf.utility.Decrypt;
	import org.osmf.logging.Log;
	import org.osmf.logging.Logger;
	import org.osmf.net.httpstreaming.HTTPStreamingFileHandlerBase;

	[Event(name="notifySegmentDuration", type="org.osmf.events.HTTPStreamingFileHandlerEvent")]
	[Event(name="notifyTimeBias", type="org.osmf.events.HTTPStreamingFileHandlerEvent")]

	public class HTTPStreamingMP2TSFileHandler extends HTTPStreamingFileHandlerBase
	{
		private var _syncFound:Boolean;
		private var _pmtPID:uint;
		private var _audioPID:uint;
		private var _videoPID:uint;
		private var _mp3AudioPID:uint;
		private var _audioPES:HTTPStreamingMP2PESAudio;
		private var _videoPES:HTTPStreamingMP2PESVideo;
		private var _mp3audioPES:HTTPStreamingMp3Audio2ToPESAudio;

		private var _cachedOutputBytes:ByteArray;
		private var alternatingYieldCounter:int = 0;

		private var _key:HTTPStreamingM3U8IndexKey = null;
		private var _iv:ByteArray = null;
		private var _decryptBuffer:ByteArray = new ByteArray;

		public function HTTPStreamingMP2TSFileHandler()
		{
			_audioPES = new HTTPStreamingMP2PESAudio;
			_videoPES = new HTTPStreamingMP2PESVideo;
			_mp3audioPES = new HTTPStreamingMp3Audio2ToPESAudio;
		}

		override public function beginProcessFile(seek:Boolean, seekTime:Number):void
		{
			_syncFound = false;
		}

		override public function get inputBytesNeeded():Number
		{
			return _syncFound ? 187 : 1;
		}

		override public function processFileSegment(input:IDataInput):ByteArray
		{
			var bytesAvailableStart:uint = input.bytesAvailable;
			var output:ByteArray;

			if (_cachedOutputBytes !== null) {
				output = _cachedOutputBytes;
				_cachedOutputBytes = null;
			}
			else {
				output = new ByteArray();
			}

			while (true) {
				if(!_syncFound)
				{
					if (_key) {
						if (_key.type == "AES-128") {
							if (input.bytesAvailable < 16) {
								if (_decryptBuffer.bytesAvailable < 1) {
									break;
								}
							} else {
								if (!decryptToBuffer(input, 16)) {
									break;
								}
							}
							if (_decryptBuffer.readByte() == 0x47) {
								_syncFound = true;
							}
						}
					} else {
						if(input.bytesAvailable < 1)
							break;

						if(input.readByte() == 0x47)
							_syncFound = true;
					}
				}
				else
				{
					_syncFound = false;
					var packet:ByteArray = new ByteArray();

					if (_key) {
						if (_key.type == "AES-128") {
							if (input.bytesAvailable < 176) {
								if (_decryptBuffer.bytesAvailable < 187) {
									break;
								}
							} else {
								var bytesLeft:uint = input.bytesAvailable - 176;
								if (bytesLeft > 0 && bytesLeft < 15) {
									if (!decryptToBuffer(input, input.bytesAvailable)) {
										break;
									}
								} else {
									if (!decryptToBuffer(input, 176)) {
										break;
									}
								}
							}
							_decryptBuffer.readBytes(packet, 0, 187);
						}
					} else {
						if(input.bytesAvailable < 187)
							break;

						input.readBytes(packet, 0, 187);
					}

					var result:ByteArray = processPacket(packet);
					if (result !== null) {
						output.writeBytes(result);
					}

					if (bytesAvailableStart - input.bytesAvailable > 10000) {
						alternatingYieldCounter = (alternatingYieldCounter + 1) & 0x03;
						if (alternatingYieldCounter /*& 0x01 === 1*/) {
							_cachedOutputBytes = output;
							return null;
						}
						break;
					}
				}
			}
			output.position = 0;

			return output.length === 0 ? null : output;
		}

		private function decryptToBuffer(input:IDataInput, blockSize:int):Boolean{
			if (_key) {
				// Clear buffer
				if (_decryptBuffer.bytesAvailable == 0) {
					_decryptBuffer.clear();
				}

				if (_key.type == "AES-128" && blockSize % 16 == 0 && _key.key) {
					// Save buffer position
					var currentPosition:uint = _decryptBuffer.position;
					_decryptBuffer.position += _decryptBuffer.bytesAvailable;

					// Save block to decrypt
					var decrypt:ByteArray = new ByteArray;
					input.readBytes(decrypt, 0, blockSize);
					// Save new IV from ciphertext
					var newIv:ByteArray = new ByteArray;
					decrypt.position += (decrypt.bytesAvailable-16);
					decrypt.readBytes(newIv, 0, 16);
					decrypt.position = 0;
					// Decrypt
					if (input.bytesAvailable == 0) {
						Decrypt.decryptAES128(decrypt, _key.key, _iv, "pkcs7");
					} else {
						Decrypt.decryptAES128(decrypt, _key.key, _iv);
					}
					decrypt.position = 0;
					// Write into buffer
					_decryptBuffer.writeBytes(decrypt);
					_decryptBuffer.position = currentPosition;
					// Update iv
					_iv = newIv;

					return true;
				}
			}

			return false;
		}

		override public function endProcessFile(input:IDataInput):ByteArray
		{
			_decryptBuffer.clear();
			return null;
		}

		public function resetCache():void{
			_cachedOutputBytes = null;
			alternatingYieldCounter = 0;
			_decryptBuffer.clear();
		}

		public function set initialOffset(offset:Number):void{
			offset *= 1000; // convert to ms
			_videoPES.initialTimestamp = offset;
			_audioPES.initialTimestamp = offset;
			_mp3audioPES.initialTimestamp = offset;
		}

		public function set key(key:HTTPStreamingM3U8IndexKey):void {
			_key = key;
		}

		public function set iv(iv:String):void {
			if (iv) {
				_iv = Decrypt.hexToByteArray(iv);
			}
		}

		private function processPacket(packet:ByteArray):ByteArray
		{
			// decode rest of transport stream prefix (after the 0x47 flag byte)

			// top of second byte
			var value:uint = packet.readUnsignedByte();

			//var tei:Boolean = Boolean(value & 0x80);	// error indicator
			var pusi:Boolean = Boolean(value & 0x40);	// payload unit start indication
			//var tpri:Boolean = Boolean(value & 0x20);	// transport priority indication

			// bottom of second byte and all of third
			value <<= 8;
			value += packet.readUnsignedByte();

			var pid:uint = value & 0x1fff;	// packet ID

			// fourth byte
			value = packet.readUnsignedByte();
			//var scramblingControl:uint = (value >> 6) & 0x03;	// scrambling control bits
			var hasAF:Boolean = Boolean(value & 0x20);	// has adaptation field
			var hasPD:Boolean = Boolean(value & 0x10);	// has payload data
			//var ccount:uint = value & 0x0f;		// continuty count

			// technically hasPD without hasAF is an error, see spec

			if(hasAF)
			{
				// process adaptation field
				// don't care about flags
				// don't care about clocks here
                var af:uint = packet.readUnsignedByte();
				packet.position += af;	// skip to end
			}

            return hasPD ? processES(pid, pusi, packet) : null;
		}

		private function processES(pid:uint, pusi:Boolean, packet:ByteArray):ByteArray
		{
			var output:ByteArray = null;
			if(pid == 0)	// PAT
			{
				if(pusi)
					processPAT(packet);
			}
			else if(pid == _pmtPID)
			{
				if(pusi)
					processPMT(packet);
			}
			else if(pid == _audioPID)
			{
				output = _audioPES.processES(pusi, packet);
			}
			else if(pid == _videoPID)
			{
				output = _videoPES.processES(pusi, packet);
			}
			else if(pid == _mp3AudioPID)
			{
				output = _mp3audioPES.processES(pusi, packet);
			}

			return output;
		}

		private function processPAT(packet:ByteArray):void
		{
			packet.readUnsignedByte();   // pointer:uint
			packet.readUnsignedByte();   // tableID:uint
			var remaining:uint = packet.readUnsignedShort() & 0x03ff; // ignoring misc and reserved bits

			packet.position += 5; // skip tsid + version/cni + sec# + last sec#
			remaining -= 5;

			while(remaining > 4)
			{
				packet.readUnsignedShort(); // program number
				_pmtPID = packet.readUnsignedShort() & 0x1fff; // 13 bits
				remaining -= 4;

				//return; // immediately after reading the first pmt ID, if we don't we get the LAST one
			}

			// and ignore the CRC (4 bytes)
		}

		private function processPMT(packet:ByteArray):void
		{
			packet.readUnsignedByte();  // pointer:uint
			var tableID:uint = packet.readUnsignedByte();

			if (tableID != 0x02)
			{
				CONFIG::LOGGING
				{
					logger.warn("PAT pointed to PMT that isn't PMT");
				}
				return; // don't try to parse it
			}

			var remaining:uint = packet.readUnsignedShort() & 0x03ff; // ignoring section syntax and reserved

			packet.position += 7; // skip program num, rserved, version, cni, section num, last section num, reserved, PCR PID
			remaining -= 7;

			var piLen:uint = packet.readUnsignedShort() & 0x0fff;
			remaining -= 2;

			packet.position += piLen; // skip program info
			remaining -= piLen;

			while(remaining > 4)
			{
				var type:uint = packet.readUnsignedByte();
				var pid:uint = packet.readUnsignedShort() & 0x1fff;
				var esiLen:uint = packet.readUnsignedShort() & 0x0fff;
				remaining -= 5;

				packet.position += esiLen;
				remaining -= esiLen;

				switch(type)
				{
					case 0x1b: // H.264 video
						_videoPID = pid;
						break;
					case 0x0f: // AAC Audio / ADTS
						_audioPID = pid;
						break;

					case 0x03: // MP3 Audio  (3 & 4)
					case 0x04:
						_mp3AudioPID = pid;
						break;

					default:
						CONFIG::LOGGING
						{
							logger.error("unsupported type "+type.toString(16)+" in PMT");
						}
						break;
				}
			}

			// and ignore CRC
		}

		override public function flushFileSegment(input:IDataInput):ByteArray
		{
			var flvBytes:ByteArray = new ByteArray();
			var flvBytesVideo:ByteArray = _videoPES.processES(false, null, true);
			var flvBytesAudio:ByteArray = _audioPES.processES(false, null, true);

			if(flvBytesVideo)
				flvBytes.readBytes(flvBytesVideo);
			if(flvBytesAudio)
				flvBytes.readBytes(flvBytesAudio);

			return flvBytes;
		}

		CONFIG::LOGGING
		{
			private var logger:Logger = Log.getLogger('org.denivip.osmf.net.httpstreaming.hls.HTTPStreamingMP2TSFileHandler') as Logger;
		}
	}
}

Update fileHandler to account for missing key data


//
// Flump - Copyright 2012 Three Rings Design

package flump.export {

import flash.desktop.NativeApplication;
import flash.display.NativeMenu;
import flash.display.NativeMenuItem;
import flash.display.Stage;
import flash.display.StageQuality;
import flash.events.Event;
import flash.events.MouseEvent;
import flash.filesystem.File;
import flash.utils.IDataOutput;

import flump.executor.Executor;
import flump.executor.Future;
import flump.xfl.ParseError;
import flump.xfl.XflLibrary;

import mx.events.PropertyChangeEvent;
import mx.managers.PopUpManager;

import spark.components.DataGrid;
import spark.components.Window;
import spark.events.GridSelectionEvent;

import com.threerings.util.F;
import com.threerings.util.Log;
import com.threerings.util.StringUtil;

public class ProjectController
{
    public static const NA :NativeApplication = NativeApplication.nativeApplication;

    public function ProjectController (configFile :File = null) {
        _win = new ProjectWindow();
        _win.open();
        _errorsGrid = _win.errors;
        _flashDocsGrid = _win.libraries;

        _confFile = configFile;
        if (_confFile != null) {
            try {
                _conf = ProjectConf.fromJSON(JSONFormat.readJSON(_confFile));
                var dir :String = _confFile.parent.resolvePath(_conf.importDir).nativePath;
                setImportDirectory(new File(dir));
            } catch (e :Error) {
                log.warning("Unable to parse conf", e);
                _errorsGrid.dataProvider.addItem(new ParseError(_confFile.nativePath,
                    ParseError.CRIT, "Unable to read configuration"));
                _confFile = null;
                _conf = null;
            }
        }

        var curSelection :DocStatus = null;
        _flashDocsGrid.addEventListener(GridSelectionEvent.SELECTION_CHANGE, function (..._) :void {
            log.info("Changed", "selected", _flashDocsGrid.selectedIndices);
            onSelectedItemChanged();

            if (curSelection != null) {
                curSelection.removeEventListener(PropertyChangeEvent.PROPERTY_CHANGE,
                    onSelectedItemChanged);
            }
            var newSelection :DocStatus = _flashDocsGrid.selectedItem as DocStatus;
            if (newSelection != null) {
                newSelection.addEventListener(PropertyChangeEvent.PROPERTY_CHANGE,
                    onSelectedItemChanged);
            }
            curSelection = newSelection;
        });

        // Reload
        _win.reload.addEventListener(MouseEvent.CLICK, F.callback(reloadNow));

        // Export
        _win.export.addEventListener(MouseEvent.CLICK, function (..._) :void {
            for each (var status :DocStatus in _flashDocsGrid.selectedItems) {
                exportFlashDocument(status);
            }
        });

        // Preview
        _win.preview.addEventListener(MouseEvent.CLICK, function (..._) :void {
            FlumpApp.app.showPreviewWindow(_flashDocsGrid.selectedItem.lib);
        });

        // Export All
        _win.exportAll.addEventListener(MouseEvent.CLICK, function (..._) :void {
            for each (var status :DocStatus in _flashDocsGrid.dataProvider.toArray()) {
                if (status.isValid) {
                    exportFlashDocument(status);
                }
            }
        });

        // Import/Export directories
        _importChooser = new DirChooser(null, _win.importRoot, _win.browseImport);
        _importChooser.changed.add(setImportDirectory);
        _exportChooser = new DirChooser(null, _win.exportRoot, _win.browseExport);
        _exportChooser.changed.add(reloadNow);

        _importChooser.changed.add(F.callback(setProjectDirty, true));
        _exportChooser.changed.add(F.callback(setProjectDirty, true));

        // Edit Formats
        var editFormatsController :EditFormatsController = null;
        _win.editFormats.addEventListener(MouseEvent.CLICK, function (..._) :void {
            if (editFormatsController == null || editFormatsController.closed) {
                editFormatsController = new EditFormatsController(_conf);
                editFormatsController.formatsChanged.add(updateUiFromConf);
                editFormatsController.formatsChanged.add(F.callback(setProjectDirty, true));
            } else {
                editFormatsController.show();
            }
        });

        _win.addEventListener(Event.CLOSING, function (e :Event) :void {
            if (_projectDirty) {
                e.preventDefault();
                promptToSaveChanges();
            }
        });

        updateUiFromConf();
        updateWindowTitle();

        setupMenus();
    }

    protected function promptToSaveChanges () :void {
        var unsavedWindow :UnsavedChangesWindow = new UnsavedChangesWindow();
        PopUpManager.addPopUp(unsavedWindow, _win, true);

        unsavedWindow.prompt.text = "Save changes to '" + projectName + "'?";

        unsavedWindow.cancel.addEventListener(MouseEvent.CLICK, function (..._) :void {
            PopUpManager.removePopUp(unsavedWindow);
        });

        unsavedWindow.dontSave.addEventListener(MouseEvent.CLICK, function (..._) :void {
            PopUpManager.removePopUp(unsavedWindow);
            _projectDirty = false;
            _win.close();
        });

        unsavedWindow.save.addEventListener(MouseEvent.CLICK, function (..._) :void {
            PopUpManager.removePopUp(unsavedWindow);
            save(F.callback(_win.close));
        });
    }

    public function get projectDirty () :Boolean {
        return _projectDirty;
    }

    public function get projectName () :String {
        return (_confFile != null ? _confFile.name.replace(/\.flump$/i, "") : "Untitled Project");
    }

    public function save (onSuccess :Function = null) :void {
        if (_confFile == null) {
            saveAs(onSuccess);
        } else {
            saveConf(onSuccess);
        }
    }

    public function saveAs (onSuccess :Function = null) :void {
        var file :File = new File();
        file.addEventListener(Event.SELECT, function (..._) :void {
            // Ensure the filename ends with .flump
            if (!StringUtil.endsWith(file.name.toLowerCase(), ".flump")) {
                file = file.parent.resolvePath(file.name + ".flump");
            }

            _confFile = file;
            saveConf(onSuccess);
        });
        file.browseForSave("Save Flump Configuration");
    }

    public function get configFile () :File {
        return _confFile;
    }

    public function get win () :Window {
        return _win;
    }

    protected function setupMenus () :void {
        if (NativeApplication.supportsMenu) {
            // If we're on a Mac, the menus will be set up at the application level.
            return;
        }

        var fileMenuItem  :NativeMenuItem =
            _win.nativeWindow.menu.addSubmenu(new NativeMenu(), "File");

        // Add save and save as by index to work with the existing items on Mac
        // Mac menus have an existing "Close" item, so everything we add should go ahead of that
        var newMenuItem :NativeMenuItem = fileMenuItem.submenu.addItemAt(new NativeMenuItem("New Project"), 0);
        newMenuItem.keyEquivalent = "n";
        newMenuItem.addEventListener(Event.SELECT, function (..._) :void {
            FlumpApp.app.newProject();
        });

        var openMenuItem :NativeMenuItem =
            fileMenuItem.submenu.addItemAt(new NativeMenuItem("Open Project..."), 1);
        openMenuItem.keyEquivalent = "o";
        openMenuItem.addEventListener(Event.SELECT, function (..._) :void {
            FlumpApp.app.showOpenProjectDialog();
        });
        fileMenuItem.submenu.addItemAt(new NativeMenuItem("Sep", /*separator=*/true), 2);

        const saveMenuItem :NativeMenuItem =
            fileMenuItem.submenu.addItemAt(new NativeMenuItem("Save Project"), 3);
        saveMenuItem.keyEquivalent = "s";
        saveMenuItem.addEventListener(Event.SELECT, F.callback(save));

        const saveAsMenuItem :NativeMenuItem =
            fileMenuItem.submenu.addItemAt(new NativeMenuItem("Save Project As..."), 4);
        saveAsMenuItem.keyEquivalent = "S";
        saveAsMenuItem.addEventListener(Event.SELECT, F.callback(saveAs));
    }

    protected function updateWindowTitle () :void {
        var name :String = this.projectName;
        if (_projectDirty) name += "*";
        _win.title = name;
    }

    protected function saveConf (onSuccess :Function) :void {
        Files.write(_confFile, function (out :IDataOutput) :void {
            // Set directories relative to where this file is being saved. Fall back to absolute
            // paths if relative paths aren't possible.
            if (_importChooser.dir != null) {
                _conf.importDir = _confFile.parent.getRelativePath(_importChooser.dir, /*useDotDot=*/true);
                if (_conf.importDir == null) _conf.importDir = _importChooser.dir.nativePath;
            }

            if (_exportChooser.dir != null) {
                _conf.exportDir = _confFile.parent.getRelativePath(_exportChooser.dir, /*useDotDot=*/true);
                if (_conf.exportDir == null) _conf.exportDir = _exportChooser.dir.nativePath;
            }

            out.writeUTFBytes(JSON.stringify(_conf, null, /*space=*/2));

            setProjectDirty(false);
            updateWindowTitle();

            if (onSuccess != null) {
                onSuccess();
            }
        });
    }

    protected function reloadNow () :void {
        setImportDirectory(_importChooser.dir);
        onSelectedItemChanged();
    }

    protected function updateUiFromConf (..._) :void {
        if (_confFile != null) {
            _importChooser.dir = (_conf.importDir != null) ? _confFile.parent.resolvePath(_conf.importDir) : null;
            _exportChooser.dir = (_conf.exportDir != null) ? _confFile.parent.resolvePath(_conf.exportDir) : null;
        } else {
            _importChooser.dir = null;
            _exportChooser.dir = null;
        }

        var formatNames :Array = [];
        for each (var export :ExportConf in _conf.exports) formatNames.push(export.description);
        _win.formatOverview.text = formatNames.join(", ");

        updateWindowTitle();
    }

    protected function onSelectedItemChanged (..._) :void {
        _win.export.enabled = _exportChooser.dir != null && _flashDocsGrid.selectionLength > 0 &&
            _flashDocsGrid.selectedItems.some(function (status :DocStatus, ..._) :Boolean {
                return status.isValid;
            });

        var status :DocStatus = _flashDocsGrid.selectedItem as DocStatus;
        _win.preview.enabled = status != null && status.isValid;

        _win.selectedItem.text = (status == null ? "" : status.path);
    }

    protected function createPublisher () :Publisher {
        if (_exportChooser.dir == null || _conf.exports.length == 0) return null;
        return new Publisher(_exportChooser.dir, _conf);
    }

    protected function setImportDirectory (dir :File) :void {
        _importDirectory = dir;
        _flashDocsGrid.dataProvider.removeAll();
        _errorsGrid.dataProvider.removeAll();
        if (dir == null) {
            return;

        }
        if (_docFinder != null) {
            _docFinder.shutdownNow();
        }
        _docFinder = new Executor();
        findFlashDocuments(dir, _docFinder, true);
        _win.reload.enabled = true;
    }

    protected function findFlashDocuments (base :File, exec :Executor, ignoreXflAtBase :Boolean = false) :void {
        Files.list(base, exec).succeeded.add(function (files :Array) :void {
            if (exec.isShutdown) return;
            for each (var file :File in files) {
                if (Files.hasExtension(file, "xfl")) {
                    if (ignoreXflAtBase) {
                        _errorsGrid.dataProvider.addItem(new ParseError(base.nativePath,
                            ParseError.CRIT, "The import directory can't be an XFL directory, did you mean " +
                            base.parent.nativePath + "?"));
                    } else addFlashDocument(file);
                    return;
                }
            }
            for each (file in files) {
                if (StringUtil.startsWith(file.name, ".", "RECOVER_")) {
                    continue; // Ignore hidden VCS directories, and recovered backups created by Flash
                }
                if (file.isDirectory) findFlashDocuments(file, exec);
                else addFlashDocument(file);
            }
        });
    }

    protected function exportFlashDocument (status :DocStatus) :void {
        const stage :Stage = NA.activeWindow.stage;
        const prevQuality :String = stage.quality;

        stage.quality = StageQuality.BEST;
        createPublisher().publish(status.lib);

        stage.quality = prevQuality;
        status.updateModified(Ternary.FALSE);
    }

    protected function addFlashDocument (file :File) :void {
        var importPathLen :int = _importDirectory.nativePath.length + 1;
        var name :String = file.nativePath.substring(importPathLen).replace(
            new RegExp("\\" + File.separator, "g"), "/");

        var load :Future;
        switch (Files.getExtension(file)) {
        case "xfl":
            name = name.substr(0, name.lastIndexOf("/"));
            load = new XflLoader().load(name, file.parent);
            break;
        case "fla":
            name = name.substr(0, name.lastIndexOf("."));
            load = new FlaLoader().load(name, file);
            break;
        default:
            // Unsupported file type, ignore
            return;
        }

        const status :DocStatus = new DocStatus(name, Ternary.UNKNOWN, Ternary.UNKNOWN, null);
        _flashDocsGrid.dataProvider.addItem(status);

        load.succeeded.add(function (lib :XflLibrary) :void {
            var pub :Publisher = createPublisher();
            status.lib = lib;
            status.updateModified(Ternary.of(pub == null || pub.modified(lib)));
            for each (var err :ParseError in lib.getErrors()) _errorsGrid.dataProvider.addItem(err);
            status.updateValid(Ternary.of(lib.valid));
        });
        load.failed.add(function (error :Error) :void {
            trace("Failed to load " + file.nativePath + ": " + error);
            status.updateValid(Ternary.FALSE);
            throw error;
        });
    }

    protected function setProjectDirty (val :Boolean) :void {
        if (_projectDirty != val) {
            _projectDirty = val;
            updateWindowTitle();
        }
    }

    protected var _importDirectory :File;

    protected var _docFinder :Executor;
    protected var _win :ProjectWindow;
    protected var _flashDocsGrid :DataGrid;
    protected var _errorsGrid :DataGrid;
    protected var _exportChooser :DirChooser;
    protected var _importChooser :DirChooser;
    protected var _conf :ProjectConf = new ProjectConf();
    protected var _confFile :File;

    protected var _projectDirty :Boolean; // true if project has unsaved changes

    private static const log :Log = Log.getLog(ProjectController);
}
}

import flash.events.EventDispatcher;

import flump.export.Ternary;
import flump.xfl.XflLibrary;

import mx.core.IPropertyChangeNotifier;
import mx.events.PropertyChangeEvent;

class DocStatus extends EventDispatcher implements IPropertyChangeNotifier {
    public var path :String;
    public var modified :String;
    public var valid :String = PENDING;
    public var lib :XflLibrary;

    public function DocStatus (path :String, modified :Ternary, valid :Ternary, lib :XflLibrary) {
        this.lib = lib;
        this.path = path;
        _uid = path;

        updateModified(modified);
        updateValid(valid);
    }

    public function updateValid (newValid :Ternary) :void {
        changeField("valid", function (..._) :void {
            if (newValid == Ternary.TRUE) valid = YES;
            else if (newValid == Ternary.FALSE) valid = ERROR;
            else valid = PENDING;
        });
    }

    public function get isValid () :Boolean { return valid == YES; }

    public function updateModified (newModified :Ternary) :void {
        changeField("modified", function (..._) :void {
            if (newModified == Ternary.TRUE) modified = YES;
            else if (newModified == Ternary.FALSE) modified = " ";
            else modified = PENDING;
        });
    }

    protected function changeField(fieldName :String, modifier :Function) :void {
        const oldValue :Object = this[fieldName];
        modifier();
        const newValue :Object = this[fieldName];
        dispatchEvent(PropertyChangeEvent.createUpdateEvent(this, fieldName, oldValue, newValue));
    }

    public function get uid () :String { return _uid; }
    public function set uid (uid :String) :void { _uid = uid; }

    protected var _uid :String;

    protected static const PENDING :String = "...";
    protected static const ERROR :String = "ERROR";
    protected static const YES :String = "Yes";
}

//
// Flump - Copyright 2012 Three Rings Design

package flump.export {

import flash.desktop.NativeApplication;
import flash.display.NativeMenu;
import flash.display.NativeMenuItem;
import flash.display.Stage;
import flash.display.StageQuality;
import flash.events.Event;
import flash.events.MouseEvent;
import flash.filesystem.File;
import flash.utils.IDataOutput;

import flump.executor.Executor;
import flump.executor.Future;
import flump.xfl.ParseError;
import flump.xfl.XflLibrary;

import mx.events.PropertyChangeEvent;
import mx.managers.PopUpManager;

import spark.components.DataGrid;
import spark.components.Window;
import spark.events.GridSelectionEvent;

import com.threerings.util.F;
import com.threerings.util.Log;
import com.threerings.util.StringUtil;

public class ProjectController
{
    public static const NA :NativeApplication = NativeApplication.nativeApplication;

    public function ProjectController (configFile :File = null) {
        _win = new ProjectWindow();
        _win.open();
        _errorsGrid = _win.errors;
        _flashDocsGrid = _win.libraries;

        _confFile = configFile;
        if (_confFile != null) {
            try {
                _conf = ProjectConf.fromJSON(JSONFormat.readJSON(_confFile));
                var dir :String = _confFile.parent.resolvePath(_conf.importDir).nativePath;
                setImportDirectory(new File(dir));
            } catch (e :Error) {
                log.warning("Unable to parse conf", e);
                _errorsGrid.dataProvider.addItem(new ParseError(_confFile.nativePath,
                    ParseError.CRIT, "Unable to read configuration"));
                _confFile = null;
                _conf = null;
            }
        }

        var curSelection :DocStatus = null;
        _flashDocsGrid.addEventListener(GridSelectionEvent.SELECTION_CHANGE, function (..._) :void {
            log.info("Changed", "selected", _flashDocsGrid.selectedIndices);
            onSelectedItemChanged();

            if (curSelection != null) {
                curSelection.removeEventListener(PropertyChangeEvent.PROPERTY_CHANGE,
                    onSelectedItemChanged);
            }
            var newSelection :DocStatus = _flashDocsGrid.selectedItem as DocStatus;
            if (newSelection != null) {
                newSelection.addEventListener(PropertyChangeEvent.PROPERTY_CHANGE,
                    onSelectedItemChanged);
            }
            curSelection = newSelection;
        });

        // Reload
        _win.reload.addEventListener(MouseEvent.CLICK, F.callback(reloadNow));

        // Export
        _win.export.addEventListener(MouseEvent.CLICK, function (..._) :void {
            for each (var status :DocStatus in _flashDocsGrid.selectedItems) {
                exportFlashDocument(status);
            }
        });

        // Preview
        _win.preview.addEventListener(MouseEvent.CLICK, function (..._) :void {
            FlumpApp.app.showPreviewWindow(_flashDocsGrid.selectedItem.lib);
        });

        // Export All
        _win.exportAll.addEventListener(MouseEvent.CLICK, function (..._) :void {
            for each (var status :DocStatus in _flashDocsGrid.dataProvider.toArray()) {
                if (status.isValid) {
                    exportFlashDocument(status);
                }
            }
        });

        // Import/Export directories
        _importChooser = new DirChooser(null, _win.importRoot, _win.browseImport);
        _importChooser.changed.add(setImportDirectory);
        _exportChooser = new DirChooser(null, _win.exportRoot, _win.browseExport);
        _exportChooser.changed.add(reloadNow);

        _importChooser.changed.add(F.callback(setProjectDirty, true));
        _exportChooser.changed.add(F.callback(setProjectDirty, true));

        // Edit Formats
        var editFormatsController :EditFormatsController = null;
        _win.editFormats.addEventListener(MouseEvent.CLICK, function (..._) :void {
            if (editFormatsController == null || editFormatsController.closed) {
                editFormatsController = new EditFormatsController(_conf);
                editFormatsController.formatsChanged.add(updateUiFromConf);
                editFormatsController.formatsChanged.add(F.callback(setProjectDirty, true));
            } else {
                editFormatsController.show();
            }
        });

        _win.addEventListener(Event.CLOSING, function (e :Event) :void {
            if (_projectDirty) {
                e.preventDefault();
                promptToSaveChanges();
            }
        });

        updateUiFromConf();
        updateWindowTitle();

        setupMenus();
    }

    protected function promptToSaveChanges () :void {
        var unsavedWindow :UnsavedChangesWindow = new UnsavedChangesWindow();
        PopUpManager.addPopUp(unsavedWindow, _win, true);

        unsavedWindow.prompt.text = "Save changes to '" + projectName + "'?";

        unsavedWindow.cancel.addEventListener(MouseEvent.CLICK, function (..._) :void {
            PopUpManager.removePopUp(unsavedWindow);
        });

        unsavedWindow.dontSave.addEventListener(MouseEvent.CLICK, function (..._) :void {
            PopUpManager.removePopUp(unsavedWindow);
            _projectDirty = false;
            _win.close();
        });

        unsavedWindow.save.addEventListener(MouseEvent.CLICK, function (..._) :void {
            PopUpManager.removePopUp(unsavedWindow);
            save(F.callback(_win.close));
        });
    }

    public function get projectDirty () :Boolean {
        return _projectDirty;
    }

    public function get projectName () :String {
        return (_confFile != null ? _confFile.name.replace(/\.flump$/i, "") : "Untitled Project");
    }

    public function save (onSuccess :Function = null) :void {
        if (_confFile == null) {
            saveAs(onSuccess);
        } else {
            saveConf(onSuccess);
        }
    }

    public function saveAs (onSuccess :Function = null) :void {
        var file :File = new File();
        file.addEventListener(Event.SELECT, function (..._) :void {
            // Ensure the filename ends with .flump
            if (!StringUtil.endsWith(file.name.toLowerCase(), ".flump")) {
                file = file.parent.resolvePath(file.name + ".flump");
            }

            _confFile = file;
            saveConf(onSuccess);
        });
        file.browseForSave("Save Flump Configuration");
    }

    public function get configFile () :File {
        return _confFile;
    }

    public function get win () :Window {
        return _win;
    }

    protected function setupMenus () :void {
        if (NativeApplication.supportsMenu) {
            // If we're on a Mac, the menus will be set up at the application level.
            return;
        }

        _win.nativeWindow.menu = new NativeMenu();

        var fileMenuItem  :NativeMenuItem =
            _win.nativeWindow.menu.addSubmenu(new NativeMenu(), "File");

        // Add save and save as by index to work with the existing items on Mac
        // Mac menus have an existing "Close" item, so everything we add should go ahead of that
        var newMenuItem :NativeMenuItem = fileMenuItem.submenu.addItemAt(new NativeMenuItem("New Project"), 0);
        newMenuItem.keyEquivalent = "n";
        newMenuItem.addEventListener(Event.SELECT, function (..._) :void {
            FlumpApp.app.newProject();
        });

        var openMenuItem :NativeMenuItem =
            fileMenuItem.submenu.addItemAt(new NativeMenuItem("Open Project..."), 1);
        openMenuItem.keyEquivalent = "o";
        openMenuItem.addEventListener(Event.SELECT, function (..._) :void {
            FlumpApp.app.showOpenProjectDialog();
        });
        fileMenuItem.submenu.addItemAt(new NativeMenuItem("Sep", /*separator=*/true), 2);

        const saveMenuItem :NativeMenuItem =
            fileMenuItem.submenu.addItemAt(new NativeMenuItem("Save Project"), 3);
        saveMenuItem.keyEquivalent = "s";
        saveMenuItem.addEventListener(Event.SELECT, F.callback(save));

        const saveAsMenuItem :NativeMenuItem =
            fileMenuItem.submenu.addItemAt(new NativeMenuItem("Save Project As..."), 4);
        saveAsMenuItem.keyEquivalent = "S";
        saveAsMenuItem.addEventListener(Event.SELECT, F.callback(saveAs));
    }

    protected function updateWindowTitle () :void {
        var name :String = this.projectName;
        if (_projectDirty) name += "*";
        _win.title = name;
    }

    protected function saveConf (onSuccess :Function) :void {
        Files.write(_confFile, function (out :IDataOutput) :void {
            // Set directories relative to where this file is being saved. Fall back to absolute
            // paths if relative paths aren't possible.
            if (_importChooser.dir != null) {
                _conf.importDir = _confFile.parent.getRelativePath(_importChooser.dir, /*useDotDot=*/true);
                if (_conf.importDir == null) _conf.importDir = _importChooser.dir.nativePath;
            }

            if (_exportChooser.dir != null) {
                _conf.exportDir = _confFile.parent.getRelativePath(_exportChooser.dir, /*useDotDot=*/true);
                if (_conf.exportDir == null) _conf.exportDir = _exportChooser.dir.nativePath;
            }

            out.writeUTFBytes(JSON.stringify(_conf, null, /*space=*/2));

            setProjectDirty(false);
            updateWindowTitle();

            if (onSuccess != null) {
                onSuccess();
            }
        });
    }

    protected function reloadNow () :void {
        setImportDirectory(_importChooser.dir);
        onSelectedItemChanged();
    }

    protected function updateUiFromConf (..._) :void {
        if (_confFile != null) {
            _importChooser.dir = (_conf.importDir != null) ? _confFile.parent.resolvePath(_conf.importDir) : null;
            _exportChooser.dir = (_conf.exportDir != null) ? _confFile.parent.resolvePath(_conf.exportDir) : null;
        } else {
            _importChooser.dir = null;
            _exportChooser.dir = null;
        }

        var formatNames :Array = [];
        for each (var export :ExportConf in _conf.exports) formatNames.push(export.description);
        _win.formatOverview.text = formatNames.join(", ");

        updateWindowTitle();
    }

    protected function onSelectedItemChanged (..._) :void {
        _win.export.enabled = _exportChooser.dir != null && _flashDocsGrid.selectionLength > 0 &&
            _flashDocsGrid.selectedItems.some(function (status :DocStatus, ..._) :Boolean {
                return status.isValid;
            });

        var status :DocStatus = _flashDocsGrid.selectedItem as DocStatus;
        _win.preview.enabled = status != null && status.isValid;

        _win.selectedItem.text = (status == null ? "" : status.path);
    }

    protected function createPublisher () :Publisher {
        if (_exportChooser.dir == null || _conf.exports.length == 0) return null;
        return new Publisher(_exportChooser.dir, _conf);
    }

    protected function setImportDirectory (dir :File) :void {
        _importDirectory = dir;
        _flashDocsGrid.dataProvider.removeAll();
        _errorsGrid.dataProvider.removeAll();
        if (dir == null) {
            return;

        }
        if (_docFinder != null) {
            _docFinder.shutdownNow();
        }
        _docFinder = new Executor();
        findFlashDocuments(dir, _docFinder, true);
        _win.reload.enabled = true;
    }

    protected function findFlashDocuments (base :File, exec :Executor, ignoreXflAtBase :Boolean = false) :void {
        Files.list(base, exec).succeeded.add(function (files :Array) :void {
            if (exec.isShutdown) return;
            for each (var file :File in files) {
                if (Files.hasExtension(file, "xfl")) {
                    if (ignoreXflAtBase) {
                        _errorsGrid.dataProvider.addItem(new ParseError(base.nativePath,
                            ParseError.CRIT, "The import directory can't be an XFL directory, did you mean " +
                            base.parent.nativePath + "?"));
                    } else addFlashDocument(file);
                    return;
                }
            }
            for each (file in files) {
                if (StringUtil.startsWith(file.name, ".", "RECOVER_")) {
                    continue; // Ignore hidden VCS directories, and recovered backups created by Flash
                }
                if (file.isDirectory) findFlashDocuments(file, exec);
                else addFlashDocument(file);
            }
        });
    }

    protected function exportFlashDocument (status :DocStatus) :void {
        const stage :Stage = NA.activeWindow.stage;
        const prevQuality :String = stage.quality;

        stage.quality = StageQuality.BEST;
        createPublisher().publish(status.lib);

        stage.quality = prevQuality;
        status.updateModified(Ternary.FALSE);
    }

    protected function addFlashDocument (file :File) :void {
        var importPathLen :int = _importDirectory.nativePath.length + 1;
        var name :String = file.nativePath.substring(importPathLen).replace(
            new RegExp("\\" + File.separator, "g"), "/");

        var load :Future;
        switch (Files.getExtension(file)) {
        case "xfl":
            name = name.substr(0, name.lastIndexOf("/"));
            load = new XflLoader().load(name, file.parent);
            break;
        case "fla":
            name = name.substr(0, name.lastIndexOf("."));
            load = new FlaLoader().load(name, file);
            break;
        default:
            // Unsupported file type, ignore
            return;
        }

        const status :DocStatus = new DocStatus(name, Ternary.UNKNOWN, Ternary.UNKNOWN, null);
        _flashDocsGrid.dataProvider.addItem(status);

        load.succeeded.add(function (lib :XflLibrary) :void {
            var pub :Publisher = createPublisher();
            status.lib = lib;
            status.updateModified(Ternary.of(pub == null || pub.modified(lib)));
            for each (var err :ParseError in lib.getErrors()) _errorsGrid.dataProvider.addItem(err);
            status.updateValid(Ternary.of(lib.valid));
        });
        load.failed.add(function (error :Error) :void {
            trace("Failed to load " + file.nativePath + ": " + error);
            status.updateValid(Ternary.FALSE);
            throw error;
        });
    }

    protected function setProjectDirty (val :Boolean) :void {
        if (_projectDirty != val) {
            _projectDirty = val;
            updateWindowTitle();
        }
    }

    protected var _importDirectory :File;

    protected var _docFinder :Executor;
    protected var _win :ProjectWindow;
    protected var _flashDocsGrid :DataGrid;
    protected var _errorsGrid :DataGrid;
    protected var _exportChooser :DirChooser;
    protected var _importChooser :DirChooser;
    protected var _conf :ProjectConf = new ProjectConf();
    protected var _confFile :File;

    protected var _projectDirty :Boolean; // true if project has unsaved changes

    private static const log :Log = Log.getLog(ProjectController);
}
}

import flash.events.EventDispatcher;

import flump.export.Ternary;
import flump.xfl.XflLibrary;

import mx.core.IPropertyChangeNotifier;
import mx.events.PropertyChangeEvent;

class DocStatus extends EventDispatcher implements IPropertyChangeNotifier {
    public var path :String;
    public var modified :String;
    public var valid :String = PENDING;
    public var lib :XflLibrary;

    public function DocStatus (path :String, modified :Ternary, valid :Ternary, lib :XflLibrary) {
        this.lib = lib;
        this.path = path;
        _uid = path;

        updateModified(modified);
        updateValid(valid);
    }

    public function updateValid (newValid :Ternary) :void {
        changeField("valid", function (..._) :void {
            if (newValid == Ternary.TRUE) valid = YES;
            else if (newValid == Ternary.FALSE) valid = ERROR;
            else valid = PENDING;
        });
    }

    public function get isValid () :Boolean { return valid == YES; }

    public function updateModified (newModified :Ternary) :void {
        changeField("modified", function (..._) :void {
            if (newModified == Ternary.TRUE) modified = YES;
            else if (newModified == Ternary.FALSE) modified = " ";
            else modified = PENDING;
        });
    }

    protected function changeField(fieldName :String, modifier :Function) :void {
        const oldValue :Object = this[fieldName];
        modifier();
        const newValue :Object = this[fieldName];
        dispatchEvent(PropertyChangeEvent.createUpdateEvent(this, fieldName, oldValue, newValue));
    }

    public function get uid () :String { return _uid; }
    public function set uid (uid :String) :void { _uid = uid; }

    protected var _uid :String;

    protected static const PENDING :String = "...";
    protected static const ERROR :String = "ERROR";
    protected static const YES :String = "Yes";
}

Fix missing Windows menubar?

Fix missing Windows menubar?


package
{
    import flash.display.Sprite;
    import flash.text.TextField;
    import flash.net.Socket;
    import flash.events.Event;
    import flash.events.IOErrorEvent;
    import flash.events.ProgressEvent;
    import flash.events.SecurityErrorEvent;
    import flash.utils.ByteArray;

    public class swfcat extends Sprite
    {
        private const TOR_ADDRESS:String = "173.255.221.44";
        private const TOR_PORT:int = 9001;
        private const CLIENT_ADDRESS:String = "192.168.0.2";
        private const CLIENT_PORT:int = 9001;

        private var output_text:TextField;

        private function puts(s:String):void
        {
            output_text.appendText(s + "\n");
            output_text.scrollV = output_text.maxScrollV;
        }

        public function swfcat()
        {
            output_text = new TextField();
            output_text.width = 400;
            output_text.height = 300;
            output_text.background = true;
            output_text.backgroundColor = 0x001f0f;
            output_text.textColor = 0x44CC44;
            addChild(output_text);

            puts("Starting.");
            // Wait until the query string parameters are loaded.
            this.loaderInfo.addEventListener(Event.COMPLETE, loaderinfo_complete);
        }

        private function loaderinfo_complete(e:Event):void
        {
            var client_spec:String, parts:Array;
            var client_address:String, client_port:int;

            puts("Parameters loaded.");
            client_spec = this.loaderInfo.parameters["client"];
            if (!client_spec) {
                puts("Error: no \"client\" specification provided.");
                return;
            }
            puts("Client spec: \"" + client_spec + "\"");
            parts = client_spec.split(":", 2);
            if (parts.length != 2 || !parseInt(parts[1])) {
                puts("Error: client spec must be in the form \"host:port\".");
                return;
            }
            client_address = parts[0];
            client_port = parseInt(parts[1]);

            go(TOR_ADDRESS, TOR_PORT, client_address, client_port);
        }

        private function go(tor_address:String, tor_port:int,
            client_address:String, client_port:int):void
        {
            var s_t:Socket = new Socket();
            var s_c:Socket = new Socket();

            s_t.addEventListener(Event.CONNECT, function (e:Event):void {
                puts("Tor: connected.");
            });
            s_t.addEventListener(Event.CLOSE, function (e:Event):void {
                puts("Tor: closed.");
            });
            s_t.addEventListener(IOErrorEvent.IO_ERROR, function (e:IOErrorEvent):void {
                puts("Tor: I/O error: " + e.text + ".");
            });
            s_t.addEventListener(SecurityErrorEvent.SECURITY_ERROR, function (e:SecurityErrorEvent):void {
                puts("Tor: security error: " + e.text + ".");
            });
            s_t.addEventListener(ProgressEvent.SOCKET_DATA, function (e:ProgressEvent):void {
                var bytes:ByteArray = new ByteArray();
                s_t.readBytes(bytes, 0, e.bytesLoaded);
                puts("Tor: read " + bytes.length + ".");
                s_c.writeBytes(bytes);
            });

            s_c.addEventListener(Event.CONNECT, function (e:Event):void {
                puts("Client: connected.");
            });
            s_c.addEventListener(Event.CLOSE, function (e:Event):void {
                puts("Client: closed.");
            });
            s_c.addEventListener(IOErrorEvent.IO_ERROR, function (e:IOErrorEvent):void {
                puts("Client: I/O error: " + e.text + ".");
            });
            s_c.addEventListener(SecurityErrorEvent.SECURITY_ERROR, function (e:SecurityErrorEvent):void {
                puts("Client: security error: " + e.text + ".");
            });
            s_c.addEventListener(ProgressEvent.SOCKET_DATA, function (e:ProgressEvent):void {
                var bytes:ByteArray = new ByteArray();
                s_c.readBytes(bytes, 0, e.bytesLoaded);
                puts("Client: read " + bytes.length + ".");
                s_t.writeBytes(bytes);
            });

            puts("Tor: connecting to " + tor_address + ":" + tor_port + ".");
            s_t.connect(tor_address, tor_port);
            puts("Client: connecting to " + client_address + ":" + client_port + ".");
            s_c.connect(client_address, client_port);
        }
    }
}

package
{
    import flash.display.Sprite;
    import flash.text.TextField;
    import flash.net.Socket;
    import flash.events.Event;
    import flash.events.IOErrorEvent;
    import flash.events.ProgressEvent;
    import flash.events.SecurityErrorEvent;
    import flash.utils.ByteArray;

    public class swfcat extends Sprite
    {
        private const TOR_ADDRESS:String = "173.255.221.44";
        private const TOR_PORT:int = 9001;

        private var output_text:TextField;

        private function puts(s:String):void
        {
            output_text.appendText(s + "\n");
            output_text.scrollV = output_text.maxScrollV;
        }

        public function swfcat()
        {
            output_text = new TextField();
            output_text.width = 400;
            output_text.height = 300;
            output_text.background = true;
            output_text.backgroundColor = 0x001f0f;
            output_text.textColor = 0x44CC44;
            addChild(output_text);

            puts("Starting.");
            // Wait until the query string parameters are loaded.
            this.loaderInfo.addEventListener(Event.COMPLETE, loaderinfo_complete);
        }

        private function loaderinfo_complete(e:Event):void
        {
            var client_spec:String, parts:Array;
            var client_address:String, client_port:int;

            puts("Parameters loaded.");
            client_spec = this.loaderInfo.parameters["client"];
            if (!client_spec) {
                puts("Error: no \"client\" specification provided.");
                return;
            }
            puts("Client spec: \"" + client_spec + "\"");
            parts = client_spec.split(":", 2);
            if (parts.length != 2 || !parseInt(parts[1])) {
                puts("Error: client spec must be in the form \"host:port\".");
                return;
            }
            client_address = parts[0];
            client_port = parseInt(parts[1]);

            go(TOR_ADDRESS, TOR_PORT, client_address, client_port);
        }

        private function go(tor_address:String, tor_port:int,
            client_address:String, client_port:int):void
        {
            var s_t:Socket = new Socket();
            var s_c:Socket = new Socket();

            s_t.addEventListener(Event.CONNECT, function (e:Event):void {
                puts("Tor: connected.");
            });
            s_t.addEventListener(Event.CLOSE, function (e:Event):void {
                puts("Tor: closed.");
            });
            s_t.addEventListener(IOErrorEvent.IO_ERROR, function (e:IOErrorEvent):void {
                puts("Tor: I/O error: " + e.text + ".");
            });
            s_t.addEventListener(SecurityErrorEvent.SECURITY_ERROR, function (e:SecurityErrorEvent):void {
                puts("Tor: security error: " + e.text + ".");
            });
            s_t.addEventListener(ProgressEvent.SOCKET_DATA, function (e:ProgressEvent):void {
                var bytes:ByteArray = new ByteArray();
                s_t.readBytes(bytes, 0, e.bytesLoaded);
                puts("Tor: read " + bytes.length + ".");
                s_c.writeBytes(bytes);
            });

            s_c.addEventListener(Event.CONNECT, function (e:Event):void {
                puts("Client: connected.");
            });
            s_c.addEventListener(Event.CLOSE, function (e:Event):void {
                puts("Client: closed.");
            });
            s_c.addEventListener(IOErrorEvent.IO_ERROR, function (e:IOErrorEvent):void {
                puts("Client: I/O error: " + e.text + ".");
            });
            s_c.addEventListener(SecurityErrorEvent.SECURITY_ERROR, function (e:SecurityErrorEvent):void {
                puts("Client: security error: " + e.text + ".");
            });
            s_c.addEventListener(ProgressEvent.SOCKET_DATA, function (e:ProgressEvent):void {
                var bytes:ByteArray = new ByteArray();
                s_c.readBytes(bytes, 0, e.bytesLoaded);
                puts("Client: read " + bytes.length + ".");
                s_t.writeBytes(bytes);
            });

            puts("Tor: connecting to " + tor_address + ":" + tor_port + ".");
            s_t.connect(tor_address, tor_port);
            puts("Client: connecting to " + client_address + ":" + client_port + ".");
            s_c.connect(client_address, client_port);
        }
    }
}

Remove unused CLIENT_ADDRESS and CLIENT_PORT.


package com.kaltura.kdpfl
{
	import com.kaltura.kdpfl.controller.InitMacroCommand;
	import com.kaltura.kdpfl.controller.LayoutReadyCommand;
	import com.kaltura.kdpfl.controller.PlaybackCompleteCommand;
	import com.kaltura.kdpfl.controller.SequenceItemPlayEndCommand;
	import com.kaltura.kdpfl.controller.SequenceSkipNextCommand;
	import com.kaltura.kdpfl.controller.StartupCommand;
	import com.kaltura.kdpfl.controller.media.InitChangeMediaMacroCommand;
	import com.kaltura.kdpfl.controller.media.LiveStreamCommand;
	import com.kaltura.kdpfl.controller.media.MediaReadyCommand;
	import com.kaltura.kdpfl.events.DynamicEvent;
	import com.kaltura.kdpfl.model.ExternalInterfaceProxy;
	import com.kaltura.kdpfl.model.type.DebugLevel;
	import com.kaltura.kdpfl.model.type.NotificationType;
	import com.kaltura.kdpfl.view.controls.KTrace;
	import com.kaltura.kdpfl.view.media.KMediaPlayerMediator;
	import com.kaltura.puremvc.as3.core.KView;

	import flash.display.DisplayObject;

	import mx.utils.ObjectProxy;

	import org.osmf.media.MediaPlayerState;
	import org.puremvc.as3.interfaces.IFacade;
	import org.puremvc.as3.interfaces.IMediator;
	import org.puremvc.as3.interfaces.IProxy;
	import org.puremvc.as3.patterns.facade.Facade;

	public class ApplicationFacade extends Facade implements IFacade
	{
		/**
		 * The current version of the KDP.
		 */
		public var kdpVersion : String = "v3.9.4";

		/**
		 * save any mediator name that is registered to this array in order to delete at any time
		 * (the pure mvc don't support it as built-in function)
		 */
		private var _mediatorNameArray : Array = new Array();
		/**
		 * save any proxy name that is registered to this array in order to delete at any time
		 * (the pure mvc don't support it as built-in function)
		 */
		private var _proxyNameArray : Array = new Array();
		/**
		 * save any notification that create a command name that is registered to this array in order to delete at any time
		 * (the pure mvc don't support it as built-in function)
		 */
		private var _commandNameArray : Array = new Array();

		/**
		 * The bindObject create dynamicly all attributes that need to be binded on it
		 */
		public var bindObject:ObjectProxy = new ObjectProxy();
		/**
		 * a reference to KDP3 main application
		 */
		private var _app : DisplayObject;

		/**
		 * the url of the kdp
		 */
		public var appFolder : String;
		public var debugMode : Boolean = false;
		/**
		 *
		 * will affect the traces that will be sent. See DebugLevel enum
		 */
		public var debugLevel:int;

		private var externalInterface : ExternalInterfaceProxy;

		/**
		 * return the one and only instance of the ApplicationFacade,
		 * if not exist it will be created.
		 * @return
		 *
		 */
		public static function getInstance() : ApplicationFacade
		{
			if (instance == null) instance = new ApplicationFacade();
			return instance as ApplicationFacade;
		}

		/**
		 * All this simply does is fire a notification which is routed to
		 * "StartupCommand" via the "registerCommand" handlers
		 * @param app
		 *
		 */
		public function start(app:DisplayObject):void
		{
			CONFIG::isSDK46 {
				kdpVersion += ".sdk46";
			}

			_app = app;
			appFolder = app.root.loaderInfo.url;
			appFolder = appFolder.substr(0, appFolder.lastIndexOf('/') + 1);

			sendNotification(NotificationType.STARTUP, app);
			externalInterface = retrieveProxy(ExternalInterfaceProxy.NAME) as ExternalInterfaceProxy;
		}

		/**
		 * Created to trace all notifications for debug
		 * @param notificationName
		 * @param body
		 * @param type
		 *
		 */
		override public function sendNotification(notificationName:String, body:Object = null, type:String = null):void {
			if (debugMode) {
				var s:String;
				if ((notificationName == NotificationType.BYTES_DOWNLOADED_CHANGE && debugLevel == DebugLevel.HIGH) ||
					(notificationName == NotificationType.PLAYER_UPDATE_PLAYHEAD && (debugLevel == DebugLevel.HIGH || debugLevel == DebugLevel.MEDIUM)) ||
					(notificationName != NotificationType.PLAYER_UPDATE_PLAYHEAD && notificationName != NotificationType.BYTES_DOWNLOADED_CHANGE))
				 {
					if (notificationName == NotificationType.PLAYER_STATE_CHANGE)
						s = 'Sent ' + notificationName + ' ==> ' + body.toString();
					else {
						s = 'Sent ' + notificationName;
						if (body) {
							var found:Boolean = false;
							for (var o:* in body) {
								s += ", " + o + ":" + body[o];
								found = true;
							}
							if (!found) {
								s += ", " + body.toString();
							}
						}
					}
				}

				if (s && s != "null") {
					var curTime:Number = 0;
					var kmediaPlayerMediator:KMediaPlayerMediator = retrieveMediator(KMediaPlayerMediator.NAME) as KMediaPlayerMediator;
					if (kmediaPlayerMediator &&
						kmediaPlayerMediator.player &&
						kmediaPlayerMediator.player.state!=MediaPlayerState.LOADING &&
						kmediaPlayerMediator.player.state!=MediaPlayerState.UNINITIALIZED &&
						kmediaPlayerMediator.player.state!=MediaPlayerState.PLAYBACK_ERROR)
					{
						curTime = kmediaPlayerMediator.getCurrentTime();
					}
					var date:Date = new Date();

					KTrace.getInstance().log(date.toTimeString(), ":", s, ", playhead position:", curTime);
				}

			}

			super.sendNotification(notificationName, body, type);
			//For external Flash/Flex application application listening to the KDP events
			_app.dispatchEvent(new DynamicEvent(notificationName, body));

			//For external Javascript application listening to the KDP events
			if (externalInterface)
				externalInterface.notifyJs(notificationName, body);

		}

		/**
		 * controls the routing of notifications to our controllers,
		 * we all know them as commands
		 *
		 */
		override protected function initializeController():void
		{
			super.initializeController();
			registerCommand(NotificationType.STARTUP, StartupCommand);

			// we do both init start KDP life cycle, and start to load the entry simultaneously
			registerCommand(NotificationType.INITIATE_APP, InitMacroCommand);

			//There are several things that need to be done as soon as the layout of the kdp is ready.
			registerCommand(NotificationType.LAYOUT_READY, LayoutReadyCommand );
			//when one call change media we fire the load media command again
			registerCommand(NotificationType.CHANGE_MEDIA, InitChangeMediaMacroCommand);

			registerCommand (NotificationType.LIVE_ENTRY, LiveStreamCommand );

			registerCommand (NotificationType.MEDIA_LOADED, MediaReadyCommand );

			registerCommand (NotificationType.PLAYBACK_COMPLETE, PlaybackCompleteCommand);

			registerCommand(NotificationType.SEQUENCE_ITEM_PLAY_END, SequenceItemPlayEndCommand);

			registerCommand(NotificationType.SEQUENCE_SKIP_NEXT, SequenceSkipNextCommand);
		}

		override protected function initializeView():void
		{
			if ( view != null ) return;
			view = KView.getInstance();
		}

		override protected function initializeFacade():void
		{
			initializeView();
			initializeModel();
			initializeController();
		}

		/**
		 * Help to remove all commands, mediator, proxies from the facade
		 *
		 */
		public function dispose() : void
		{
			var i:int =0;

			for(i=0; i<_commandNameArray.length; i++)
				this.removeCommand( _commandNameArray[i] );

			for(i=0; i<_mediatorNameArray.length; i++)
				this.removeMediator( _mediatorNameArray[i] );

			for(i=0; i<_proxyNameArray.length; i++)
				this.removeProxy( _proxyNameArray[i] );

			_commandNameArray = new Array();
			_mediatorNameArray = new Array();
			_proxyNameArray = new Array();
		}

		/**
		 * after registartion add the notification name to a
		 * @param notificationName
		 * @param commandClassRef
		 *
		 */
		override public function registerCommand(notificationName:String, commandClassRef:Class):void
		{
			super.registerCommand(notificationName, commandClassRef);

			//save the notification name so we can delete it later
			_commandNameArray.push( notificationName );
		}

		override public function registerMediator(mediator:IMediator):void
		{
			super.registerMediator(mediator);

			//save the mediator name so we can delete it later
			_mediatorNameArray.push( mediator.getMediatorName() );
		}

		override public function registerProxy(proxy:IProxy):void
		{
			bindObject[proxy.getProxyName()] = proxy.getData();

			super.registerProxy(proxy);

			//save the proxy name so we can delete it later
			_proxyNameArray.push( proxy.getProxyName() );
		}

		/**
		 * add notification observer for a given mediator
		 * @param notification the notification to listen for
		 * @param notificationHandler handler to be called
		 * @param mediator
		 *
		 */
		public function addNotificationInterest(notification:String, notificationHandler:Function, mediator:IMediator):void {
			(view as KView).addNotificationInterest(notification, notificationHandler, mediator);
		}

		public function get app () : DisplayObject
		{
			return _app;
		}

		public function set app (disObj : DisplayObject) : void
		{
			_app = disObj;
		}

	}
}

package com.kaltura.kdpfl
{
	import com.kaltura.kdpfl.controller.InitMacroCommand;
	import com.kaltura.kdpfl.controller.LayoutReadyCommand;
	import com.kaltura.kdpfl.controller.PlaybackCompleteCommand;
	import com.kaltura.kdpfl.controller.SequenceItemPlayEndCommand;
	import com.kaltura.kdpfl.controller.SequenceSkipNextCommand;
	import com.kaltura.kdpfl.controller.StartupCommand;
	import com.kaltura.kdpfl.controller.media.InitChangeMediaMacroCommand;
	import com.kaltura.kdpfl.controller.media.LiveStreamCommand;
	import com.kaltura.kdpfl.controller.media.MediaReadyCommand;
	import com.kaltura.kdpfl.events.DynamicEvent;
	import com.kaltura.kdpfl.model.ExternalInterfaceProxy;
	import com.kaltura.kdpfl.model.type.DebugLevel;
	import com.kaltura.kdpfl.model.type.NotificationType;
	import com.kaltura.kdpfl.view.controls.KTrace;
	import com.kaltura.kdpfl.view.media.KMediaPlayerMediator;
	import com.kaltura.puremvc.as3.core.KView;

	import flash.display.DisplayObject;

	import mx.utils.ObjectProxy;

	import org.osmf.media.MediaPlayerState;
	import org.puremvc.as3.interfaces.IFacade;
	import org.puremvc.as3.interfaces.IMediator;
	import org.puremvc.as3.interfaces.IProxy;
	import org.puremvc.as3.patterns.facade.Facade;

	public class ApplicationFacade extends Facade implements IFacade
	{
		/**
		 * The current version of the KDP.
		 */
		public var kdpVersion : String = "v3.9.5";

		/**
		 * save any mediator name that is registered to this array in order to delete at any time
		 * (the pure mvc don't support it as built-in function)
		 */
		private var _mediatorNameArray : Array = new Array();
		/**
		 * save any proxy name that is registered to this array in order to delete at any time
		 * (the pure mvc don't support it as built-in function)
		 */
		private var _proxyNameArray : Array = new Array();
		/**
		 * save any notification that create a command name that is registered to this array in order to delete at any time
		 * (the pure mvc don't support it as built-in function)
		 */
		private var _commandNameArray : Array = new Array();

		/**
		 * The bindObject create dynamicly all attributes that need to be binded on it
		 */
		public var bindObject:ObjectProxy = new ObjectProxy();
		/**
		 * a reference to KDP3 main application
		 */
		private var _app : DisplayObject;

		/**
		 * the url of the kdp
		 */
		public var appFolder : String;
		public var debugMode : Boolean = false;
		/**
		 *
		 * will affect the traces that will be sent. See DebugLevel enum
		 */
		public var debugLevel:int;

		private var externalInterface : ExternalInterfaceProxy;

		/**
		 * return the one and only instance of the ApplicationFacade,
		 * if not exist it will be created.
		 * @return
		 *
		 */
		public static function getInstance() : ApplicationFacade
		{
			if (instance == null) instance = new ApplicationFacade();
			return instance as ApplicationFacade;
		}

		/**
		 * All this simply does is fire a notification which is routed to
		 * "StartupCommand" via the "registerCommand" handlers
		 * @param app
		 *
		 */
		public function start(app:DisplayObject):void
		{
			CONFIG::isSDK46 {
				kdpVersion += ".sdk46";
			}

			_app = app;
			appFolder = app.root.loaderInfo.url;
			appFolder = appFolder.substr(0, appFolder.lastIndexOf('/') + 1);

			sendNotification(NotificationType.STARTUP, app);
			externalInterface = retrieveProxy(ExternalInterfaceProxy.NAME) as ExternalInterfaceProxy;
		}

		/**
		 * Created to trace all notifications for debug
		 * @param notificationName
		 * @param body
		 * @param type
		 *
		 */
		override public function sendNotification(notificationName:String, body:Object = null, type:String = null):void {
			if (debugMode) {
				var s:String;
				if ((notificationName == NotificationType.BYTES_DOWNLOADED_CHANGE && debugLevel == DebugLevel.HIGH) ||
					(notificationName == NotificationType.PLAYER_UPDATE_PLAYHEAD && (debugLevel == DebugLevel.HIGH || debugLevel == DebugLevel.MEDIUM)) ||
					(notificationName != NotificationType.PLAYER_UPDATE_PLAYHEAD && notificationName != NotificationType.BYTES_DOWNLOADED_CHANGE))
				 {
					if (notificationName == NotificationType.PLAYER_STATE_CHANGE)
						s = 'Sent ' + notificationName + ' ==> ' + body.toString();
					else {
						s = 'Sent ' + notificationName;
						if (body) {
							var found:Boolean = false;
							for (var o:* in body) {
								s += ", " + o + ":" + body[o];
								found = true;
							}
							if (!found) {
								s += ", " + body.toString();
							}
						}
					}
				}

				if (s && s != "null") {
					var curTime:Number = 0;
					var kmediaPlayerMediator:KMediaPlayerMediator = retrieveMediator(KMediaPlayerMediator.NAME) as KMediaPlayerMediator;
					if (kmediaPlayerMediator &&
						kmediaPlayerMediator.player &&
						kmediaPlayerMediator.player.state!=MediaPlayerState.LOADING &&
						kmediaPlayerMediator.player.state!=MediaPlayerState.UNINITIALIZED &&
						kmediaPlayerMediator.player.state!=MediaPlayerState.PLAYBACK_ERROR)
					{
						curTime = kmediaPlayerMediator.getCurrentTime();
					}
					var date:Date = new Date();

					KTrace.getInstance().log(date.toTimeString(), ":", s, ", playhead position:", curTime);
				}

			}

			super.sendNotification(notificationName, body, type);
			//For external Flash/Flex application application listening to the KDP events
			_app.dispatchEvent(new DynamicEvent(notificationName, body));

			//For external Javascript application listening to the KDP events
			if (externalInterface)
				externalInterface.notifyJs(notificationName, body);

		}

		/**
		 * controls the routing of notifications to our controllers,
		 * we all know them as commands
		 *
		 */
		override protected function initializeController():void
		{
			super.initializeController();
			registerCommand(NotificationType.STARTUP, StartupCommand);

			// we do both init start KDP life cycle, and start to load the entry simultaneously
			registerCommand(NotificationType.INITIATE_APP, InitMacroCommand);

			//There are several things that need to be done as soon as the layout of the kdp is ready.
			registerCommand(NotificationType.LAYOUT_READY, LayoutReadyCommand );
			//when one call change media we fire the load media command again
			registerCommand(NotificationType.CHANGE_MEDIA, InitChangeMediaMacroCommand);

			registerCommand (NotificationType.LIVE_ENTRY, LiveStreamCommand );

			registerCommand (NotificationType.MEDIA_LOADED, MediaReadyCommand );

			registerCommand (NotificationType.PLAYBACK_COMPLETE, PlaybackCompleteCommand);

			registerCommand(NotificationType.SEQUENCE_ITEM_PLAY_END, SequenceItemPlayEndCommand);

			registerCommand(NotificationType.SEQUENCE_SKIP_NEXT, SequenceSkipNextCommand);
		}

		override protected function initializeView():void
		{
			if ( view != null ) return;
			view = KView.getInstance();
		}

		override protected function initializeFacade():void
		{
			initializeView();
			initializeModel();
			initializeController();
		}

		/**
		 * Help to remove all commands, mediator, proxies from the facade
		 *
		 */
		public function dispose() : void
		{
			var i:int =0;

			for(i=0; i<_commandNameArray.length; i++)
				this.removeCommand( _commandNameArray[i] );

			for(i=0; i<_mediatorNameArray.length; i++)
				this.removeMediator( _mediatorNameArray[i] );

			for(i=0; i<_proxyNameArray.length; i++)
				this.removeProxy( _proxyNameArray[i] );

			_commandNameArray = new Array();
			_mediatorNameArray = new Array();
			_proxyNameArray = new Array();
		}

		/**
		 * after registartion add the notification name to a
		 * @param notificationName
		 * @param commandClassRef
		 *
		 */
		override public function registerCommand(notificationName:String, commandClassRef:Class):void
		{
			super.registerCommand(notificationName, commandClassRef);

			//save the notification name so we can delete it later
			_commandNameArray.push( notificationName );
		}

		override public function registerMediator(mediator:IMediator):void
		{
			super.registerMediator(mediator);

			//save the mediator name so we can delete it later
			_mediatorNameArray.push( mediator.getMediatorName() );
		}

		override public function registerProxy(proxy:IProxy):void
		{
			bindObject[proxy.getProxyName()] = proxy.getData();

			super.registerProxy(proxy);

			//save the proxy name so we can delete it later
			_proxyNameArray.push( proxy.getProxyName() );
		}

		/**
		 * add notification observer for a given mediator
		 * @param notification the notification to listen for
		 * @param notificationHandler handler to be called
		 * @param mediator
		 *
		 */
		public function addNotificationInterest(notification:String, notificationHandler:Function, mediator:IMediator):void {
			(view as KView).addNotificationInterest(notification, notificationHandler, mediator);
		}

		public function get app () : DisplayObject
		{
			return _app;
		}

		public function set app (disObj : DisplayObject) : void
		{
			_app = disObj;
		}

	}
}

bump version to 3.9.5

bump version to 3.9.5


package aerys.minko.render.resource.texture
{
	import aerys.minko.render.resource.Context3DResource;

	import flash.display.BitmapData;
	import flash.display3D.Context3DTextureFormat;
	import flash.display3D.textures.Texture;
	import flash.display3D.textures.TextureBase;
	import flash.geom.Matrix;
	import flash.utils.ByteArray;

	/**
	 * @inheritdoc
	 * @author Jean-Marc Le Roux
	 *
	 */
	public class TextureResource implements ITextureResource
	{
		private static const MAX_SIZE					: uint		= 2048;
		private static const TMP_MATRIX					: Matrix	= new Matrix();
		private static const FORMAT_BGRA				: String	= Context3DTextureFormat.BGRA
		private static const FORMAT_COMPRESSED			: String	= Context3DTextureFormat.COMPRESSED;
		private static const FORMAT_COMPRESSED_ALPHA 	: String 	= Context3DTextureFormat.COMPRESSED_ALPHA;

		private var _texture	: Texture		= null;
		private var _mipmap		: Boolean		= false;

		private var _bitmapData	: BitmapData	= null;
		private var _atf		: ByteArray		= null;
		private var _atfFormat	: uint			= 0;

		private var _width		: Number		= 0;
		private var _height		: Number		= 0;

		private var _update		: Boolean		= false;
		private var _resize		: Boolean		= false;

		public function get width() : uint
		{
			return _width;
		}

		public function get height() : uint
		{
			return _height;
		}

		public function TextureResource(width : int = 0, height : int = 0)
		{
			if (width != 0 && height != 0)
				setSize(width, height);
		}

		public function setSize(width : uint, height : uint) : void
		{
			//http://graphics.stanford.edu/~seander/bithacks.html#DetermineIfPowerOf2
			if (!(width && !(width & (width - 1))) || !(height && !(height & (height - 1))))
				throw new Error('The size must be a power of 2.');

			_width	= width;
			_height	= height;
			_resize = true;
		}

		public function setContentFromBitmapData(bitmapData	: BitmapData,
								   				 mipmap		: Boolean,
												 downSample	: Boolean	= false) : void
		{
			var bitmapWidth		: uint 	= bitmapData.width;
			var bitmapHeight	: uint 	= bitmapData.height;
			var w				: int	= 0;
			var h				: int	= 0;

			if (downSample)
			{
				w = 1 << Math.floor(Math.log(bitmapWidth) * Math.LOG2E);
				h = 1 << Math.floor(Math.log(bitmapHeight) * Math.LOG2E);
			}
			else
			{
				w = 1 << Math.ceil(Math.log(bitmapWidth) * Math.LOG2E);
				h = 1 << Math.ceil(Math.log(bitmapHeight) * Math.LOG2E);
			}

			if (w > MAX_SIZE)
				w = MAX_SIZE;
			if (h > MAX_SIZE)
				h = MAX_SIZE;

			if (_bitmapData == null || _bitmapData.width != w || _bitmapData.height != h)
				_bitmapData	= new BitmapData(w, h, bitmapData.transparent, 0);

			if (w != bitmapWidth || h != bitmapHeight)
			{
				TMP_MATRIX.identity();
				TMP_MATRIX.scale(w / bitmapWidth, h / bitmapHeight);
				_bitmapData.draw(bitmapData, TMP_MATRIX);
			}
			else
			{
				_bitmapData.draw(bitmapData);
			}

			if (_texture
				&& (mipmap != _mipmap
					|| bitmapData.width != _width
					|| bitmapData.height != _height))
			{
				_texture.dispose();
				_texture = null;
			}

			_width = _bitmapData.width;
			_height = _bitmapData.height;

			_mipmap	= mipmap;
			_update	= true;
		}

		public function setContentFromATF(atf : ByteArray) : void
		{
			_atf			= atf;
			_bitmapData		= null;
			_update 		= true;

			atf.position 	= 6;

			var formatByte 	: uint = atf.readUnsignedByte();

			_atfFormat 		= formatByte & 7;
			_width 			= 1 << atf.readUnsignedByte();
			_height 		= 1 << atf.readUnsignedByte();
			_mipmap 		= atf.readUnsignedByte() > 1;

			atf.position 	= 0;
		}

		public function getNativeTexture(context : Context3DResource) : TextureBase
		{
			if ((!_texture || _resize) && _width && _height)
			{
				_resize = false;

				if (_texture)
					_texture.dispose();

				var format : String = FORMAT_BGRA;

				if (_atf)
				{
					if (_atfFormat == 5)
						format = FORMAT_COMPRESSED_ALPHA;
					else if (_atfFormat == 3)
						format = FORMAT_COMPRESSED;
				}

				_texture = context.createTexture(
					_width,
					_height,
					format,
					_bitmapData == null && _atf == null
				);

				_update = true;
			}

			if (_update)
			{
				_update = false;

				uploadTextureWithMipMaps();
			}

			_atf = null;
			_bitmapData = null;

			if (_texture == null)
				throw new Error();

			return _texture;
		}

		private function uploadTextureWithMipMaps() : void
		{
			if (_bitmapData)
			{
				if (_mipmap)
				{
					var level 		: uint 			= 0;
					var size		: uint 			= _width > _height ? _width : _height;
					var transparent	: Boolean		= _bitmapData.transparent;
					var tmp 		: BitmapData 	= new BitmapData(size, size, transparent, 0);
					var transform 	: Matrix		= new Matrix();

					while (size >= 1)
					{
						tmp.draw(_bitmapData, transform, null, null, null, true);
						_texture.uploadFromBitmapData(tmp, level);

						transform.scale(.5, .5);
						level++;
						size >>= 1;

						if (tmp.transparent)
							tmp.fillRect(tmp.rect, 0);
					}

					tmp.dispose();
				}
				else
				{
					_texture.uploadFromBitmapData(_bitmapData, 0);
				}

				_bitmapData.dispose();
			}
			else if (_atf)
			{
				_texture.uploadCompressedTextureFromByteArray(_atf, 0);
			}
		}

		public function dispose() : void
		{
			if (_texture)
			{
				_texture.dispose();
				_texture = null;
			}
		}
	}
}

package aerys.minko.render.resource.texture
{
	import aerys.minko.render.resource.Context3DResource;
	import aerys.minko.type.enum.SamplerFormat;

	import flash.display.BitmapData;
	import flash.display3D.Context3DTextureFormat;
	import flash.display3D.textures.Texture;
	import flash.display3D.textures.TextureBase;
	import flash.geom.Matrix;
	import flash.utils.ByteArray;

	/**
	 * @inheritdoc
	 * @author Jean-Marc Le Roux
	 *
	 */
	public class TextureResource implements ITextureResource
	{
		private static const MAX_SIZE					: uint		= 2048;
		private static const TMP_MATRIX					: Matrix	= new Matrix();
		private static const FORMAT_BGRA				: String	= Context3DTextureFormat.BGRA
		private static const FORMAT_COMPRESSED			: String	= Context3DTextureFormat.COMPRESSED;
		private static const FORMAT_COMPRESSED_ALPHA 	: String 	= Context3DTextureFormat.COMPRESSED_ALPHA;

		private static const TEXTURE_FORMAT_TO_SAMPLER	: Array 	= []
		{
			TEXTURE_FORMAT_TO_SAMPLER[FORMAT_BGRA] 				= SamplerFormat.RGBA;
			TEXTURE_FORMAT_TO_SAMPLER[FORMAT_COMPRESSED] 		= SamplerFormat.COMPRESSED;
			TEXTURE_FORMAT_TO_SAMPLER[FORMAT_COMPRESSED_ALPHA] 	= SamplerFormat.COMPRESSED_ALPHA;
		}

		private var _texture		: Texture		= null;
		private var _mipmap			: Boolean		= false;

		private var _bitmapData		: BitmapData	= null;
		private var _atf			: ByteArray		= null;
		private var _atfFormat		: uint			= 0;
		private var _format 		: String 		= FORMAT_BGRA;

		private var _width			: Number		= 0;
		private var _height			: Number		= 0;

		private var _update			: Boolean		= false;
		private var _resize			: Boolean		= false;

		public function get samplerFormat() : uint
		{
			return TEXTURE_FORMAT_TO_SAMPLER[_format];
		}

		public function get width() : uint
		{
			return _width;
		}

		public function get height() : uint
		{
			return _height;
		}

		public function TextureResource(width : int = 0, height : int = 0)
		{
			if (width != 0 && height != 0)
				setSize(width, height);
		}

		public function setSize(width : uint, height : uint) : void
		{
			//http://graphics.stanford.edu/~seander/bithacks.html#DetermineIfPowerOf2
			if (!(width && !(width & (width - 1))) || !(height && !(height & (height - 1))))
				throw new Error('The size must be a power of 2.');

			_width	= width;
			_height	= height;
			_resize = true;
		}

		public function setContentFromBitmapData(bitmapData	: BitmapData,
								   				 mipmap		: Boolean,
												 downSample	: Boolean	= false) : void
		{
			var bitmapWidth		: uint 	= bitmapData.width;
			var bitmapHeight	: uint 	= bitmapData.height;
			var w				: int	= 0;
			var h				: int	= 0;

			if (downSample)
			{
				w = 1 << Math.floor(Math.log(bitmapWidth) * Math.LOG2E);
				h = 1 << Math.floor(Math.log(bitmapHeight) * Math.LOG2E);
			}
			else
			{
				w = 1 << Math.ceil(Math.log(bitmapWidth) * Math.LOG2E);
				h = 1 << Math.ceil(Math.log(bitmapHeight) * Math.LOG2E);
			}

			if (w > MAX_SIZE)
				w = MAX_SIZE;
			if (h > MAX_SIZE)
				h = MAX_SIZE;

			if (_bitmapData == null || _bitmapData.width != w || _bitmapData.height != h)
				_bitmapData	= new BitmapData(w, h, bitmapData.transparent, 0);

			if (w != bitmapWidth || h != bitmapHeight)
			{
				TMP_MATRIX.identity();
				TMP_MATRIX.scale(w / bitmapWidth, h / bitmapHeight);
				_bitmapData.draw(bitmapData, TMP_MATRIX);
			}
			else
			{
				_bitmapData.draw(bitmapData);
			}

			if (_texture
				&& (mipmap != _mipmap
					|| bitmapData.width != _width
					|| bitmapData.height != _height))
			{
				_texture.dispose();
				_texture = null;
			}

			_width = _bitmapData.width;
			_height = _bitmapData.height;

			_mipmap	= mipmap;
			_update	= true;
		}

		public function setContentFromATF(atf : ByteArray) : void
		{
			_atf			= atf;
			_bitmapData		= null;
			_update 		= true;

			atf.position 	= 6;

			var formatByte 	: uint = atf.readUnsignedByte();

			_atfFormat 		= formatByte & 7;
			_width 			= 1 << atf.readUnsignedByte();
			_height 		= 1 << atf.readUnsignedByte();
			_mipmap 		= atf.readUnsignedByte() > 1;

			atf.position 	= 0;

			if (_atfFormat == 5)
				_format = FORMAT_COMPRESSED_ALPHA;
			else if (_atfFormat == 3)
				_format = FORMAT_COMPRESSED;

		}

		public function getNativeTexture(context : Context3DResource) : TextureBase
		{
			if ((!_texture || _resize) && _width && _height)
			{
				_resize = false;

				if (_texture)
					_texture.dispose();

				_texture = context.createTexture(
					_width,
					_height,
					_format,
					_bitmapData == null && _atf == null
				);

				_update = true;
			}

			if (_update)
			{
				_update = false;

				uploadTextureWithMipMaps();
			}

			_atf = null;
			_bitmapData = null;

			if (_texture == null)
				throw new Error();

			return _texture;
		}

		private function uploadTextureWithMipMaps() : void
		{
			if (_bitmapData)
			{
				if (_mipmap)
				{
					var level 		: uint 			= 0;
					var size		: uint 			= _width > _height ? _width : _height;
					var transparent	: Boolean		= _bitmapData.transparent;
					var tmp 		: BitmapData 	= new BitmapData(size, size, transparent, 0);
					var transform 	: Matrix		= new Matrix();

					while (size >= 1)
					{
						tmp.draw(_bitmapData, transform, null, null, null, true);
						_texture.uploadFromBitmapData(tmp, level);

						transform.scale(.5, .5);
						level++;
						size >>= 1;

						if (tmp.transparent)
							tmp.fillRect(tmp.rect, 0);
					}

					tmp.dispose();
				}
				else
				{
					_texture.uploadFromBitmapData(_bitmapData, 0);
				}

				_bitmapData.dispose();
			}
			else if (_atf)
			{
				_texture.uploadCompressedTextureFromByteArray(_atf, 0);
			}
		}

		public function dispose() : void
		{
			if (_texture)
			{
				_texture.dispose();
				_texture = null;
			}
		}
	}
}

Fix textureResource to handle properly atf textures