/*
 * Copyright 2008 Adobe Systems Inc., 2008 Google Inc.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *
 * Contributor(s):
 *   Zwetan Kjukov <zwetan@gmail.com>.
 *   Marc Alcaraz <ekameleon@gmail.com>.
 */

package com.google.analytics.v4
{
    import com.google.analytics.campaign.CampaignKey;
    import com.google.analytics.core.Domain;
    import com.google.analytics.core.DomainNameMode;
    import com.google.analytics.core.Organic;
    import com.google.analytics.core.ServerOperationMode;
    import com.google.analytics.debug.DebugConfiguration;
    import com.google.analytics.utils.Timespan;

    /**
     * Google Analytic Tracker Code (GATC)'s configuration / state component.
     * This encapsulates all the configurations for the entire GATC module.
     */
    public class Configuration
    {

        private var _debug:DebugConfiguration;

        private var _version:String = "4.3as";

        /**
         * Sampling percentage of visitors to track.
         * @private
         */
        private var _sampleRate:Number = 1; //100%

        private var _trackingLimitPerSession:int = 500;

        private var _domain:Domain;

        private var _organic:Organic = new Organic();

        /**
         * Substring of host names to ignore when auto decorating href anchor elements for outbound link tracking.
         * @private
         */
        //private var _ignoredOutboundHosts:Array = [];

        /**
         * This is the cse path that needs to be ignored for Google.
         * All referers with path cse from google donmains will be ignored from organic and referrer campaigns.
         * @private
         */
        public  var googleCsePath:String = "cse";

        /**
         * The parameter used by google for the search keyword.
         * @private
         */
        public var googleSearchParam:String = "q";

        /**
         * Google string value.
         * @private
         */
        public var google:String = "google";

        /**
         * Name used by the SharedObject (read-only)
         */
        private var _cookieName:String = "analytics";

        /**
         * Unique domain hash for cookies.
         */
        public var allowDomainHash:Boolean  = true;

        /**
         * Enable use of anchors for campaigns.
         */
        public var allowAnchor:Boolean      = false ;

        /**
         * Enable linker functionality.
         */
        public var allowLinker:Boolean      = false ;

        /**
         * Indicates if has site overlay.
         */
        public var hasSiteOverlay:Boolean   = false ;

        /**
         * The rate of token being released into the token bucket.
         * Unit for this parameter is number of token released per second.
         * This is set to 0.20 right now, which translates to 1 token released every 5 seconds.
         */
        public var tokenRate:Number        = 0.20;

        /**
         * Default cookie expiration time in seconds. (6 months).
         */
        public var conversionTimeout:Number = Timespan.sixmonths;

        /**
         * Default inactive session timeout in seconds (30 minutes).
         */
        public var sessionTimeout:Number = Timespan.thirtyminutes;

        /**
        * Default idle timer loop time in seconds (30 seconds).
        */
        public var idleLoop:Number    = 30;

        /**
        * Default idle timer inactivity time in seconds (1 minute).
        */
        public var idleTimeout:Number = 60;

        /**
         * Upper limit for number of href anchor tags to examine.
         *
If this number is set to -1, then we will examine all the href anchor tags.

         *
In other words, a -1 value indicates that there is no upper limit.

         *
Note: maybe use Number.INFINITY instead of -1

         */
        public var maxOutboundLinkExamined:Number = 1000;

        /**
         * The number of tokens available at the start of the session.
         */
        public var tokenCliff:int = 10;

        /**
         * Capacity of the token bucket.
         */
        public var bucketCapacity:Number = 10;

        /**
         * Detect client browser information flag.
         */
        public var detectClientInfo:Boolean  = true;

        /**
         * Flash version detection option.
         */
        public var detectFlash:Boolean = true;

        /**
         * Set document title detection option.
         */
        public var detectTitle:Boolean = true;

        /**
         * The campaign key value of the application.
         * @see com.google.analytics.campaign.CampaignKey
         */
        public var campaignKey:CampaignKey = new CampaignKey();

        /**
         * Track campaign information flag.
         */
        public var campaignTracking:Boolean = true;

        /**
         * Boolean flag to indicate if outbound links for subdomains of the current domain
         * needs to be considered as outbound links. Default value is false.
         */
        public var isTrackOutboundSubdomains:Boolean = false;

        /**
         * Actual service model.
         *
Note : "service" is wrong we name it server

         */
        public var serverMode:ServerOperationMode = ServerOperationMode.remote;

        /**
         * Local service mode GIF url.
         */
        public var localGIFpath:String = "/__utm.gif";

        /**
         * The remote service mode GIF url.
         */
        public var remoteGIFpath:String = "http://www.google-analytics.com/__utm.gif";

        /**
         * The secure remote service mode GIF url.
         */
        public var secureRemoteGIFpath:String = "https://ssl.google-analytics.com/__utm.gif";

        /**
         * Default cookie path to set in document header.
         */
        public var cookiePath:String = "/" ; //SharedObjectPath

        /**
         * Delimiter for e-commerce transaction fields.
         */
        public var transactionFieldDelim:String = "|";

        /**
         * The domain name value.
         */
        public var domainName:String = "";

        /**
         * To be able to track in local mode (when protocol is file://)
         */
        public var allowLocalTracking:Boolean = true;

        /**
         * Creates a new Configuration instance.
         */
        public function Configuration( debug:DebugConfiguration = null )
        {
            _debug = debug;

            _domain = new Domain( DomainNameMode.auto, "", _debug );
            serverMode = ServerOperationMode.remote;

            _initOrganicSources();
        }

        /**
         * @private
         */
        private function _initOrganicSources():void
        {
            addOrganicSource( google,            googleSearchParam );
            addOrganicSource( "yahoo",          "p"                );
            addOrganicSource( "msn",            "q"                );
            addOrganicSource( "aol",            "query"            );
            addOrganicSource( "aol",            "encquery"         );
            addOrganicSource( "lycos",          "query"            );
            addOrganicSource( "ask",            "q"                );
            addOrganicSource( "altavista",      "q"                );
            addOrganicSource( "netscape",       "query"            );
            addOrganicSource( "cnn",            "query"            );
            addOrganicSource( "looksmart",      "qt"               );
            addOrganicSource( "about",          "terms"            );
            addOrganicSource( "mamma",          "query"            );
            addOrganicSource( "alltheweb",      "q"                );
            addOrganicSource( "gigablast",      "q"                );
            addOrganicSource( "voila",          "rdata"            );
            addOrganicSource( "virgilio",       "qs"               );
            addOrganicSource( "live",           "q"                );
            addOrganicSource( "baidu",          "wd"               );
            addOrganicSource( "alice",          "qs"               );
            addOrganicSource( "yandex",         "text"             );
            addOrganicSource( "najdi",          "q"                );
            addOrganicSource( "aol",            "q"                );
            addOrganicSource( "club-internet",  "q"                );
            addOrganicSource( "mama",           "query"            );
            addOrganicSource( "seznam",         "q"                );
            addOrganicSource( "search",         "q"                );
            addOrganicSource( "wp",             "szukaj"           );
            addOrganicSource( "onet",           "qt"               );
            addOrganicSource( "netsprint",      "q"                );
            addOrganicSource( "google.interia", "q"                );
            addOrganicSource( "szukacz",        "q"                );
            addOrganicSource( "yam",            "k"                );
            addOrganicSource( "pchome",         "q"                );
            addOrganicSource( "kvasir",         "searchExpr"       );
            addOrganicSource( "sesam",          "q"                );
            addOrganicSource( "ozu",            "q"                );
            addOrganicSource( "terra",          "query"            );
            addOrganicSource( "nostrum",        "query"            );
            addOrganicSource( "mynet",          "q"                );
            addOrganicSource( "ekolay",         "q"                );
            addOrganicSource( "search.ilse",    "search_for"       );
        }

        /**
         * Indicates the name of the cookie.
         */
        public function get cookieName():String
        {
            return _cookieName;
        }

        /**
         * Indicates the version String representation of the application.
         */
        public function get version():String
        {
            return _version;
        }

        /**
        * Domain name for cookies.
        * (auto | none | domain)
        * If this variable is set to "auto",
        * then we will try to resolve the domain name
        * based on the HTMLDocument object.
        *
        * note:
        * for Flash we try to auto detect
        * the domain name by using the URL info
        * if we are in HTTP or HTTPS
        *
        * if we can not detect the protocol or find file://
        * then the "auto" domain is none.
        */
        public function get domain():Domain
        {
            return _domain;
        }

        /**
         * Indicates the Organic reference.
         */
        public function get organic():Organic
        {
            return _organic;
        }

//        /**
//         * Indicates the collection (Array) of all organic sources.
//         */
//        public function get organicSources():Array
//        {
//            return _organicSources;
//        }
//
//        /**
//         * @private
//         */
//        public function set organicSources(sources:Array):void
//        {
//            _organicSources = sources;
//        }

        /**
         * Indicates the sample rate value of the application.
         */
        public function get sampleRate():Number
        {
            return _sampleRate;
        }

        /**
         * Sampling percentage of visitors to track.
         */
        public function set sampleRate( value:Number ):void
        {
            if( value <= 0 )
            {
                value = 0.1;
            }

            if( value > 1 )
            {
                value = 1;
            }

            value = Number( value.toFixed( 2 ) );

            _sampleRate = value;
        }

        /**
         * This is the max number of tracking requests to the backend
         * allowed per session.
         */
        public function get trackingLimitPerSession():int
        {
            return _trackingLimitPerSession;
        }

        /**
         * Adds a new organic source.
         * @param engine The engine value.
         * @param keyword The keyword of the specified engine value.
         */
        public function addOrganicSource(engine:String, keyword:String):void
        {
            try
            {
                _organic.addSource( engine, keyword );
            }
            catch( e:Error )
            {
                if( _debug && _debug.active )
                {
                    _debug.warning( e.message );
                }
            }
        }

    }
}

/*
 * Copyright 2008 Adobe Systems Inc., 2008 Google Inc.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *
 * Contributor(s):
 *   Zwetan Kjukov <zwetan@gmail.com>.
 *   Marc Alcaraz <ekameleon@gmail.com>.
 */

package com.google.analytics.v4
{
    import com.google.analytics.campaign.CampaignKey;
    import com.google.analytics.core.Domain;
    import com.google.analytics.core.DomainNameMode;
    import com.google.analytics.core.Organic;
    import com.google.analytics.core.ServerOperationMode;
    import com.google.analytics.debug.DebugConfiguration;
    import com.google.analytics.utils.Timespan;

    /**
     * Google Analytic Tracker Code (GATC)'s configuration / state component.
     * This encapsulates all the configurations for the entire GATC module.
     */
    public class Configuration
    {

        private var _debug:DebugConfiguration;

        private var _version:String = "4.3as";

        /**
         * Sampling percentage of visitors to track.
         * @private
         */
        private var _sampleRate:Number = 1; //100%

        private var _trackingLimitPerSession:int = 500;

        private var _domain:Domain;

        private var _organic:Organic = new Organic();

        /**
         * Substring of host names to ignore when auto decorating href anchor elements for outbound link tracking.
         * @private
         */
        //private var _ignoredOutboundHosts:Array = [];

        /**
         * This is the cse path that needs to be ignored for Google.
         * All referers with path cse from google donmains will be ignored from organic and referrer campaigns.
         * @private
         */
        public  var googleCsePath:String = "cse";

        /**
         * The parameter used by google for the search keyword.
         * @private
         */
        public var googleSearchParam:String = "q";

        /**
         * Google string value.
         * @private
         */
        public var google:String = "google";

        /**
         * Name used by the SharedObject (read-only)
         */
        private var _cookieName:String = "analytics";

        /**
         * Unique domain hash for cookies.
         */
        public var allowDomainHash:Boolean  = true;

        /**
         * Enable use of anchors for campaigns.
         */
        public var allowAnchor:Boolean      = false ;

        /**
         * Enable linker functionality.
         */
        public var allowLinker:Boolean      = false ;

        /**
         * Indicates if has site overlay.
         */
        public var hasSiteOverlay:Boolean   = false ;

        /**
         * The rate of token being released into the token bucket.
         * Unit for this parameter is number of token released per second.
         * This is set to 0.20 right now, which translates to 1 token released every 5 seconds.
         */
        public var tokenRate:Number        = 0.20;

        /**
         * Default cookie expiration time in seconds. (6 months).
         */
        public var conversionTimeout:Number = Timespan.sixmonths;

        /**
         * Default inactive session timeout in seconds (30 minutes).
         */
        public var sessionTimeout:Number = Timespan.thirtyminutes;

        /**
         * Upper limit for number of href anchor tags to examine.
         *
If this number is set to -1, then we will examine all the href anchor tags.

         *
In other words, a -1 value indicates that there is no upper limit.

         *
Note: maybe use Number.INFINITY instead of -1

         */
        public var maxOutboundLinkExamined:Number = 1000;

        /**
         * The number of tokens available at the start of the session.
         */
        public var tokenCliff:int = 10;

        /**
         * Capacity of the token bucket.
         */
        public var bucketCapacity:Number = 10;

        /**
         * Detect client browser information flag.
         */
        public var detectClientInfo:Boolean  = true;

        /**
         * Flash version detection option.
         */
        public var detectFlash:Boolean = true;

        /**
         * Set document title detection option.
         */
        public var detectTitle:Boolean = true;

        /**
         * The campaign key value of the application.
         * @see com.google.analytics.campaign.CampaignKey
         */
        public var campaignKey:CampaignKey = new CampaignKey();

        /**
         * Track campaign information flag.
         */
        public var campaignTracking:Boolean = true;

        /**
         * Boolean flag to indicate if outbound links for subdomains of the current domain
         * needs to be considered as outbound links. Default value is false.
         */
        public var isTrackOutboundSubdomains:Boolean = false;

        /**
         * Actual service model.
         *
Note : "service" is wrong we name it server

         */
        public var serverMode:ServerOperationMode = ServerOperationMode.remote;

        /**
         * Local service mode GIF url.
         */
        public var localGIFpath:String = "/__utm.gif";

        /**
         * The remote service mode GIF url.
         */
        public var remoteGIFpath:String = "http://www.google-analytics.com/__utm.gif";

        /**
         * The secure remote service mode GIF url.
         */
        public var secureRemoteGIFpath:String = "https://ssl.google-analytics.com/__utm.gif";

        /**
         * Default cookie path to set in document header.
         */
        public var cookiePath:String = "/" ; //SharedObjectPath

        /**
         * Delimiter for e-commerce transaction fields.
         */
        public var transactionFieldDelim:String = "|";

        /**
         * The domain name value.
         */
        public var domainName:String = "";

        /**
         * To be able to track in local mode (when protocol is file://)
         */
        public var allowLocalTracking:Boolean = true;

        /**
         * Creates a new Configuration instance.
         */
        public function Configuration( debug:DebugConfiguration = null )
        {
            _debug = debug;

            _domain = new Domain( DomainNameMode.auto, "", _debug );
            serverMode = ServerOperationMode.remote;

            _initOrganicSources();
        }

        /**
         * @private
         */
        private function _initOrganicSources():void
        {
            addOrganicSource( google,            googleSearchParam );
            addOrganicSource( "yahoo",          "p"                );
            addOrganicSource( "msn",            "q"                );
            addOrganicSource( "aol",            "query"            );
            addOrganicSource( "aol",            "encquery"         );
            addOrganicSource( "lycos",          "query"            );
            addOrganicSource( "ask",            "q"                );
            addOrganicSource( "altavista",      "q"                );
            addOrganicSource( "netscape",       "query"            );
            addOrganicSource( "cnn",            "query"            );
            addOrganicSource( "looksmart",      "qt"               );
            addOrganicSource( "about",          "terms"            );
            addOrganicSource( "mamma",          "query"            );
            addOrganicSource( "alltheweb",      "q"                );
            addOrganicSource( "gigablast",      "q"                );
            addOrganicSource( "voila",          "rdata"            );
            addOrganicSource( "virgilio",       "qs"               );
            addOrganicSource( "live",           "q"                );
            addOrganicSource( "baidu",          "wd"               );
            addOrganicSource( "alice",          "qs"               );
            addOrganicSource( "yandex",         "text"             );
            addOrganicSource( "najdi",          "q"                );
            addOrganicSource( "aol",            "q"                );
            addOrganicSource( "club-internet",  "q"                );
            addOrganicSource( "mama",           "query"            );
            addOrganicSource( "seznam",         "q"                );
            addOrganicSource( "search",         "q"                );
            addOrganicSource( "wp",             "szukaj"           );
            addOrganicSource( "onet",           "qt"               );
            addOrganicSource( "netsprint",      "q"                );
            addOrganicSource( "google.interia", "q"                );
            addOrganicSource( "szukacz",        "q"                );
            addOrganicSource( "yam",            "k"                );
            addOrganicSource( "pchome",         "q"                );
            addOrganicSource( "kvasir",         "searchExpr"       );
            addOrganicSource( "sesam",          "q"                );
            addOrganicSource( "ozu",            "q"                );
            addOrganicSource( "terra",          "query"            );
            addOrganicSource( "nostrum",        "query"            );
            addOrganicSource( "mynet",          "q"                );
            addOrganicSource( "ekolay",         "q"                );
            addOrganicSource( "search.ilse",    "search_for"       );
        }

        /**
         * Indicates the name of the cookie.
         */
        public function get cookieName():String
        {
            return _cookieName;
        }

        /**
         * Indicates the version String representation of the application.
         */
        public function get version():String
        {
            return _version;
        }

        /**
        * Domain name for cookies.
        * (auto | none | domain)
        * If this variable is set to "auto",
        * then we will try to resolve the domain name
        * based on the HTMLDocument object.
        *
        * note:
        * for Flash we try to auto detect
        * the domain name by using the URL info
        * if we are in HTTP or HTTPS
        *
        * if we can not detect the protocol or find file://
        * then the "auto" domain is none.
        */
        public function get domain():Domain
        {
            return _domain;
        }

        /**
         * Indicates the Organic reference.
         */
        public function get organic():Organic
        {
            return _organic;
        }

//        /**
//         * Indicates the collection (Array) of all organic sources.
//         */
//        public function get organicSources():Array
//        {
//            return _organicSources;
//        }
//
//        /**
//         * @private
//         */
//        public function set organicSources(sources:Array):void
//        {
//            _organicSources = sources;
//        }

        /**
         * Indicates the sample rate value of the application.
         */
        public function get sampleRate():Number
        {
            return _sampleRate;
        }

        /**
         * Sampling percentage of visitors to track.
         */
        public function set sampleRate( value:Number ):void
        {
            if( value <= 0 )
            {
                value = 0.1;
            }

            if( value > 1 )
            {
                value = 1;
            }

            value = Number( value.toFixed( 2 ) );

            _sampleRate = value;
        }

        /**
         * This is the max number of tracking requests to the backend
         * allowed per session.
         */
        public function get trackingLimitPerSession():int
        {
            return _trackingLimitPerSession;
        }

        /**
         * Adds a new organic source.
         * @param engine The engine value.
         * @param keyword The keyword of the specified engine value.
         */
        public function addOrganicSource(engine:String, keyword:String):void
        {
            try
            {
                _organic.addSource( engine, keyword );
            }
            catch( e:Error )
            {
                if( _debug && _debug.active )
                {
                    _debug.warning( e.message );
                }
            }
        }

    }
}

remove params related to idle timer

remove params related to idle timer


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

import com.esri.builder.controllers.supportClasses.WellKnownDirectories;
import com.esri.builder.eventbus.AppEvent;
import com.esri.builder.model.Model;
import com.esri.builder.model.WidgetType;
import com.esri.builder.supportClasses.LogUtil;

import flash.filesystem.File;

import mx.logging.ILogger;
import mx.logging.Log;

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

    private var addedWidgetType:WidgetType;

    public function WidgetSyncController()
    {
        AppEvent.addListener(AppEvent.WIDGET_ADDED_TO_APP, widgetAddedToAppHandler);
    }

    private function widgetAddedToAppHandler(event:AppEvent):void
    {
        addedWidgetType = event.data as WidgetType;
        copyMissingFilesToApp(getWidgetDirectory());
    }

    private function getWidgetDirectory():File
    {
        var widgetDirectory:File;
        if (addedWidgetType.isCustom)
        {
            widgetDirectory = WellKnownDirectories.getInstance().customFlexViewer.resolvePath("widgets/" + addedWidgetType.name);
        }
        else
        {
            widgetDirectory = WellKnownDirectories.getInstance().bundledFlexViewer.resolvePath("widgets/" + addedWidgetType.name);
        }
        return widgetDirectory;
    }

    private function copyMissingFilesToApp(widgetDirectory:File):void
    {
        if (!widgetDirectory.exists)
        {
            return;
        }

        if (Log.isInfo())
        {
            LOG.info("Copying missing widget files.");
        }

        var directoryContents:Array = widgetDirectory.getDirectoryListing();
        for each (var fileOrFolder:File in directoryContents)
        {
            if (fileOrFolder.isDirectory)
            {
                copyMissingFilesToApp(fileOrFolder);
            }
            else
            {
                copySafelyToApp(fileOrFolder);
            }
        }
    }

    private function copySafelyToApp(fileOrFolder:File):void
    {
        var relativePathToWidgetDirectory:String = getRelativePathToWidgetDirectory(fileOrFolder);
        var fileDestination:File = Model.instance.appDir.resolvePath(relativePathToWidgetDirectory);
        //only copying missing files
        if (!fileDestination.exists)
        {
            if (Log.isDebug())
            {
                LOG.debug("Copying {0} to {1}", fileOrFolder.nativePath, fileDestination.nativePath);
            }

            try
            {
                fileOrFolder.copyTo(fileDestination);
            }
            catch (error:Error)
            {
                if (Log.isWarn())
                {
                    LOG.warn("Could not copy {0} to {1}: {2}", fileOrFolder.nativePath, fileDestination.nativePath, error.toString());
                }
                    //fail silently
            }
        }
    }

    private function getRelativePathToWidgetDirectory(fileOrFolder:File):String
    {
        if (addedWidgetType.isCustom)
        {
            return WellKnownDirectories.getInstance().customFlexViewer.getRelativePath(fileOrFolder);
        }
        else
        {
            return WellKnownDirectories.getInstance().bundledFlexViewer.getRelativePath(fileOrFolder);
        }
    }
}
}

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

import com.esri.builder.controllers.supportClasses.WellKnownDirectories;
import com.esri.builder.eventbus.AppEvent;
import com.esri.builder.model.Model;
import com.esri.builder.model.WidgetType;
import com.esri.builder.supportClasses.LogUtil;

import flash.filesystem.File;

import mx.logging.ILogger;
import mx.logging.Log;

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

    private var addedWidgetType:WidgetType;

    public function WidgetSyncController()
    {
        AppEvent.addListener(AppEvent.WIDGET_ADDED_TO_APP, widgetAddedToAppHandler);
    }

    private function widgetAddedToAppHandler(event:AppEvent):void
    {
        addedWidgetType = event.data as WidgetType;
        copyMissingFilesToApp(getWidgetDirectory());
    }

    private function getWidgetDirectory():File
    {
        return addedWidgetType.isCustom ?
            WellKnownDirectories.getInstance().customFlexViewer.resolvePath("widgets/" + addedWidgetType.name) :
            WellKnownDirectories.getInstance().bundledFlexViewer.resolvePath("widgets/" + addedWidgetType.name);
    }

    private function copyMissingFilesToApp(widgetDirectory:File):void
    {
        if (!widgetDirectory.exists)
        {
            return;
        }

        if (Log.isInfo())
        {
            LOG.info("Copying missing widget files.");
        }

        var directoryContents:Array = widgetDirectory.getDirectoryListing();
        for each (var fileOrFolder:File in directoryContents)
        {
            if (fileOrFolder.isDirectory)
            {
                copyMissingFilesToApp(fileOrFolder);
            }
            else
            {
                copySafelyToApp(fileOrFolder);
            }
        }
    }

    private function copySafelyToApp(fileOrFolder:File):void
    {
        var relativePathToWidgetDirectory:String = getRelativePathToWidgetDirectory(fileOrFolder);
        var fileDestination:File = Model.instance.appDir.resolvePath(relativePathToWidgetDirectory);
        //only copying missing files
        if (!fileDestination.exists)
        {
            if (Log.isDebug())
            {
                LOG.debug("Copying {0} to {1}", fileOrFolder.nativePath, fileDestination.nativePath);
            }

            try
            {
                fileOrFolder.copyTo(fileDestination);
            }
            catch (error:Error)
            {
                if (Log.isWarn())
                {
                    LOG.warn("Could not copy {0} to {1}: {2}", fileOrFolder.nativePath, fileDestination.nativePath, error.toString());
                }
                    //fail silently
            }
        }
    }

    private function getRelativePathToWidgetDirectory(fileOrFolder:File):String
    {
        return addedWidgetType.isCustom ?
            WellKnownDirectories.getInstance().customFlexViewer.getRelativePath(fileOrFolder) :
            WellKnownDirectories.getInstance().bundledFlexViewer.getRelativePath(fileOrFolder);
    }
}
}

Simplify WidgetSyncController logic.

Simplify WidgetSyncController logic.


package com.benoitfreslon.layoutmanager {

	import flash.display.MovieClip;
	import flash.events.Event;
	import flash.utils.getDefinitionByName;
	import starling.display.Button;
	import starling.display.DisplayObject;
	import starling.display.DisplayObjectContainer;
	import starling.display.Image;
	import starling.display.Sprite;
	import starling.text.TextField;
	import starling.utils.AssetManager;

	/**
	 * Loader of Layout
	 * @version 1.01
	 * @author Benoît Freslon
	 */
	public class LayoutLoader {
		private var _movieclip : MovieClip;
		private var _displayObject : DisplayObjectContainer;
		private var _assetManager : AssetManager;
		static public var debug:Boolean = false;
		private var onLoad : Function = function() : void {
		};

		public function LayoutLoader() {

		}

		public function loadLayout( displayObject : DisplayObjectContainer, LayoutClass : Class, assetManager : AssetManager, callBack : Function = null ) : void {
			_displayObject = displayObject;
			_assetManager = assetManager;
			_movieclip = new LayoutClass();
			_movieclip.addEventListener( Event.ENTER_FRAME, layoutLoaded );
			if ( onLoad != null )
				onLoad = callBack
		}

		private function layoutLoaded( e : Event ) : void {
			_movieclip.removeEventListener( Event.ENTER_FRAME, layoutLoaded );
			var child : BFObject;
			var n : int = _movieclip.numChildren;

			for ( var i : uint = 0; i < n; ++i ) {
				child = _movieclip.getChildAt( i ) as BFObject;

				if ( child && child is BFObject ) {
					if ( child.mainClass ) {

						var objectClass : Class
						if ( child.className ) {
							objectClass = getDefinitionByName( child.className ) as Class;
						} else {
							objectClass = getDefinitionByName( child.mainClass ) as Class;
						}

						var obj : DisplayObject;
						if ( child.mainClass == "starling.display.Image" ) {
							obj = addImage( objectClass, child as BFImage );
						} else if ( child.mainClass == "starling.display.ButtonExtended" ) {
							obj = addButton( objectClass, child as BFButton );
						} else if ( child.mainClass == "starling.text.TextField" ) {
							obj = addTextField( objectClass, child as BFTextField );
						} else if ( child.mainClass == "starling.display.sprite" ) {
							obj = addSprite( objectClass, child as BFSprite );
						} else {
							trace( new Error( "No mainClass defined " + child ) );
						}

						if ( child.hasOwnProperty( "params" ) ) {
							for ( var metatags : String in child.params ) {
								if ( obj.hasOwnProperty( metatags ) )
									obj[ metatags ] = child.params[ metatags ];
							}
						}
						obj.name = child.name;

						obj.pivotX = obj.width / 2;
						obj.pivotY = obj.height / 2;
						obj.x = child.x;
						obj.y = child.y;
						//obj.scaleX = child.scaleX;
						//obj.scaleY = child.scaleY;
						obj.alpha = child.alpha;
						obj.rotation = child.rotation;
						obj.visible = child.isVisible;
						_displayObject.addChild( obj );

						if ( obj.hasOwnProperty( "tag" ) ) {
							obj[ "tag" ] = child.tag;
						}

						if ( _displayObject.hasOwnProperty( child.name ) ) {
							_displayObject[ child.name ] = obj as objectClass;
						}
					} else {
						trace( new Error( "No className defined " + child ) );
					}
				}
			}
			loaded();
		}

		private function loaded() : void {
			_assetManager = null;
			_displayObject = null;
			_movieclip = null;
			onLoad();
		}

		private function addImage( objectClass : Class, child : BFImage ) : Image {
			var img : Image = new objectClass( _assetManager.getTexture( child.texture ) ) as Image;
			//img.pivotX = img.width / 2;
			//img.pivotY = img.height / 2;
			return img;
		}

		private function addSprite( objectClass : Class, child : BFSprite ) : Sprite {
			var s : Sprite = new objectClass() as Sprite;

			return s;
		}

		private function addTextField( objectClass : Class, child : BFTextField ) : TextField {
			var t : TextField = new objectClass( child.width, child.height, "" ) as TextField;
			t.fontName = child.fontName;
			t.fontSize = child.fontSize;
			t.text = child.text;
			t.color = child.color;
			t.hAlign = child.hAlign;
			t.vAlign = child.vAlign;
			t.bold = child.bold;
			t.italic = child.italic;
			t.underline = child.underline;
			//t.pivotX 		= t.width / 2;
			//t.pivotY		= t.height / 2;
			return t;
		}

		private function addButton( objectClass : Class, child : BFButton ) : Button {
			var bt : Button = new objectClass( _assetManager.getTexture( child.upState ) ) as Button;
			bt.fontBold = child.fontBold;
			bt.fontColor = child.fontColor;
			bt.fontName = child.fontName;
			bt.fontSize = child.fontSize;
			bt.text = child.text;
			//bt.pivotX 		= bt.width / 2;
			if ( child.downState )
				bt.downState = _assetManager.getTexture( child.downState );
			trace(child.onTouch, bt.hasOwnProperty( "onTouch" ), _displayObject.hasOwnProperty( child.onTouch ));
			if ( bt.hasOwnProperty( "onTouch" ) && _displayObject.hasOwnProperty( child.onTouch ) ) {
				trace(bt[ "onTouch" ], child.onTouch)
				bt[ "onTouch" ] = _displayObject[ child.onTouch ];
			} else if (bt.hasOwnProperty( "onTouch" )) {
				trace( new Error("The public method " + child.onTouch + " is not defined in " + _displayObject));
			}
			return bt;
		}

	}

}

package com.benoitfreslon.layoutmanager {

	import flash.display.MovieClip;
	import flash.events.Event;
	import flash.utils.getDefinitionByName;
	import starling.display.Button;
	import starling.display.DisplayObject;
	import starling.display.DisplayObjectContainer;
	import starling.display.Image;
	import starling.display.Sprite;
	import starling.text.TextField;
	import starling.utils.AssetManager;

	/**
	 * Loader of Layout
	 * @version 1.01
	 * @author Benoît Freslon
	 */
	public class LayoutLoader {
		private var _movieclip : MovieClip;
		private var _displayObject : DisplayObjectContainer;
		private var _assetManager : AssetManager;
		static public var debug:Boolean = false;
		private var onLoad : Function = function() : void {
		};

		public function LayoutLoader() {

		}

		public function loadLayout( displayObject : DisplayObjectContainer, LayoutClass : Class, assetManager : AssetManager, callBack : Function = null ) : void {
			_displayObject = displayObject;
			_assetManager = assetManager;
			_movieclip = new LayoutClass();
			_movieclip.addEventListener( Event.ENTER_FRAME, layoutLoaded );
			if ( onLoad != null )
				onLoad = callBack
		}

		private function layoutLoaded( e : Event ) : void {
			_movieclip.removeEventListener( Event.ENTER_FRAME, layoutLoaded );
			var child : BFObject;
			var n : int = _movieclip.numChildren;

			for ( var i : uint = 0; i < n; ++i ) {
				child = _movieclip.getChildAt( i ) as BFObject;

				if ( child && child is BFObject ) {
					if ( child.mainClass ) {

						var objectClass : Class
						if ( child.className ) {
							objectClass = getDefinitionByName( child.className ) as Class;
						} else {
							objectClass = getDefinitionByName( child.mainClass ) as Class;
						}

						var obj : DisplayObject;
						if ( child.mainClass == "starling.display.Image" ) {
							obj = addImage( objectClass, child as BFImage );
						} else if ( child.mainClass == "starling.display.ButtonExtended" ) {
							obj = addButton( objectClass, child as BFButton );
						} else if ( child.mainClass == "starling.text.TextField" ) {
							obj = addTextField( objectClass, child as BFTextField );
						} else if ( child.mainClass == "starling.display.sprite" ) {
							obj = addSprite( objectClass, child as BFSprite );
						} else {
							trace( new Error( "No mainClass defined " + child ) );
						}

						if ( child.hasOwnProperty( "params" ) ) {
							for ( var metatags : String in child.params ) {
								if ( obj.hasOwnProperty( metatags ) )
									obj[ metatags ] = child.params[ metatags ];
							}
						}
						obj.name = child.name;

						obj.pivotX = obj.width / 2;
						obj.pivotY = obj.height / 2;
						obj.x = child.x;
						obj.y = child.y;
						//obj.scaleX = child.scaleX;
						//obj.scaleY = child.scaleY;
						obj.alpha = child.alpha;
						obj.rotation = child.rotation;
						obj.visible = child.isVisible;
						_displayObject.addChild( obj );

						if ( obj.hasOwnProperty( "tag" ) ) {
							obj[ "tag" ] = child.tag;
						}

						if ( _displayObject.hasOwnProperty( child.name ) ) {
							_displayObject[ child.name ] = obj as objectClass;
						}
					} else {
						trace( new Error( "No className defined " + child ) );
					}
				}
			}
			loaded();
		}

		private function loaded() : void {
			_assetManager = null;
			_displayObject = null;
			_movieclip = null;
			onLoad();
		}

		private function addImage( objectClass : Class, child : BFImage ) : Image {
			var img : Image = new objectClass( _assetManager.getTexture( child.texture ) ) as Image;
			//img.pivotX = img.width / 2;
			//img.pivotY = img.height / 2;
			return img;
		}

		private function addSprite( objectClass : Class, child : BFSprite ) : Sprite {
			var s : Sprite = new objectClass() as Sprite;

			return s;
		}

		private function addTextField( objectClass : Class, child : BFTextField ) : TextField {
			var t : TextField = new objectClass( child.width, child.height, "" ) as TextField;
			t.fontName = child.fontName;
			t.fontSize = child.fontSize;
			t.text = child.text;
			t.color = child.color;
			t.hAlign = child.hAlign;
			t.vAlign = child.vAlign;
			t.bold = child.bold;
			t.italic = child.italic;
			t.underline = child.underline;
			//t.pivotX 		= t.width / 2;
			//t.pivotY		= t.height / 2;
			return t;
		}

		private function addButton( objectClass : Class, child : BFButton ) : Button {
			var bt : Button = new objectClass( _assetManager.getTexture( child.upState ) ) as Button;
			bt.fontBold = child.fontBold;
			bt.fontColor = child.fontColor;
			bt.fontName = child.fontName;
			bt.fontSize = child.fontSize;
			bt.text = child.text;
			//bt.pivotX 		= bt.width / 2;
			if ( child.downState )
				bt.downState = _assetManager.getTexture( child.downState );
			if ( bt.hasOwnProperty( "onTouch" ) && _displayObject.hasOwnProperty( child.onTouch ) ) {
				bt[ "onTouch" ] = _displayObject[ child.onTouch ];
			} else if (bt.hasOwnProperty( "onTouch" )) {
				trace( new Error("The public method " + child.onTouch + " is not defined in " + _displayObject));
			}
			return bt;
		}

	}

}

Remove trace

Remove trace


package dolly {
import dolly.core.dolly_internal;
import dolly.data.ClassLevelCopyable;
import dolly.data.PropertyLevelCopyable;

import org.as3commons.reflect.Field;
import org.as3commons.reflect.Type;
import org.flexunit.asserts.assertEquals;
import org.flexunit.asserts.assertNotNull;
import org.flexunit.asserts.assertNull;

use namespace dolly_internal;

public class CopierTest {

	private var classLevelCopyable:ClassLevelCopyable;
	private var classLevelCopyableType:Type;

	private var propertyLevelCopyable:PropertyLevelCopyable;
	private var propertyLevelCopyableType:Type;

	[Before]
	public function before():void {
		classLevelCopyable = new ClassLevelCopyable();
		classLevelCopyable.property1 = "value 1";
		classLevelCopyable.property2 = "value 2";
		classLevelCopyable.property3 = "value 3";
		classLevelCopyable.writableField = "value 4";

		propertyLevelCopyable = new PropertyLevelCopyable();
		propertyLevelCopyable.property1 = "value 1";
		propertyLevelCopyable.property2 = "value 2";
		propertyLevelCopyable.property3 = "value 3";
		propertyLevelCopyable.writableField = "value 4";

		classLevelCopyableType = Type.forInstance(classLevelCopyable);
		propertyLevelCopyableType = Type.forInstance(propertyLevelCopyable);
	}

	[After]
	public function after():void {
		classLevelCopyable = null;
		classLevelCopyableType = null;

		propertyLevelCopyable = null;
		propertyLevelCopyableType = null;
	}

	[Test]
	public function testGetCopyableFieldsForType():void {
		var copyableFields:Vector.<Field> = Copier.getCopyableFieldsForType(classLevelCopyableType);

		assertNotNull(copyableFields);
		assertEquals(4, copyableFields.length);
		assertNotNull(copyableFields[0]);
		assertNotNull(copyableFields[1]);
		assertNotNull(copyableFields[2]);
		assertNotNull(copyableFields[3]);

		copyableFields = Copier.getCopyableFieldsForType(propertyLevelCopyableType);

		assertNotNull(copyableFields);
		assertEquals(3, copyableFields.length);
		assertNotNull(copyableFields[0]);
		assertNotNull(copyableFields[1]);
		assertNotNull(copyableFields[2]);
	}

	[Test]
	public function testCopyWithClassLevelMetadata():void {
		const copy:ClassLevelCopyable = Copier.copy(classLevelCopyable);

		assertNotNull(copy);
		assertNotNull(copy.property1);
		assertEquals(copy.property1, classLevelCopyable.property1);
		assertNotNull(copy.property2);
		assertEquals(copy.property2, classLevelCopyable.property2);
		assertNotNull(copy.property3);
		assertEquals(copy.property3, classLevelCopyable.property3);
		assertNotNull(copy.writableField);
		assertEquals(copy.writableField, classLevelCopyable.writableField);
	}

	[Test]
	public function testCopyWithPropertyLevelMetadata():void {
		const copy:PropertyLevelCopyable = Copier.copy(propertyLevelCopyable);

		assertNotNull(copy);
		assertNull(copy.property1);
		assertNotNull(copy.property2);
		assertEquals(copy.property2, classLevelCopyable.property2);
		assertNotNull(copy.property3);
		assertEquals(copy.property3, classLevelCopyable.property3);
		assertNotNull(copy.writableField);
		assertEquals(copy.writableField, classLevelCopyable.writableField);
	}
}
}

package dolly {
import dolly.core.dolly_internal;
import dolly.data.ClassLevelCopyable;
import dolly.data.ClassLevelCopyableCloneable;
import dolly.data.PropertyLevelCopyable;
import dolly.data.PropertyLevelCopyableCloneable;

import org.as3commons.reflect.Field;
import org.as3commons.reflect.Type;
import org.flexunit.asserts.assertEquals;
import org.flexunit.asserts.assertNotNull;
import org.flexunit.asserts.assertNull;

use namespace dolly_internal;

public class CopierTest {

	private var classLevelCopyable:ClassLevelCopyable;
	private var classLevelCopyableType:Type;

	private var classLevelCopyableCloneable:ClassLevelCopyableCloneable;
	private var classLevelCopyableCloneableType:Type;

	private var propertyLevelCopyable:PropertyLevelCopyable;
	private var propertyLevelCopyableType:Type;

	private var propertyLevelCopyableCloneable:PropertyLevelCopyableCloneable;
	private var propertyLevelCopyableCloneableType:Type;

	[Before]
	public function before():void {
		classLevelCopyable = new ClassLevelCopyable();
		classLevelCopyable.property1 = "value 1";
		classLevelCopyable.property2 = "value 2";
		classLevelCopyable.property3 = "value 3";
		classLevelCopyable.writableField = "value 4";
		classLevelCopyableType = Type.forInstance(classLevelCopyable);

		classLevelCopyableCloneable = new ClassLevelCopyableCloneable();
		classLevelCopyableCloneable.property1 = "value 1";
		classLevelCopyableCloneable.property2 = "value 2";
		classLevelCopyableCloneable.property3 = "value 3";
		classLevelCopyableCloneable.writableField = "value 4";
		classLevelCopyableCloneableType = Type.forInstance(classLevelCopyableCloneable);

		propertyLevelCopyable = new PropertyLevelCopyable();
		propertyLevelCopyable.property1 = "value 1";
		propertyLevelCopyable.property2 = "value 2";
		propertyLevelCopyable.property3 = "value 3";
		propertyLevelCopyable.writableField = "value 4";
		propertyLevelCopyableType = Type.forInstance(propertyLevelCopyable);

		propertyLevelCopyableCloneable = new PropertyLevelCopyableCloneable();
		propertyLevelCopyableCloneable.property1 = "value 1";
		propertyLevelCopyableCloneable.property2 = "value 2";
		propertyLevelCopyableCloneable.property3 = "value 3";
		propertyLevelCopyableCloneable.writableField = "value 4";
		propertyLevelCopyableCloneableType = Type.forInstance(propertyLevelCopyableCloneable);
	}

	[After]
	public function after():void {
		classLevelCopyable = null;
		classLevelCopyableType = null;

		classLevelCopyableCloneable = null;
		classLevelCopyableCloneableType = null;

		propertyLevelCopyable = null;
		propertyLevelCopyableType = null;

		propertyLevelCopyableCloneable = null;
		propertyLevelCopyableCloneableType = null;
	}

	[Test]
	public function testGetCopyableFieldsForType():void {
		var copyableFields:Vector.<Field> = Copier.getCopyableFieldsForType(classLevelCopyableType);
		assertNotNull(copyableFields);
		assertEquals(4, copyableFields.length);
		assertNotNull(copyableFields[0]);
		assertNotNull(copyableFields[1]);
		assertNotNull(copyableFields[2]);
		assertNotNull(copyableFields[3]);

		copyableFields = Copier.getCopyableFieldsForType(classLevelCopyableCloneableType);
		assertNotNull(copyableFields);
		assertEquals(4, copyableFields.length);
		assertNotNull(copyableFields[0]);
		assertNotNull(copyableFields[1]);
		assertNotNull(copyableFields[2]);
		assertNotNull(copyableFields[3]);

		copyableFields = Copier.getCopyableFieldsForType(propertyLevelCopyableType);
		assertNotNull(copyableFields);
		assertEquals(3, copyableFields.length);
		assertNotNull(copyableFields[0]);
		assertNotNull(copyableFields[1]);
		assertNotNull(copyableFields[2]);

		copyableFields = Copier.getCopyableFieldsForType(propertyLevelCopyableCloneableType);
		assertNotNull(copyableFields);
		assertEquals(3, copyableFields.length);
		assertNotNull(copyableFields[0]);
		assertNotNull(copyableFields[1]);
		assertNotNull(copyableFields[2]);
	}

	[Test]
	public function testCopyWithClassLevelMetadata():void {
		const copy1:ClassLevelCopyable = Copier.copy(classLevelCopyable);
		assertNotNull(copy1);
		assertNotNull(copy1.property1);
		assertEquals(copy1.property1, classLevelCopyable.property1);
		assertNotNull(copy1.property2);
		assertEquals(copy1.property2, classLevelCopyable.property2);
		assertNotNull(copy1.property3);
		assertEquals(copy1.property3, classLevelCopyable.property3);
		assertNotNull(copy1.writableField);
		assertEquals(copy1.writableField, classLevelCopyable.writableField);

		const copy2:ClassLevelCopyableCloneable = Copier.copy(classLevelCopyableCloneable);
		assertNotNull(copy2);
		assertNotNull(copy2.property1);
		assertEquals(copy2.property1, classLevelCopyable.property1);
		assertNotNull(copy2.property2);
		assertEquals(copy2.property2, classLevelCopyable.property2);
		assertNotNull(copy2.property3);
		assertEquals(copy2.property3, classLevelCopyable.property3);
		assertNotNull(copy2.writableField);
		assertEquals(copy2.writableField, classLevelCopyable.writableField);
	}

	[Test]
	public function testCopyWithPropertyLevelMetadata():void {
		const copy1:PropertyLevelCopyable = Copier.copy(propertyLevelCopyable);
		assertNotNull(copy1);
		assertNull(copy1.property1);
		assertNotNull(copy1.property2);
		assertEquals(copy1.property2, classLevelCopyable.property2);
		assertNotNull(copy1.property3);
		assertEquals(copy1.property3, classLevelCopyable.property3);
		assertNotNull(copy1.writableField);
		assertEquals(copy1.writableField, classLevelCopyable.writableField);

		const copy2:PropertyLevelCopyableCloneable = Copier.copy(propertyLevelCopyableCloneable);
		assertNotNull(copy2);
		assertNull(copy2.property1);
		assertNotNull(copy2.property2);
		assertEquals(copy2.property2, classLevelCopyable.property2);
		assertNotNull(copy2.property3);
		assertEquals(copy2.property3, classLevelCopyable.property3);
		assertNotNull(copy2.writableField);
		assertEquals(copy2.writableField, classLevelCopyable.writableField);
	}
}
}

Test classes ClassLevelCopyableCloneable and PropertyLevelCopyableCloneable in CopierTest.


//
// Flump - Copyright 2012 Three Rings Design

package flump.export {

import flash.desktop.NativeApplication;
import flash.display.NativeWindow;
import flash.display.Stage;
import flash.display.StageQuality;
import flash.events.Event;
import flash.events.MouseEvent;
import flash.filesystem.File;
import flash.net.SharedObject;

import com.adobe.crypto.MD5;

import deng.fzip.FZip;
import deng.fzip.FZipFile;

import flump.bytesToXML;
import flump.display.Movie;
import flump.executor.Executor;
import flump.executor.Future;
import flump.export.Ternary;
import flump.xfl.ParseError;
import flump.xfl.XflLibrary;
import flump.xfl.XflMovie;

import mx.collections.ArrayCollection;

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

import starling.display.Sprite;

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

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

    protected static const IMPORT_ROOT :String = "IMPORT_ROOT";
    protected static const AUTHORED_RESOLUTION :String = "AUTHORED_RESOLUTION";

    public function Exporter (win :ExporterWindow) {
        Log.setLevel("", Log.INFO);
        _win = win;
        _errors = _win.errors;
        _libraries = _win.libraries;

        _authoredResolution = _win.authoredResolutionPopup;
        _authoredResolution.dataProvider = new ArrayCollection(DeviceType.values().map(
            function (type :DeviceType, ..._) :Object {
                return new DeviceSelection(type);
            }));
        var initialSelection :DeviceType = null;
        if (_settings.data.hasOwnProperty(AUTHORED_RESOLUTION)) {
            try {
                initialSelection = DeviceType.valueOf(_settings.data[AUTHORED_RESOLUTION]);
            } catch (e :Error) {}
        }
        if (initialSelection == null) initialSelection = DeviceType.IPHONE_RETINA;
        _authoredResolution.selectedIndex = DeviceType.values().indexOf(initialSelection);
        _authoredResolution.addEventListener(Event.CHANGE, function (..._) :void {
            var selectedType :DeviceType = DeviceSelection(_authoredResolution.selectedItem).type;
            _settings.data[AUTHORED_RESOLUTION] = selectedType.name();
        });

        function updateExportEnabled (..._) :void {
            _win.export.enabled = _exportChooser.dir != null && _libraries.selectionLength > 0 &&
              _libraries.selectedItems.some(function (status :DocStatus, ..._) :Boolean {
                return status.isValid;
            });
        }
        _libraries.addEventListener(GridSelectionEvent.SELECTION_CHANGE, function (..._) :void {
            log.info("Changed", "selected", _libraries.selectedIndices);
            updateExportEnabled();
            _win.preview.enabled = _libraries.selectedItem.isValid;
        });
        _win.reload.addEventListener(MouseEvent.CLICK, function (..._) :void {
            setImport(_importChooser.dir);
        });
        _win.export.addEventListener(MouseEvent.CLICK, function (..._) :void {
            for each (var status :DocStatus in _libraries.selectedItems) {
                exportFlashDocument(status);
            }
        });
        _win.preview.addEventListener(MouseEvent.CLICK, function (..._) :void {
            showPreviewWindow(_libraries.selectedItem.lib);
        });
        _importChooser =
            new DirChooser(_settings, "IMPORT_ROOT", _win.importRoot, _win.browseImport);
        _importChooser.changed.add(setImport);
        setImport(_importChooser.dir);
        _exportChooser =
            new DirChooser(_settings, "EXPORT_ROOT", _win.exportRoot, _win.browseExport);
        _exportChooser.changed.add(updateExportEnabled);
        function updatePublisher (..._) :void {
            if (_exportChooser.dir == null) _publisher = null;
            else {
                _publisher =
                    new Publisher(_exportChooser.dir, new XMLFormat(), new JSONFormat(), new StarlingFormat());
            }
        };
        _exportChooser.changed.add(updatePublisher);
        updatePublisher();
        _win.addEventListener(Event.CLOSE, function (..._) :void { NA.exit(0); });
    }

    protected function setImport (root :File) :void {
        _libraries.dataProvider.removeAll();
        _errors.dataProvider.removeAll();
        if (root == null) return;
        _rootLen = root.nativePath.length + 1;
        if (_docFinder != null) _docFinder.shutdownNow();
        _docFinder = new Executor(1);
        findFlashDocuments(root, _docFinder, true);
        _win.reload.enabled = true;
    }

    protected function showPreviewWindow (lib :XflLibrary) :void {
        if (_previewController == null || _previewWindow.closed || _previewControls.closed) {
            _previewWindow = new PreviewWindow();
            _previewControls = new PreviewControlsWindow();
            _previewWindow.started = function (container :Sprite) :void {
                _previewController = new PreviewController(lib, container, _previewWindow,
                    _previewControls);
            }

            _previewWindow.open();
            _previewControls.open();

            preventWindowClose(_previewWindow.nativeWindow);
            preventWindowClose(_previewControls.nativeWindow);

        } else {
            _previewController.lib = lib;
            _previewWindow.nativeWindow.visible = true;
            _previewControls.nativeWindow.visible = true;
        }
    }

    // Causes a window to be hidden, rather than closed, when its close box is clicked
    protected static function preventWindowClose (window :NativeWindow) :void {
        window.addEventListener(Event.CLOSING, function (e :Event) :void {
            e.preventDefault();
            window.visible = false;
        });
    }

    protected var _previewController :PreviewController;
    protected var _previewWindow :PreviewWindow;
    protected var _previewControls :PreviewControlsWindow;

    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) {
                        _errors.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.parent);
                    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 if (Files.hasExtension(file, "fla")) addFlashDocument(file);
            }
        });
    }

    protected function addFlashDocument (file :File) :void {
        const status :DocStatus = new DocStatus(file, _rootLen, Ternary.UNKNOWN, Ternary.UNKNOWN, null);
        _libraries.dataProvider.addItem(status);
        loadFlashDocument(status);
    }

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

        stage.quality = StageQuality.BEST;
        _publisher.publish(status.lib, DeviceSelection(_authoredResolution.selectedItem).type);

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

    protected function loadFlashDocument (status :DocStatus) :void {
        if (Files.hasExtension(status.file, "xfl")) status.file = status.file.parent;
        if (status.file.isDirectory) {
            const name :String = status.file.nativePath
                .substring(_rootLen).replace(File.separator, "/");
            const load :Future = new XflLoader().load(name, status.file);
            load.succeeded.add(function (lib :XflLibrary) :void {
                status.lib = lib;
                status.updateModified(Ternary.of(_publisher == null || _publisher.modified(lib)));
                for each (var err :ParseError in lib.getErrors()) _errors.dataProvider.addItem(err);
                status.updateValid(Ternary.of(lib.valid));
            });
            load.failed.add(function (e :Error) :void {
                trace("Failed to load " + status.file.nativePath + ":" + e);
                status.updateValid(Ternary.FALSE);
                throw e;
            });
        } else loadFla(status.file);
    }

    protected function loadFla (file :File) :void {
        log.info("fla support not implemented", "path", file.nativePath);
        return;
        Files.load(file).succeeded.add(function (file :File) :void {
            const zip :FZip = new FZip();
            zip.loadBytes(file.data);
            const files :Array = [];
            for (var ii :int = 0; ii < zip.getFileCount(); ii++) files.push(zip.getFileAt(ii));
            const xmls :Array = F.filter(files, function (fz :FZipFile) :Boolean {
                return StringUtil.endsWith(fz.filename, ".xml");
            });
            const movies :Array = F.filter(xmls, function (fz :FZipFile) :Boolean {
                return StringUtil.startsWith(fz.filename, "LIBRARY/Animations/");
            });
            const textures :Array = F.filter(xmls, function (fz :FZipFile) :Boolean {
                return StringUtil.startsWith(fz.filename, "LIBRARY/Textures/");
            });
            function toFn (fz :FZipFile) :String { return fz.filename };
            log.info("Loaded", "bytes", file.data.length, "movies", F.map(movies, toFn),
                "textures", F.map(textures, toFn));
            for each (var fz :FZipFile in movies) {
                XflMovie.parse(null, bytesToXML(fz.content), MD5.hashBytes(fz.content));
            }
            NA.exit(0);
        });
    }

    protected var _rootLen :int;

    protected var _publisher :Publisher;
    protected var _docFinder :Executor;
    protected var _win :ExporterWindow;
    protected var _libraries :DataGrid;
    protected var _errors :DataGrid;
    protected var _exportChooser :DirChooser;
    protected var _importChooser :DirChooser;
    protected var _authoredResolution :DropDownList;
    protected const _settings :SharedObject = SharedObject.getLocal("flump/Exporter");

    private static const log :Log = Log.getLog(Exporter);
}
}
import flump.export.DeviceType;

class DeviceSelection {
    public var type :DeviceType;
    public function DeviceSelection (type :DeviceType) {
        this.type = type;
    }
    public function toString () :String {
        return type.displayName + " (" + type.resWidth + "x" + type.resHeight + ")";
    }
}
import flash.events.EventDispatcher;
import flash.filesystem.File;

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 = QUESTION;
    public var file :File;
    public var lib :XflLibrary;

    public function DocStatus (file :File, rootLen :int, modified :Ternary, valid :Ternary, lib :XflLibrary) {
        this.file = file;
        this.lib = lib;
        path = file.nativePath.substring(rootLen);
        _uid = path;

        updateModified(modified);
        updateValid(valid);
    }

    public function updateValid (newValid :Ternary) :void {
        changeField("valid", function (..._) :void {
            if (newValid == Ternary.TRUE) valid = CHECK;
            else if (newValid == Ternary.FALSE) valid = FROWN;
            else valid = QUESTION;
        });
    }

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

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

    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 QUESTION :String = "?";
    protected static const FROWN :String = "☹";
    protected static const CHECK :String = "✓";
}

//
// Flump - Copyright 2012 Three Rings Design

package flump.export {

import flash.desktop.NativeApplication;
import flash.display.NativeWindow;
import flash.display.Stage;
import flash.display.StageQuality;
import flash.events.Event;
import flash.events.MouseEvent;
import flash.filesystem.File;
import flash.net.SharedObject;

import com.adobe.crypto.MD5;

import deng.fzip.FZip;
import deng.fzip.FZipFile;

import flump.bytesToXML;
import flump.display.Movie;
import flump.executor.Executor;
import flump.executor.Future;
import flump.export.Ternary;
import flump.xfl.ParseError;
import flump.xfl.XflLibrary;
import flump.xfl.XflMovie;

import mx.collections.ArrayCollection;
import mx.events.PropertyChangeEvent;

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

import starling.display.Sprite;

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

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

    protected static const IMPORT_ROOT :String = "IMPORT_ROOT";
    protected static const AUTHORED_RESOLUTION :String = "AUTHORED_RESOLUTION";

    public function Exporter (win :ExporterWindow) {
        Log.setLevel("", Log.INFO);
        _win = win;
        _errors = _win.errors;
        _libraries = _win.libraries;

        _authoredResolution = _win.authoredResolutionPopup;
        _authoredResolution.dataProvider = new ArrayCollection(DeviceType.values().map(
            function (type :DeviceType, ..._) :Object {
                return new DeviceSelection(type);
            }));
        var initialSelection :DeviceType = null;
        if (_settings.data.hasOwnProperty(AUTHORED_RESOLUTION)) {
            try {
                initialSelection = DeviceType.valueOf(_settings.data[AUTHORED_RESOLUTION]);
            } catch (e :Error) {}
        }
        if (initialSelection == null) initialSelection = DeviceType.IPHONE_RETINA;
        _authoredResolution.selectedIndex = DeviceType.values().indexOf(initialSelection);
        _authoredResolution.addEventListener(Event.CHANGE, function (..._) :void {
            var selectedType :DeviceType = DeviceSelection(_authoredResolution.selectedItem).type;
            _settings.data[AUTHORED_RESOLUTION] = selectedType.name();
        });

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

            var status :DocStatus = _libraries.selectedItem as DocStatus;
            _win.preview.enabled = (status != null && status.isValid);
        }

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

            if (curSelection != null) {
                curSelection.removeEventListener(PropertyChangeEvent.PROPERTY_CHANGE, updatePreviewAndExport);
            }
            var newSelection :DocStatus = _libraries.selectedItem as DocStatus;
            if (newSelection != null) {
                newSelection.addEventListener(PropertyChangeEvent.PROPERTY_CHANGE, updatePreviewAndExport);
            }
            curSelection = newSelection;
        });
        _win.reload.addEventListener(MouseEvent.CLICK, function (..._) :void {
            setImport(_importChooser.dir);
            updatePreviewAndExport();
        });
        _win.export.addEventListener(MouseEvent.CLICK, function (..._) :void {
            for each (var status :DocStatus in _libraries.selectedItems) {
                exportFlashDocument(status);
            }
        });
        _win.preview.addEventListener(MouseEvent.CLICK, function (..._) :void {
            showPreviewWindow(_libraries.selectedItem.lib);
        });
        _importChooser =
            new DirChooser(_settings, "IMPORT_ROOT", _win.importRoot, _win.browseImport);
        _importChooser.changed.add(setImport);
        setImport(_importChooser.dir);
        _exportChooser =
            new DirChooser(_settings, "EXPORT_ROOT", _win.exportRoot, _win.browseExport);
        _exportChooser.changed.add(updatePreviewAndExport);
        function updatePublisher (..._) :void {
            if (_exportChooser.dir == null) _publisher = null;
            else {
                _publisher =
                    new Publisher(_exportChooser.dir, new XMLFormat(), new JSONFormat(), new StarlingFormat());
            }
        };
        _exportChooser.changed.add(updatePublisher);
        updatePublisher();
        _win.addEventListener(Event.CLOSE, function (..._) :void { NA.exit(0); });
    }

    protected function setImport (root :File) :void {
        _libraries.dataProvider.removeAll();
        _errors.dataProvider.removeAll();
        if (root == null) return;
        _rootLen = root.nativePath.length + 1;
        if (_docFinder != null) _docFinder.shutdownNow();
        _docFinder = new Executor(1);
        findFlashDocuments(root, _docFinder, true);
        _win.reload.enabled = true;
    }

    protected function showPreviewWindow (lib :XflLibrary) :void {
        if (_previewController == null || _previewWindow.closed || _previewControls.closed) {
            _previewWindow = new PreviewWindow();
            _previewControls = new PreviewControlsWindow();
            _previewWindow.started = function (container :Sprite) :void {
                _previewController = new PreviewController(lib, container, _previewWindow,
                    _previewControls);
            }

            _previewWindow.open();
            _previewControls.open();

            preventWindowClose(_previewWindow.nativeWindow);
            preventWindowClose(_previewControls.nativeWindow);

        } else {
            _previewController.lib = lib;
            _previewWindow.nativeWindow.visible = true;
            _previewControls.nativeWindow.visible = true;
        }
    }

    // Causes a window to be hidden, rather than closed, when its close box is clicked
    protected static function preventWindowClose (window :NativeWindow) :void {
        window.addEventListener(Event.CLOSING, function (e :Event) :void {
            e.preventDefault();
            window.visible = false;
        });
    }

    protected var _previewController :PreviewController;
    protected var _previewWindow :PreviewWindow;
    protected var _previewControls :PreviewControlsWindow;

    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) {
                        _errors.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.parent);
                    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 if (Files.hasExtension(file, "fla")) addFlashDocument(file);
            }
        });
    }

    protected function addFlashDocument (file :File) :void {
        const status :DocStatus = new DocStatus(file, _rootLen, Ternary.UNKNOWN, Ternary.UNKNOWN, null);
        _libraries.dataProvider.addItem(status);
        loadFlashDocument(status);
    }

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

        stage.quality = StageQuality.BEST;
        _publisher.publish(status.lib, DeviceSelection(_authoredResolution.selectedItem).type);

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

    protected function loadFlashDocument (status :DocStatus) :void {
        if (Files.hasExtension(status.file, "xfl")) status.file = status.file.parent;
        if (status.file.isDirectory) {
            const name :String = status.file.nativePath
                .substring(_rootLen).replace(File.separator, "/");
            const load :Future = new XflLoader().load(name, status.file);
            load.succeeded.add(function (lib :XflLibrary) :void {
                status.lib = lib;
                status.updateModified(Ternary.of(_publisher == null || _publisher.modified(lib)));
                for each (var err :ParseError in lib.getErrors()) _errors.dataProvider.addItem(err);
                status.updateValid(Ternary.of(lib.valid));
            });
            load.failed.add(function (e :Error) :void {
                trace("Failed to load " + status.file.nativePath + ":" + e);
                status.updateValid(Ternary.FALSE);
                throw e;
            });
        } else loadFla(status.file);
    }

    protected function loadFla (file :File) :void {
        log.info("fla support not implemented", "path", file.nativePath);
        return;
        Files.load(file).succeeded.add(function (file :File) :void {
            const zip :FZip = new FZip();
            zip.loadBytes(file.data);
            const files :Array = [];
            for (var ii :int = 0; ii < zip.getFileCount(); ii++) files.push(zip.getFileAt(ii));
            const xmls :Array = F.filter(files, function (fz :FZipFile) :Boolean {
                return StringUtil.endsWith(fz.filename, ".xml");
            });
            const movies :Array = F.filter(xmls, function (fz :FZipFile) :Boolean {
                return StringUtil.startsWith(fz.filename, "LIBRARY/Animations/");
            });
            const textures :Array = F.filter(xmls, function (fz :FZipFile) :Boolean {
                return StringUtil.startsWith(fz.filename, "LIBRARY/Textures/");
            });
            function toFn (fz :FZipFile) :String { return fz.filename };
            log.info("Loaded", "bytes", file.data.length, "movies", F.map(movies, toFn),
                "textures", F.map(textures, toFn));
            for each (var fz :FZipFile in movies) {
                XflMovie.parse(null, bytesToXML(fz.content), MD5.hashBytes(fz.content));
            }
            NA.exit(0);
        });
    }

    protected var _rootLen :int;

    protected var _publisher :Publisher;
    protected var _docFinder :Executor;
    protected var _win :ExporterWindow;
    protected var _libraries :DataGrid;
    protected var _errors :DataGrid;
    protected var _exportChooser :DirChooser;
    protected var _importChooser :DirChooser;
    protected var _authoredResolution :DropDownList;
    protected const _settings :SharedObject = SharedObject.getLocal("flump/Exporter");

    private static const log :Log = Log.getLog(Exporter);
}
}
import flump.export.DeviceType;

class DeviceSelection {
    public var type :DeviceType;
    public function DeviceSelection (type :DeviceType) {
        this.type = type;
    }
    public function toString () :String {
        return type.displayName + " (" + type.resWidth + "x" + type.resHeight + ")";
    }
}
import flash.events.EventDispatcher;
import flash.filesystem.File;

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 = QUESTION;
    public var file :File;
    public var lib :XflLibrary;

    public function DocStatus (file :File, rootLen :int, modified :Ternary, valid :Ternary, lib :XflLibrary) {
        this.file = file;
        this.lib = lib;
        path = file.nativePath.substring(rootLen);
        _uid = path;

        updateModified(modified);
        updateValid(valid);
    }

    public function updateValid (newValid :Ternary) :void {
        changeField("valid", function (..._) :void {
            if (newValid == Ternary.TRUE) valid = CHECK;
            else if (newValid == Ternary.FALSE) valid = FROWN;
            else valid = QUESTION;
        });
    }

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

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

    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 QUESTION :String = "?";
    protected static const FROWN :String = "☹";
    protected static const CHECK :String = "✓";
}

update the Preview and Export buttons when the "valid" status changes