// ==========================================================================
//	Module: MasterScript.js
// ==========================================================================
//	JS functionality for Updater
// ==========================================================================
//  The Software, including this file, is subject to the End User License 
//  Agreement.
//  Copyright (c) 2003-2004, Adobe Systems Incorporated, All Rights Reserved.
// ==========================================================================

//
// All scripts (master, data, code, ui, maintainance, etc) should obey the
// following conventions:
//
// -- each script can define one and only one object.  eval() returns the
// result of evaluating the last statement of the script.  therefore, the
// last statement of all script should simply be the script obj.
// 
// -- has a "version" key
// 
// --  more to be added

// constants used during evaluation for dialog descriptions
var isMac = new Boolean(updater.app.platform.substr(0,3) == "MAC");
var isWin = new Boolean(updater.app.platform.substr(0,3) == "WIN");
var isReader = new Boolean((updater.app.viewerType.substr(0,6) == "Reader"));

var nextCheckText = (updater.app.getUpdaterString("IDS_PREFS_PANEL_NEXTCHECK"));
var manualNextCheck = (updater.app.getUpdaterString("IDS_PREFS_PANEL_MANUALNEXTCHECK"));
var checkForUpdatesText = (updater.app.getUpdaterString("IDS_PREFS_PANEL_CHECKTXT"));
var updateAcrobatNowText = (updater.app.getUpdaterString("IDS_UPDATE_ACROBAT_NOW"));
var updateReaderNowText = (updater.app.getUpdaterString("IDS_UPDATE_READER_NOW"));
var updateNotificationBtn = (updater.app.getUpdaterString("IDS_PREFS_PANEL_VIEW_NOTIFS"));
var displayStartupNotifDlgText = (updater.app.getUpdaterString("IDS_UPDATE_SHOW_NOTIF_TXT"));
var showInstallCompDlgText = (updater.app.getUpdaterString("IDS_UPDATE_SHOW_INSTCOMP_TXT"));
var autoBkgrndMethodText = (updater.app.getUpdaterString("IDS_PREFS_PANEL_UPDATEINBACKGROUND"));
var autoForegrndMethodText = (updater.app.getUpdaterString("IDS_PREFS_PANEL_UPDATEMONTHLY"));

// calcuate width of the button 
var gStaticTextWidth = 8;
//var gUpdateNowWidth =  (gStaticTextWidth * Math.max(updateAcrobatNowText.length, updateReaderNowText.length));
var gUpdateNowWidth = (gStaticTextWidth * updateNotificationBtn.length);

var ScriptObj = {

    version:"0.50",
    
    msUDStateUnknown: 0,
    msUDStateAutoUD: 1,
    msUDStateManualUD: 2,
    msUDStateFindComp: 3,
    msUDStateCheckForUD: 4,
    msUDStateNewPDF: 5,

	// Localized JS strings needed by the Pref Panel -- copy these directly from ResourceScript.js files.
	// Don't put these in ResourceScript.js because the user may see the pref panel without having 
	// any scripts downloaded.
	// Not being used currently (used to have Installed Updates button text that got added between 6.0 and 
	// 6.0.1)
	PrefPanelLocJSStrings:
	{
		"ENU" :
		{

		},
		"CHS" :
		{

		},
		"CHT" :
		{

		},
		"DAN" :
		{

		},
		"DEU" :
		{

		},
		"ESP" :
		{

		},
		"FRA" :
		{

		},
		"ITA" :
		{

		},
		"JPN" :
		{

		},
		"KOR" :
		{

		},
		"NLD" :
		{

		},
		"NOR" :
		{

		},
		"PTB" :
		{

		},
		"SUO" :
		{

		},
		"SVE" :
		{

		}
	},

	getPrefPanelLocStr: function(strID)
	{
		updater.MasterScript.assert(strID && (strID.length > 0), "invalid string id");

		var appLang = updater.MasterScript.getAppLang();
		if (updater.MasterScript.PropertyIsDefined(updater.MasterScript.PrefPanelLocJSStrings, appLang))
		{
			var strRes = updater.MasterScript.PrefPanelLocJSStrings[appLang];

			if (updater.MasterScript.PropertyIsDefined(strRes, strID))
			{
				return strRes[strID];
			}
			else
			{
				// Try ENU if strID is missing from nonENU string resource.
				strRes = updater.MasterScript.PrefPanelLocJSStrings["ENU"];
				if (updater.MasterScript.PropertyIsDefined(strRes, strID))
				{
					return strRes[strID];
				}
				else
				{
					return "MISSING_STRID_" + strID;
				}
			}
		}
		else
		{
			return "MISSING_STRING_RESOURCE_" + appLang;
		}
	},

    prodConfigToURLMap: {
      "Exchange-Pro": "pro",
      "Exchange" : "std",
      "Reader" : "rdr"
    },

    scriptRootTemplate: "_PRODCONF_/_PLATFORM_/_LANG_/",

    getScriptRootURL: function()
    {
      var prod, platform, lang;
      
      prod = this.prodConfigToURLMap[updater.app.viewerType];
      this.assert(prod.length > 0, "Invalid Product Config");
      
      platform = updater.app.platform.toLowerCase();

      var lang;

      var tier = this.getAppTier();

      // user is in cross tier config
      if (tier.toLowerCase() == "all") { lang = "all"; }
      else if (tier.toLowerCase() == "enu") { lang = "enu"; }
      // else { lang = this.getDefLangForTier(tier).toLowerCase(); }
      // always use running lang to produce the URL
      else { lang = this.getAppLang().toLowerCase(); }

      var urlext = new String(this.scriptRootTemplate);

      urlext = urlext.replace(/_PRODCONF_/, prod);
      urlext = urlext.replace(/_PLATFORM_/, platform);
      urlext = urlext.replace(/_LANG_/, lang);
      
      updater.console.println("url ext = " + urlext);
      
      return updater.scriptRootURL + urlext;
    },
    
    getDSURL: function()
    {
        var dsfile = "DataScript.js";
        var ret = this.getScriptRootURL() + dsfile;
        return ret;
    },


    //
    // The App Tier and Language  API
    //
    // The Language code
    //
    //      {"DEU", "de-DE"},
    // 		{"ESP", "es-ES"},
    // 		{"FRA", "fr-FR"},
    // 		{"ITA", "it-IT"},
    // 		{"JPN", "ja-JP"},
    // 		{"NLD", "nl-NL"},
    // 		{"SVE", "sv-SE"},
    // 		{"SUO", "fi-FI"},
    // 		{"NOR", "no-NO"},
    // 		{"NON", "no-NO"},
    // 		{"DAN", "da-DK"},
    // 		{"KOR", "ko-KR"},
    // 		{"PTB", "pt-BR"},
    // 		{"CHS", "zh-CN"},
    // 		{"CHT", "zh-TW"},
    // 		{"ENU", "en-US"}
    //
  
    // language code id to description
    LangToDescMap:
    {
    "ENU" : "English",
    "JPN" : "Japanese",
    "CHT" : "Chinese Traditional",
    "CHS" : "Chinese Simplified",
    "KOR" : "Korean",
    "FRA" : "French",
    "DEU" : "German",
    "PTB" : "Portuguese Brazil",
    "ITA" : "Italian",
    "NLD" : "Dutch",
    "ESP" : "Spanish",
    "SVE" : "Swedish",
    "DAN" : "Danish",
    "SUO" : "Finish",
    "NOR" : "Norwegian",
    "HEB" : "Hebrew",
    "ARA" : "Arabic"
    },

    // Map either ISO4Char or RFC1766 to language code id
    CountryToLangMap: 
    {
    "en"    : "ENU",
    "en-us" : "ENU",
    "ja"    : "JPN",
    "ja-jp" : "JPN",
    "zh-tw" : "CHT",
    "zh-cn" : "CHS",
    "ko"    : "KOR", 
    "ko-kr" : "KOR",
    "fr"    : "FRA",
    "fr-fr" : "FRA",
    "de"    : "DEU",
    "de-de" : "DEU",
	"pt"	: "PTB",
    "pt-br" : "PTB",
    "it"    : "ITA",
    "it-it" : "ITA",
    "nl"    : "NLD",
    "nl-nl" : "NLD",
    "es"    : "ESP",
    "es-sp" : "ESP",
    "sv"    : "SVE",
    "sv-se" : "SVE",
    "da"    : "DAN",
    "da-dk" : "DAN",
    "fi"    : "SUO",
    "fi-fi" : "SUO",
    "no"    : "NOR",
    "no-no" : "NOR",
    "he"    : "HEB",
    "he-il" : "HEB",
    "ar"    : "ARA",
    "ar-ae" : "ARA"
    },

    // TODO: need to update the tier map
    // Map Tier to lang in lang code id
    TierToLangsMap:
    {
    "EFGJ" : ["FRA", "DEU", "JPN"],
    "CCK"  : ["CHT", "CHS", "KOR"],
    "DD"   : ["DAN", "NLD"],
    "NSF"  : ["NOR", "SVE", "SUO"],
    "SIP"  : ["ESP", "ITA", "PTB"]
    },

    // if the app is in a tier BUT running in ENU, which is always available,
    // then we need to have a default lang for the tier so that we know where
    // to fetch the correct DataScript from
    getDefLangForTier: function(tier) {
      var m = this.TierToLangsMap;
      for (var i in this.TierToLangsMap) {
        if (i.toLowerCase() == tier.toLowerCase()) {
          return m[i][0]; // the first element in array is the default lang
          }
        }
      updater.MasterScript.assert(false, "Invalid Tier: " + tier);
      return "ENU";  // just in case something really bad happens
    },

    getAppLang: function() {
      if ( ! updater.MasterScript.PropertyIsDefined(updater, "appLang") ||
           updater.appLang == null ) 
        {
        updater.appLang = updater.app.language;
        }
      return updater.appLang;
    },
  
    // Returns
    // 1. ENU iff ENU is the only lang the app has
    // 2. ALL if app has cross tier lang res
    // 3. one of the key in TierToLangsMap reflecting the tier app is in
    getAppTier: function() {

      if ( updater.MasterScript.PropertyIsDefined(updater, "appTier") &&
           updater.appTier != null ) {
        return updater.appTier;
        }

      updater.console.println("app is running with lang = " + this.getAppLang());
    
      // determine tier
      var res;
      try {
        res = this.getLangRes();
        }
      catch (e) {
        updater.console.println("Exception calling getLangRes()" + e);
        this.assert(false, "  getLangRes() exception");
        updater.console.println("  defaulting res to ENU");
        res = new Array();
        res[0] = "ENU";
        }
    
      updater.console.println("app has follow lang resources = " + res);

      if (res.length == 1) { 
        // if the app has ONLY single lang has, it MUST be ENU
        updater.MasterScript.assert(res[0] == "ENU", "Single Lang res MUST be ENU");
        updater.appTier = res[0];
        return res[0]; 
        }
    
      var dup = {};  // used as a boolean bitmap
      var t = Array();  // list of tiers
      
      // narrow down to Tiers given all the res available
      for ( var i in res ) {
        // skip ENU cause all config has ENU
        if (res[i] == "ENU") { continue; }

        var tier = this.mapLangToTier(res[i]);
        if ( ! updater.MasterScript.PropertyIsDefined(dup, tier) ) {
          dup[tier] = true;
          t.push(tier);
          }
        // updater.MasterScript.DumpObject(dup, "dup", true);
        }
      
      updater.console.println("app is in tier = " + t);
      
      if (t.length == 1) {
        updater.appTier = t[0];
        }
      else {
        updater.console.println("app is in Multi-Tier = " + t);
        updater.appTier = "ALL";
        }
      return updater.appTier;
    },
    
    mapLangToTier: function(lang) {
      var m = this.TierToLangsMap;
      for ( var tier in m ) {
        var langs = m[tier];
        for ( var i in langs) {
          if ( lang.toLowerCase() == langs[i].toLowerCase() ) return tier;
          }
        }
      updater.MasterScript.assert(false, "Input Lang invalid: " + lang);
    },
  
    // return array of resources in Country Code app has.
    // ENU MUST always be in the array.
    getLangRes: function() {
	  updater.console.println("getLangRes()");
      var langRet = new Array();
      var idx = 0;
      
      // Get path to app root folder
	  var avSpCat = updater.fileSys.AVSpecialCategory;
      var avSpFold = updater.fileSys.AVSpecialFolder;
      var resRoot = updater.fileSys.GetSpecialFolder(avSpCat["kAVSCApp"], avSpFold["kAVSFRoot"]);

      if (updater.app.platform == "WIN") 
      {
        var type = updater.app.viewerType;
        var winLangFileBase;
        if ( type == "Reader" ) {
           winLangFileBase = "RdLang32";
          }
        else {
          winLangFileBase = "ExLang32";
          }
          
        // win always has ENU
        langRet[idx++] = "ENU";
          
        // Don't enum all files in the app root folder (way too slow), instead, check for specific files
        for (var i in updater.MasterScript.LangToDescMap)
        {
			if (i.toUpperCase() == "ENU")
			{
				continue;
			}
			
			var sideCarExt = i;
			var sideCarFile = winLangFileBase + "." + sideCarExt;
			var sideCarPath = resRoot.mdPath + sideCarFile;
			updater.console.println("  Looking for side car file " + sideCarPath);
			
			var fileProps = updater.fileSys.getFileProps(sideCarPath);
			if (fileProps.isExistingFile)
			{
				updater.console.println("  Found side car file " + sideCarPath);
				var lcode = sideCarExt.toUpperCase();
				updater.console.println(" lcode = " + lcode);
				if ( typeof(lcode) != "undefined" ) 
				{
					langRet[idx++] = lcode; 
				}
			}
        }
      }
      else if (updater.app.platform == "MAC") 
      {
        var macLangFileExt = "lproj";
        
        // no special folder defined to access lang resource folder.
        // manually create the path from app root:
        //    /Acrobat 7.0.app/Contents/MacOS/
        // -->/Acrobat 7.0.app/Contents/Resources/
        var dip = new String(resRoot.diPath);
        var resPath = dip.replace(/MacOS/, "Resources");

        for (var i in updater.MasterScript.CountryToLangMap)
        {
			var resFolderBase = i;
			var resFolder = resFolderBase + "." + macLangFileExt;
			var resFolderPath = resPath + resFolder;
			updater.console.println("  Looking for resource folder " + resFolderPath);
			
			var fileProps = updater.fileSys.getFileProps(resFolderPath, true);
			if (fileProps.isExistingFolder)
			{
				updater.console.println("  Found resource folder " + resFolderPath);
				var cnt = resFolderBase.toLowerCase();
				// Mac may use underscore instead of dash in country lang code 
				// ie: "zh_cn" instead of "zh-cn".  
				// replace it with dash.
				cnt = cnt.replace(/_/, "-");
				updater.console.println(" cnt   = " + cnt);
				var lcode = updater.MasterScript.CountryToLangMap[cnt];
				updater.console.println(" lcode = " + lcode);
				updater.MasterScript.assert(lcode.length > 0, "Cannot find " + cnt + " in CountryToLangMap");
				
	            if (typeof(lcode) != "undefined")
	            { 
					langRet[idx++] = lcode; 
				}
			}
        }
      }
      else 
      {
        this.assert(false, "Unsupported platform : " + updater.app.platform);
        return null;
      }
    
      return langRet;
    },

    // base.ext
    getFileNameBase: function(fn) {
      var sep = fn.indexOf(".");
      if (sep == -1) return fn;
      return fn.substring(0, sep);
    },

    // base.ext
    getFileNameExt: function(fn) {
      var sep = fn.indexOf(".");
      if (sep == -1) return "";
      return fn.substring(sep + 1, fn.length);
    },


    // pop a dialog and return true if restart pending.
    checkPendingRestart: function(silent) 
    {
      var restartPending;
      
      if ( typeof(updater["restartPending"]) != "undefined" && updater.restartPending == true) {
        updater.console.println("===> App Restart Pending!  Skip Running Updater");
        restartPending = updater.restartPending;
        
        // need restarting Acrobat...  
        if (! silent) {
          var title = updater.app.getUpdaterString("uisJSNeedRestartTitle");
          var type = updater.app.viewerType;
          var infoMsg;
          if ( type == "Reader" ) {
            infoMsg = updater.app.getUpdaterString("uisJSNeedRestartTxtRdr");
            }
          else {
            infoMsg = updater.app.getUpdaterString("uisJSNeedRestartTxtVwr");
            }
          updater.app.alert({nIcon:3, nType:0, cTitle:title, cMsg:infoMsg});  
          }
        }
      else {
        restartPending = false;
        }
      return restartPending;
    },

    // ALL MasterScript entry point MUST call init() before proceeding.
    // if init() returns false, caller should exit and not proceed.
    init: function(silent)
    {
      updater.console.println("MasterScript init() - silent = " + silent);
      
      if (updater.app.getUpdateDisabled()) {
        updater.console.println("   Updater Disabled!!");
        return false;
      }
      
      // checkPendingRestart is the only init func for now.
      // may have more MasterScript global init func later.
      var ret = this.checkPendingRestart(silent);

      // don't cont if restart pending
      var toCont = !ret;

      updater.console.println("MasterScript init() - to continue = " + toCont);
      return (toCont);
    },

    //
    // Main Entry point for doing manual or auto updates
    //
    // set force to true for manual update, otherwise, it determines if it is
    // time to update for the auto update case
    // 
    entryPoint: function(force)
    {
		var timer = updater.MasterScript.CreateTimer("  Auto/Manual UD MasterScript entry point");
		force ? this.ManualUpdateAsync() : this.AutoUpdateAsync();
		updater.MasterScript.LogTimingInfo(timer, "  Exiting MasterScript entry point");
    },

    // 
    // utils function 
    //
    PropertyIsDefined: function( obj, propName )
    {
        var ret = (typeof( obj[ propName ] ) != "undefined");
        // updater.console.println("PropertyIsDefined returns " + ret + " for " + propName);
        return ret;
    },

    DumpStrObj: function (obj) {
      // updater.console.println("  ---- DumpStrObj -----");
      updater.console.println("  obj.length = " + obj.length);
      for (var i = 0; i < obj.length; i++) {
        updater.console.println("  charcode obj[" + i + "] = " + obj.charCodeAt(i));
      }
      // updater.console.println("  ---- DumpStrObj End -----");
    },
    
    // improve it to dump recursively later if i have time
    DumpObject: function (obj, str, vals) {
      if (! str ) str = "";
      else str = str + " ";
      str += "(" + obj + ") [" + typeof(obj) + "]\n";
      for (var p in obj) {
        str += "  " + p + ( vals ? ": " + obj[p] : "") + "\n";
      }
      updater.console.println(str);
    },

    DumpArray: function (array, str) {
      if (! str ) str = "";
      else str = str + " ";
      str += "(" + array + ") [" + typeof(array) + "]\n{ ";
      for( var i = 0;  i < array.length;  i++ ) {
		str += array[i] + ( i < array.length - 1 ? ", " : " }" );
      }
      updater.console.println(str);
    },

    GetFunctionName: function(f) {
      var name = f.toString().match(/(function .*\))/)[1];

      if ((name == null) || (name.length == 0))
        name = 'anonymous';

      return name;
    },

    StackTrace: function() 
    {
      var ret = '';
      for (var i = arguments.callee.caller; i != null; i = i.caller) {
        if (i) {
          ret += '> ' + this.GetFunctionName(i) + '\n';
        }
        // if (i.caller == i) {
        //           ret += '*';
        //           break;
        //         }
      }
      return ret;
    },

    assert: function(cond, details) {
      if (!cond) {
        var msg = "!!!!! Assert failure ";
        if (details) {
          msg = msg + " - " + details;
        }
        if (arguments.callee.caller != null) {
          msg = msg + "in " 
                + this.GetFunctionName(arguments.callee.caller)
                + ")";
        }
        msg = msg + "\n" + this.StackTrace();
        updater.console.println(msg);
      }
    },


    //
    // convert a string in generalized ASN1 time format to JS Date Object
    // returns null if conversion failed.  (it only support UTC as timezone.
    // cannot convert between timezones yet)
    // 
    // eg:
    // http last mod date:
	// Date: Thu, 07 Nov 2002 10:09:38 GMT
    // GEN_ASN1 eg1:
    // "20021107061101Z"
    //  012345678901234
    // 
    // GEN_ASN1 eg2:
    // "20021107061101.27Z"
    // 
    // GEN_ASN1 eg3:
    // "20021107061101.27-0200"
    //
    GenASN1ToDate: function (str) {

      var year, month, day, hours, mins, secs, ms, timezone;
      var timeStr = new String(str);

      // has to be at least 14 or more chars
      if (timeStr.length < 14) { return null; }

      year  = timeStr.substring(0 , 4);
      month = timeStr.substring(4 , 6);
      // zero based month used in JS Date
      month = month - 1;
      day   = timeStr.substring(6 , 8);
      hours = timeStr.substring(8 , 10);
      mins  = timeStr.substring(10, 12);
      secs  = timeStr.substring(12, 14);

//       updater.console.println(year);
//       updater.console.println(month);
//       updater.console.println(day);
//       updater.console.println(hours);
//       updater.console.println(mins);
//       updater.console.println(secs);

      
      // if more than 14 chars, ms and/or timezone may be present
      ms = 0; 
      timezone = "Z";

      if (timeStr.length > 14) {
        var plus, minus, zee, dot;
        dot =  timeStr.indexOf(".", 14);
        plus = timeStr.indexOf("+", 14);
        minus = timeStr.indexOf("-", 14);
        zee = timeStr.indexOf("Z", 14);

//         updater.console.println("dot = " + dot);        
//         updater.console.println("plus = " + plus);
//         updater.console.println("minus = " + minus);
//         updater.console.println("zee = " + zee);

        // check if . exists, if so, try parse ms
        //
        // FIXME: ms parsing is NOT correct here....
        // 0.5 should really be 500 ms, rather than 5 here.

        // must compare >= 0 to force numeric comparison instead of lexical
        if (dot >= 0) {
          if (zee >= 0) { ms = timeStr.substring( dot + 1, zee); }
          else if (plus >= 0) { ms = timeStr.substring( dot + 1, plus); }
          else if (minus >= 0) { ms = timeStr.substring( dot + 1, minus); } 
          else { ms = 0; }
        }

//        updater.console.println(ms);

        if (zee >= 0) { timezone = "Z"; }
        else if (plus >= 0) { timezone = timeStr.substring( plus, plus + 5); }
        else if (minus >= 0 ) { timezone = timeStr.substring( minus, minus + 5); }
        else { timezone = "Z"; }

//        updater.console.println("timezone = " + timezone);
      }

      //updater.console.println("returning");
      return new Date(Date.UTC(year, month, day, hours, mins, secs, ms));
    },

    // replace as many %s in str with replacements in array in order
    ReplacePrintfToken: function(str, array) {
      if (array.constructor != Array || array.length == 0) return str;
      if (str.constructor != String || str.length == 0) return str;

      var token = "%s";
      for (var i = 0; i < array.length; i++) {
        str = str.replace(token, array[i]);
        }
      
      return str;
    },

    // 
    // return Date Object
    // the first update time is NOW
    CreateFirstUpdateTime: function()
    {
        var t = new Date(); // current time in UTC
        var ms = t.getTime();
        var first = new Date(ms);
        // updater.console.println("now = " + t);
        // updater.console.println("next = " + next);
 
        return first;
    },

    isTimeToUpdate: function()
    {
      // define nextCheckDate
      // updater.console.println("isTimeToUpdate()");

        if (!this.PropertyIsDefined(updater, "nextCheckDate")) {
          //updater.console.println("nextCheckDate not defined; define it now");
          updater.nextCheckDate = this.CreateFirstUpdateTime();
          updater.console.println("nextCheckDate = " + updater.nextCheckDate);          }

        //return true;

        var now = new Date();

        if (now.getTime() >= updater.nextCheckDate.getTime()) {
            updater.console.println("-->It is time to do monthly auto update");
            return true;
        } else {
            updater.console.println("-->It is NOT time to do monthly auto update yet.  will check on:");
            updater.console.println(updater.nextCheckDate);
            return false;
        }
    },

    advanceNextCheckDate: function()
    {
        updater.console.println("advanceNextCheckDate()");
        var fuss = ((Math.random() * 7) + 28) * (1000 * 60 * 60 * 24); // in ms
        var now_d = new Date();
        var now_ms = now_d.getTime();
        var next_ms = updater.nextCheckDate.getTime();
        next_ms = (next_ms >= now_d.ms - 12*60*60*1000) ? next_ms + fuss : now_ms + fuss;
        var later = new Date(next_ms);
        updater.nextCheckDate = later;
    },
    
    // All items not in the server-side files that should go in the js cache (ie, udstore.js) 
	// need to be represented in UpdateFromStore().
    UpdateFromStore: function()
    {
      if (updater.MasterScript.PropertyIsDefined(updater.store.perUser, "nextCheckDate")) 
      {
        updater.nextCheckDate = new Date(Date.parse(updater.store.perUser.nextCheckDate));
        updater.console.println(".. nextCheckDate = " + updater.nextCheckDate);
      }
      
      // viewed messages
      if (updater.MasterScript.PropertyIsDefined(updater.store.perUser, "viewedMessages")) 
      {
        updater.viewedMessages = updater.store.perUser.viewedMessages;
      }
      else 
      {
        updater.viewedMessages = {};
      }
    },

    loadState: function()
    {
      updater.console.println("updater loading store");
      try {
        updater.store.load();
        }
      catch (e) {
        updater.console.println("Exception in loadState(): " + e);
        updater.console.println("  --> skip loading store");
        }
        
        updater.MasterScript.UpdateFromStore();
    },

	// All items not in the server-side files that should go in the js cache (ie, udstore.js) 
	// need to be represented in UpdateStore().
	UpdateStore: function()
	{
		if (updater.MasterScript.PropertyIsDefined(updater, "nextCheckDate")) 
		{
			updater.store.perUser.nextCheckDate = updater.nextCheckDate.toUTCString();
        }

		if (updater.MasterScript.PropertyIsDefined(updater, "viewedMessages")) 
		{
			updater.store.perUser.viewedMessages = updater.viewedMessages;
        }
	},
    
    saveState: function()
    {
      updater.console.println("updater saving store");
      updater.MasterScript.UpdateStore();

      try {
        updater.store.save();
        }
      catch (e) {
        updater.console.println("Exception in saveState(): " + e);
        throw new Error("SaveStateException");
        }
    },

    // will pop up a dialog warning user if no net connection is found
    checkNetConnection: function(skipUI) 
    {
      var hasNet; 
      if ( typeof(updater.net["isConnected"]) != "undefined") {
        hasNet = updater.net.isConnected();
        }
      else {
        hasNet = updater.dlm.hasNetAccess();
        }
      
      if (!hasNet && !skipUI) {
        var title = updater.app.getUpdaterString("bsJSNoNetConnectTitle");
        var errMsg = updater.app.getUpdaterString("bsJSNoNetConnectText");
        updater.app.alert({nIcon:0, nType:0, cTitle:title, cMsg:errMsg});
      }
      
      return hasNet;
      
    },


    // dialog for missing component
    showMissingCompDialog: function(cName) {
      var text;
      var title = updater.app.getUpdaterString("csJSMissCompTitle");
      
      if (!cName || cName.length == 0) {
        text = updater.app.getUpdaterString("csJSMissCompText");
        }
      else {
        text = updater.MasterScript.ReplacePrintfToken(updater.app.getUpdaterString("csJSMissCompTextName"),
                                                       [cName]);
        }
      return updater.app.alert({nIcon:2, nType:2, cTitle:title, cMsg:text});
    },

    wellKnownCompDesc: {
      "Plugin/EBook" : function() {
        return updater.app.getUpdaterString("IDS_SAI_EBOOK_NAME");
      },
      
      "Plugin/MultiMedia": function() { 
        return updater.app.getUpdaterString("IDS_SAI_MULTIMEDIA_NAME");
      },
      
      "Plugin/PictureTasks": function() { 
        return updater.app.getUpdaterString("IDS_SAI_MULTIMEDIA_NAME");
      },

      "Annot/Screen": function() { 
        return updater.app.getUpdaterString("IDS_SAI_MULTIMEDIA_NAME");
      },

      "Annot/Movie": function() { 
        return updater.app.getUpdaterString("IDS_SAI_MULTIMEDIA_NAME");
      },

      "AsianFont/TChn": function() { 
        return updater.app.getUpdaterString("IDS_SAI_ASIANFONT_CHT_NAME");
      },

      "AsianFont/SChn": function() { 
        return updater.app.getUpdaterString("IDS_SAI_ASIANFONT_CHS_NAME");
      },

      "AsianFont/Japn": function() { 
        return updater.app.getUpdaterString("IDS_SAI_ASIANFONT_JPN_NAME");
      },
      
      "AsianFont/Kore": function() { 
        return updater.app.getUpdaterString("IDS_SAI_ASIANFONT_KOR_NAME");
      }
      },

    // First look up in updater.wellKnownCompDesc (maybe updated by
    // CodeScipt) if missing, then look up in
    // updater.MasterScript.wellKnownCompDesc (the default list)
    // returns a non zero length string if desc is known and found for comp
    lookUpWellKnownCompDesc: function(type, name) {
      var l;
      var key = type + "/" + name;
      if (updater.MasterScript.PropertyIsDefined(updater, "wellKnownCompDesc")) {
        updater.console.println("  using updater.wellKnownCompDesc");
        l = updater.wellKnownCompDesc;
        }
      else {
        updater.console.println("  using updater.MasterScript.wellKnownCompDesc"); 
        l = updater.MasterScript.wellKnownCompDesc;
        }
      if (! updater.MasterScript.PropertyIsDefined(l, key) ) {
        updater.console.println("  cannot find prop " + key);
        return null;
        }
      if (l[key].constructor == Function) {
        try {
          updater.console.println("  calling " + key + " as function");
          return l[key].call();
          }
        catch (e) {
          updater.console.println("Exception calling wellKnownCompDesc." + key);
          return null;
          }
        }
      else 
        return l[key];
    },

    // MasterScript Entry Point
    // app.findComponent()
    findComponent: function(type, name, desc, ver, params, silent)
    {
      try {
        return this.FindComponentAsync(type, name, desc, ver, params, silent);
        }
      catch (e) {
        updater.console.println("exception in findComponent: " + e);
        return false;
        }
    },
    
    // MasterScript entry point
	// app.checkForUpdate()
	checkForUpdate: function(type, name, ver, msg, params)
	{
		try
		{
			return this.CheckForUpdateAsync(type, name, ver, msg, params, true);
		}
		catch (e)
		{
			updater.console.println("exception in app.checkForUpdate(): " + e);
			return false;
		}
	},
	
	newPDFCheck: function(fileSizeHi, fileSizeLo, headBuf, headSize, tailBuf, tailSize)
	{
		var ret = false;
	
		try
		{
			ret = updater.MasterScript.NewPDFCheckAsync(fileSizeHi, fileSizeLo, headBuf, headSize, tailBuf, tailSize, true);
		}
		catch (e)
		{
			updater.console.println("exception in newPDFCheck(): " + e);
			ret = false;
		}
		
		return ret;
	},

    // MasterScript Entry Point
    //   this is called by the idle proc
    CheckDLMState: function()
    {
      try {
        updater.MasterScript.asset(false, "CheckDLMState is obsoleted and should not be called.  DLM state checking is now done by AutoUpdateAsync");
        return;
        }
      catch (e) {
        updater.console.println("exception in CheckDLMState: " + e);
        }
    },


    // find out which message-only notification user has not read and pop up
    // modal dialogs
    popNotifications: function()
    {
      try {
        updater.console.println("  MasterScript popNotifications()");

        if ( ! this.init(true) ) return;

        updater.console.println("popNotifications()");

        // since called AFTER doUpdate(), just need to check if required
        // scripts exist. also, check user pref. if so, pop new notifications
        var notiEnable = updater.avpref.get("Updater", "ShowNotifDialog", 
                                            updater.avpref.type["Boolean"], true);
        // bail out if user doesn't want to see notifications
        if (!notiEnable) { return; }
        
        var hasAllScripts = updater.MasterScript.getAllScriptsFromStore();

        updater.console.println("notiEnable = " + notiEnable);
        updater.console.println("hasAllScripts = " + hasAllScripts);
        if (notiEnable && hasAllScripts) {
          updater.DataScript.entryPoint({func:"popNotifications", args:{}});
          }
        updater.console.println("popNotifications() Done");
        }
      catch (e) {
        updater.console.println("exception in popNotifications: " + e);
        return false;
        }
    },

    // URL utils
    urlGetProgUI: {
      cancelled: false,
      initialize: function(dialog) 
        {
          currentDialog = dialog;
          this.cancelled = false;
        },
      commit: function(dialog) {},
      close: function()
        {
          if(currentDialog)
            {
            currentDialog.end();
            return true;
            } else return false;
        },
      "cncl": function(dialog) {
        updater.console.println("  cancelled progress dialog");
        this.cancelled = true;
        this.close();
      },
      description: {
        name: updater.app.getUpdaterString("msJSChkForUpdtTitle"),
        elements:
        [
            {
            type: "view",
            align_children: "align_left",
            elements: [
                {
                type: "static_text",
                name: updater.app.getUpdaterString("msJSChkForUpdtText"),
                alignment: "align_center",
                width: 200
                },
                {
                type: "view",
                alignment: "align_fill",
                align_children: "align_fill",
                elements:[
                    {
                    type: "button",
                    item_id: "cncl",
                    name: updater.app.getUpdaterString("msJSChkForUpdtCancel"),
                    alignment: "align_center"
                    }
                  ]
                }
              ]
            }
        ]
      }
    },

    urlMonTask: function()
    {
      try {
        updater.console.println("enter gURLMonTask");
        var mon = updater.gURLMon;
        if (! mon.done) { 
          updater.console.println("     gURLMonTask waits...");
          mon.wait();
        }
        else {
          updater.console.println("     gURLMonTask got all...");
         if (updater.gURLMonTask) {
            updater.app.clearInterval(updater.gURLMonTask);
            updater.gURLMonTask = null;
           }
         updater.MasterScript.urlGetProgUI.close();
        }
        updater.console.println("exit gURLMonTask");
        }
      catch (e) {
        if (updater.gURLMonTask) {
          updater.app.clearInterval(updater.gURLMonTask);
          updater.gURLMonTask = null;
          }
        updater.MasterScript.urlGetProgUI.close();
        updater.console.println("Exception in URLMonTask" + e);
      }
    },

    // ifModSinceDate: js Date obj
    urlGetAsyncJSUI: function(url, ifModSinceDate)
    {
      updater.console.println("Enter urlGetAsyncUI");
      if (updater.gURLMonTask || updater.gURLMon ) {
        updater.console.println("gURLMonTask or gURLMon non-null! throwing...");
        throw new Error("Fatal error: urlGetAsyncUI is in progress!!");
      }

      var taskStr = "updater.MasterScript.urlMonTask();";
      
      updater.console.println("--- Added IdleProc");
      if (ifModSinceDate) {
        updater.gURLMon = updater.net.urlGetAsync(url, ifModSinceDate.toUTCString());
      }
      else {
        updater.gURLMon = updater.net.urlGetAsync(url);
      }
      updater.gURLMonTask = updater.app.setInterval(taskStr, 1000);
      updater.gURLMon.wait();
      updater.app.execDialog(this.urlGetProgUI);

      if ( this.urlGetProgUI.cancelled ) {
        if (updater.gURLMonTask) {
          updater.app.clearInterval(updater.gURLMonTask);
          updater.gURLMonTask = null;
        }
        updater.gURLMon.cancel();
        updater.gURLMon = null;

        updater.console.println("urlGetAyncUI cancelled!  throwing...");
        throw new Error("urlGetAyncUI cancelled");
      }
      var resp = updater.gURLMon.response;

      updater.gURLMonTask = null;
      updater.gURLMon = null;

      updater.console.println("Exit urlGetAsyncUI");
      updater.console.println(" http response status = " + resp["Status"]);
      return resp;
    },

    // ifModSinceDate: js Date obj
    // use blocking updater.net.urlGet
    urlGetUI: function(url, ifModSinceDate, closeUI)
    {
      updater.console.println("Enter urlGetUI");

      // for backward compatibility
//       if ( !this.PropertyIsDefined(updater.app, "showProgressDialog") ) {
//         var resp;
//         if (ifModSinceDate) {
//           updater.console.println(" -- pt 1 ");          
//           resp = updater.net.urlGet(url, ifModSinceDate.toUTCString());
//           this.DumpObject(ifModSinceDate);
//           updater.console.println(" -- pt 2 "); 
//         }
//         else {
//           resp = updater.net.urlGet(url);
//         }
//         return resp;
//       }

      // new
      if (!updater.isAutoUpdate) {
        updater.app.showProgressDialog();
      }
      updater.app.processDialogEvent();

      var resp;
      if (ifModSinceDate) {
        updater.console.println(" start urlGet() using if-mod-since");
        resp = updater.net.urlGet(url, ifModSinceDate.toUTCString());
        updater.console.println(" done urlGet()");
        }
      else {
        updater.console.println(" start urlGet()");
        resp = updater.net.urlGet(url);
        updater.console.println(" done urlGet()"); 
        }

      updater.app.processDialogEvent();
      if (closeUI) {
        updater.app.hideProgressDialog();
        }

      // this.DumpObject(resp);

      if ( updater.app.isProgressDialogCancelled() ) {
        throw new Error("ProgressDialog Cancelled");
        }
      
      updater.console.println("Exit urlGetUI");
      return resp;
    },


    // ifModSinceDate: js Date obj
    // use Async updater.net.urlGetAsync
    urlGetAsyncUI: function(url, ifModSinceDate, closeUI)
    {
      updater.console.println("Enter urlGetUIAsync");

      // new
      // updater.isAutoUpdate is a gloabl
      if (! updater.isAutoUpdate) {
        updater.app.showProgressDialog();
      }

      var mon;
      if (ifModSinceDate) {
        updater.console.println(" calls updater.net.urlGetAsync() using if-mod-since");
        mon = updater.net.urlGetAsync(url, ifModSinceDate.toUTCString());
        }
      else {
        updater.console.println(" calls updater.net.urlGetAsync()");
        mon = updater.net.urlGetAsync(url);
        }

      // loop for waiting for async get
      updater.console.println(" start looping url mon");
      while (! mon.done) {
        //updater.console.println("  ...waiting mon");
        updater.app.processDialogEvent();
        mon.wait();
        updater.app.processDialogEvent();
        if ( updater.app.isProgressDialogCancelled() ) {
          updater.console.println("  ...cancelling mon");
          mon.cancel(); 
          // updater.app.processDialogEvent();
          updater.app.hideProgressDialog();
          throw "URLGetCancelled";
          }
        }
      if (closeUI) {
        updater.app.hideProgressDialog();
        }

      var resp = mon.response;

      updater.console.println("Exit urlGetUIAsync");
      return resp;
    },

    //
    // FixUp the cacheScriptName so that it is product and language
    // dependent
    // eg: cachedMasterScript -> cachedMasterScript_Exchange-Pro_JPN
    //     cachedDataScript -> cachedDataScript_Reader_FRA
    //
    FixUpCacheScriptName: function(str) {
      var ret = str + "_" + updater.app.viewerType + "_" + updater.app.language;
      return ret;
    },
    // 
    // updater.tmp
    // updater.MasterScript
    // updater.DataScript
    // updater.CodeScript
    // updater.UIScript
    //

    //
    // updater.store.perUser:
    //   cachedMasterScript = { cachedDate: Date, scriptObj: script };
    //   cachedDataScript
    //   cachedCodeScript
    //   cachedUIScript
    //   nextCheckDate
    // 
    // CachedScript obj is like { cachedDate: Date, scriptObj: script };
    //
    
    // The input scriptCacheName are cachedMasterScript, cachedDataScript,
    // cachedCodeScript, cachedUIScript, etc.
    // 
    // scriptCacheName are postpended with prodConfig and Lang:
    // eg: cachedMasterScript -> cachedMasterScript_Exchange-Pro_JPN
    //     cachedDataScript -> cachedDataScript_Reader_FRA
    //
    // the postpended names are the actually names used in udstore.js
    //
    GetCachedScriptObj: function(serializationRoot, // updater.store.perUser
                                 scriptCacheName) // cachedDataScript, name for a CachedScript obj
    {
      updater.console.println("  GetCachedScriptObj()");
      var cachedScriptObj = null;
      
      // Should validate input params here!
      if (! scriptCacheName || scriptCacheName.length <=0 ) { throw "InvalidCachedName"; };
      
      scriptCacheName = this.FixUpCacheScriptName(scriptCacheName);
      updater.console.println("  scriptCacheName = " + scriptCacheName);
      
      // var serializationRoot = updater.store.perUser;
      
      // Do we have a cached data script object? If so, grab the date
      // associated with it.  This represents the moddate (in server-time) of
      // the data.

      try {
        // try finding cachedScriptModDate from presisted script
        if ( this.PropertyIsDefined(serializationRoot, scriptCacheName) ) {

          // Looks like we have a cached data script obj.  Just to be safe,
          // we'll ensure that it has the required keys
          // var cachedScriptObj = serializationRoot.cachedDataScript;
          cachedScriptObj = serializationRoot[scriptCacheName];

          // the cachedScriptObj must have prop scriptObj and cachedData
          if ( !this.PropertyIsDefined( cachedScriptObj, "scriptObj" ) || !this.PropertyIsDefined( cachedScriptObj, "cachedDate" ) ) {
            updater.console.println("   corrupted scriptobj");
            // missing a critical prop! nuke the cache info
            // delete serializationRoot.cachedDataScript;
            delete serializationRoot[scriptCacheName];
            }
            // else {
            // setup to use the moddate of the data JS in an if-modified-since query
            // cachedScriptModDate = new Date(Date.parse(cachedScriptObj.cachedDate));
            //updater.console.println("  cachedScriptObj.cachedDate = " + cachedScriptModDate);
            // }

          // if older version of DataScript < 0.02
          // updater.console.println("  cachedScript version = " + cachedScriptObj.scriptObj.version);
          
          //if (cachedScriptObj.scriptObj.version < "0.02") {
          //updater.console.println(" has older DataScript.  force loading new one");
          //cachedScriptModDate = null;
          //}
          }
        }
      catch (e) {
        updater.console.println(" exception in finding out cachedScriptModDate ");
        updater.console.println(" setting cachedScriptModDate to null");
      }
      return cachedScriptObj;
    },

    GetCachedScriptModDate: function(cachedScriptObj)
    {
      updater.console.println("  GetCachedScriptModDate()");
      var cachedScriptModDate = null; 
      if ( cachedScriptObj && this.PropertyIsDefined( cachedScriptObj, "cachedDate" ) ) {
        cachedScriptModDate  = new Date(Date.parse(cachedScriptObj.cachedDate));
        }
      return cachedScriptModDate;
    },

    // returns a resp obj
    DownloadScript: function(scriptURL, cachedScriptModDate)
    {
      updater.console.println("  DownloadScript()");
      var resp;
      try {
        if (cachedScriptModDate) {
          updater.console.println("  Using if-mod-since");
          if (updater.useSyncRead) {
            resp = this.urlGetUI(scriptURL, cachedScriptModDate);
            }
          else {
            resp = this.urlGetAsyncUI(scriptURL, cachedScriptModDate);
            }
          }
        else {
          updater.console.println("  NOT Using if-mod-since");
          if (updater.useSyncRead) {
            resp = this.urlGetUI(scriptURL);
            }
          else {
            resp = this.urlGetAsyncUI(scriptURL);
            }
          }
        }
      catch (e) {
        if (e == "URLGetCancelled") {
          throw "DownloadCancelled";
        }
        updater.console.println("Exception raised from updater.net.urlGet() or urlGetAsync() - " + e);
        updater.app.hideProgressDialog();
        throw "URLGetException";
        }
      return resp;
    },

    // return a scriptObj 
    GetAndCacheScriptObjFromResp: function(cachedScriptObj, resp, 
                                           serializationRoot, scriptCacheName)
    {
      updater.console.println("  GetAndCacheScriptObjFromResp()");
      scriptCacheName = this.FixUpCacheScriptName(scriptCacheName);
      updater.console.println("  scriptCacheName = " + scriptCacheName);

      // If we succesfully downloaded the data script, or if we got a not-modified result, we need to grab the
      // date returned by the server, which we'll use as our new last-check date.  This avoids clock skew
      // between the local machine and the server.
      var serverDate = null;

      updater.console.println("  checking response status");
      if (!resp || ! this.PropertyIsDefined(resp, "Status")) {
        updater.app.hideProgressDialog();
        throw "NoReponseStatus";
      }

      updater.console.println("  http response status = " + resp["Status"]);

      // if-modified-since case
      if (resp["Status"] == 304) {
        updater.console.println("  if-mod-since used since server response status = " + resp["Status"]);
        // used the cached datascript
        // updater.DataScript = cachedScriptObj.scriptObj;
        return cachedScriptObj.scriptObj;
        }

      // all other server error
      if (resp["Status"] != 200) {
        updater.app.hideProgressDialog();          
        throw "ServerError";
        }

      // Status = 200
      updater.console.println("  got newer content from server");
      // updater.console.println("resp.Content = " + resp.Content.substr(0, 30));
      // Get rid of the old cached data since it is out of date
      // delete serializationRoot.cachedDataScript;
      delete serializationRoot[scriptCacheName];
      
      if ( this.PropertyIsDefined( resp.Headers, "Date" ) ) {
        serverDate = this.GenASN1ToDate( resp.Headers["Date"] );
        updater.console.println("  Got Server Date ");
        }
      else {
        // illegal HTTP response without a Date header!
        updater.console.println("  illegal HTTP response without a Date header!  throw!");
        updater.app.hideProgressDialog();
        throw "NoDateHeader";
        }

      // Get the new moddate for the downloaded data out of the HTTP response headers.  Per HTTP 1.1 spec
      // section 14.25, we'll first try to get the Last-Modified header.  Failing this, we'll use the value
      // of the date header (which we retrieved above and stored in serverDate)
      var newModDate;
      if ( this.PropertyIsDefined( resp.Headers, "Last-Modified" ) ) {
        updater.console.println("  using Last-Modified to cache");
        newModDate = this.GenASN1ToDate( resp.Headers["Last-Modified"] );
        }
      else {
        updater.console.println("  using serverDate to cache");
        newModDate = serverDate;
        }
      updater.console.println("  newModDate = " + newModDate);
      
      // eval the returned data.  If this pukes, it'll throw.  We'll put in a check for null as well
      // just to be paranoid
      updater.console.println("  Eval resp.Content ");
      var newScriptObj = eval( resp.Content );
      if ( newScriptObj == null ) {
        updater.console.println("  Something wrong while eval resp.Content.  throws!");
        updater.app.hideProgressDialog();
        throw "BadData";
        }
        
      // Put the new cached data where it belongs
      updater.console.println("  Caching Content");
      // serializationRoot.cachedDataScript = { cachedDate : newModDate, scriptObj : newScriptObj };
      serializationRoot[scriptCacheName] = { cachedDate : newModDate, scriptObj : newScriptObj };
      
      // updater.DataScript = newScriptObj;
      return newScriptObj;
    },

    // return scriptObj
    downloadAndCacheScript: function(scriptURL,
                                     serializationRoot, // updater.store.perUser
                                     scriptCacheName // cachedDataScript, name for a CachedScript obj
                                     ) {

      if (! scriptURL || scriptURL.length <=0 ) { throw "InvalidScriptURL"; };
      updater.console.println("===== downloadAndCacheScript =====");
      updater.console.println("   url = " + scriptURL);

      var cachedScriptObj = null;
      cachedScriptObj = this.GetCachedScriptObj(serializationRoot, scriptCacheName);

      var cachedScriptModDate = null;
      cachedScriptModDate = this.GetCachedScriptModDate(cachedScriptObj);

      var resp = null;

      resp = this.DownloadScript(scriptURL, cachedScriptModDate);
      
      var scriptObj = null;
      scriptObj = this.GetAndCacheScriptObjFromResp(cachedScriptObj, resp, 
                                                    serializationRoot, scriptCacheName);

      return scriptObj;
    },


    // return a script or null
    getScriptFromStore: function(serializationRoot, 
                                 scriptCacheName) {
      if (updater.MasterScript.PropertyIsDefined(serializationRoot, scriptCacheName)) {
        updater.console.println("getScriptFromStore got " + scriptCacheName);
        return serializationRoot[scriptCacheName].scriptObj;
        
        }
      else {
        updater.console.println("getScriptFromStore returns null ");
        return null;
        }
    },

    // bring all scripts to runtime from store
    // only called by popNotifications().
    // will NOT go to net and fetch latest script
    // return true if all scripts exist
    //
    // TODO: need to check ResourceScript as well 

    // TODO: even better, if DataScript exist, check it dependency list for
    // required scripts rather than just code in all scripts names here

    // FIXME: MUST CALL FixUpCacheScriptName!!! otherwise won't find any scripts

    getAllScriptsFromStore: function() {
      updater.console.println(" getAllScriptsFromStore()");
 
      if ( ! updater.MasterScript.PropertyIsDefined(updater, "DataScript")) {
        var ret = updater.MasterScript.getScriptFromStore(updater.store.perUser, 
                                                          this.FixUpCacheScriptName("cachedDataScript"));
        if (ret != null) { 
          updater.DataScript = ret; 
          }
        else { 
          updater.console.println("   missing from store - DataScript");
          return false; 
          }  // if DataScript not in store, other script cannot be
      }

      updater.MasterScript.assert(updater.DataScript, "update.DataScript undefined");

      updater.console.println("   got from store - DataScript");

      var depList = updater.DataScript.GetScriptDependency();
      updater.MasterScript.assert(depList.constructor == Array, "depList not an Array!");
      var idx = 0;
      var gotAll = false;
      updater.console.println("  depList.length = " + depList.length);
      while ( idx < depList.length ) {
      updater.console.println("  idx = " + idx);

        var sRoot = depList[idx]["serializationRoot"]; // updater.store.perUser
        var sCachName = depList[idx]["scriptCacheName"]; // "cachedCodeScript"
        var cPoint = depList[idx]["callPoint"]; // "CodeScript"
        
        if ( updater.MasterScript.PropertyIsDefined(updater, cPoint) ) {
          // already defined
          updater.console.println("   got " + cPoint);
          idx++;
          }
        else {
          // trying mounting from store
          var scpt = updater.MasterScript.getScriptFromStore(sRoot, this.FixUpCacheScriptName(sCachName));
          if (ret != null) { 
            updater[cPoint] = scpt;
            idx++;
            updater.console.println("   got from store - " + cPoint);
            }
          else {
            updater.console.println("   missing from store - " + cPoint);
            break;
            }
          }
        }

      if (idx == depList.length) { gotAll = true; };
      return gotAll;
    },

    // Dev Tests
    
    getTestMasterURL: function()
    {
        var testmaster = "TestMaster.js";
        var ret = updater.scriptRootURL + testmaster;
        return ret;
    },


    testExec: function()
    {
      // this.findComponent("annotation", "bumper sticker");
      this.popNotifications();
      
//       var d1 = "20021107061101Z";
//       var d2 = "20010315182201.27Z";
//       var d3 = "20020910213422.56-0200";

//       var res = updater.MasterScript.GenASN1ToDate(d1);
//       updater.console.println(" d1 = " + res.toUTCString());

//       var res = updater.MasterScript.GenASN1ToDate(d2);
//       updater.console.println(" d1 = " + res.toUTCString());

//       var res = updater.MasterScript.GenASN1ToDate(d3);
//       updater.console.println(" d1 = " + res.toUTCString());

//       return;

      updater.console.println("Start Updater Test");

        var testMasterURL = this.getTestMasterURL();
        var testScript;
        try {
          var resp;
          // resp = this.urlGetAsyncUI(testMasterURL);
          resp = updater.MasterScript.urlGetUI(testMasterURL, null, true);
          updater.console.println("  Got TestMaster");
          updater.TestMaster = eval(resp.Content);
          updater.console.println("  Eval TestMaster done");

          if (this.PropertyIsDefined(updater, "TestMaster")) {
            updater.TestMaster.exec();
            }
          else {
            updater.console.println("  Cannot load TestMaster");
            }
        }
        catch (e) {
          updater.console.println("exception while fetching TestMaster:  " + e);
        }
    },
    
    updaterPrefPanelDisableSrc:
    {
		initialize: function(dialog) {},
    },
    
    GetUpdaterPrefPanelDisabled: function()
    {
		var gPrefsPanelWidth = 550;
		
		var updaterPrefPanelDisableSrc = updater.MasterScript.updaterPrefPanelDisableSrc;
    	updaterPrefPanelDisableSrc["description"] = 
		{
			name: "Update Prefs Panel",
			margin_height: 0,
			margin_width: 0,
			elements: 
			[
				{
					type: "cluster",
					name: updater.app.getUpdaterString("IDS_PREFS_PANEL_UPDATE"),
					dwidth: (gPrefsPanelWidth),
					align_children: "align_left",
					elements: 
					[
						{
							type: "static_text",
							name: updater.app.getUpdaterString("IDS_PREFS_PANEL_DISABLEDDESC"),
							alignment: "align_fill"
						}
					]
				}
			]
		};
		
		return updaterPrefPanelDisableSrc;
    },

	updaterPrefPanelSrc:
	{
      version: "0.01",

	  inBackgroundText : updater.app.getUpdaterString("IDS_PREFS_PANEL_UPDATEINBACKGROUND"),
      monthlyText : updater.app.getUpdaterString("IDS_PREFS_PANEL_UPDATEMONTHLY"),
      manuallyText : updater.app.getUpdaterString("IDS_PREFS_PANEL_UPDATEMANUALLY"),

      // constants
      inBackground: 2,
      monthly: 1,
      manually: 0,
      
      nextCheckText: updater.app.getUpdaterString("IDS_PREFS_PANEL_NEXTCHECK"),
      manualNextCheck: updater.app.getUpdaterString("IDS_PREFS_PANEL_MANUALNEXTCHECK"),

      updateDescText: updater.app.getUpdaterString("IDS_PREFS_PANEL_UPDATEDESCTEXT"),
      updateDescTextReader: updater.app.getUpdaterString("IDS_PREFS_PANEL_UPDATEDESCTEXT_READER"),
      updateAcrobatNowText: updater.app.getUpdaterString("IDS_UPDATE_ACROBAT_NOW"),
      updateReaderNowText: updater.app.getUpdaterString("IDS_UPDATE_READER_NOW"),


	  hasMsgFunc: function(arg) {
	      updater.console.println("arg.returnVal = " + arg.returnVal);
		  var hasMsg = (arg.returnVal > 0);
		  arg["hasMsg"] = hasMsg;
		  updater.console.println("inner hasMsg = " + hasMsg);
	  },

      // TODO: need to unwind this to CodeScript
      initialize: function(dialog) {
        updater.console.println("updaterPrefDialog initialize");

        // this.l10n_init(dialog);

        var freq = updater.app.getUpdateFrequency();
        updater.console.println("  freq = " + freq);

        var choices = {};
        if (freq == this.inBackground)
        {
			updater.console.println("    using inBackground");
			choices[this.inBackgroundText] = 1;
			choices[this.monthlyText] = 0;
			choices[this.manuallyText] = 0;
			dialog.load({"autn" : false});
			dialog.load({"autd" : true});
			dialog.load({"naut" : false});
        } 
        else if (freq == this.monthly) 
        {
			updater.console.println("    using monthly");
			choices[this.inBackgroundText] = 0;
			choices[this.monthlyText] = 1;
			choices[this.manuallyText] = 0;
			dialog.load({"autn" : true});
			dialog.load({"autd" : false});
			dialog.load({"naut" : false});
        }
        else 
        {
			updater.console.println("    using manually");
			choices[this.inBackgroundText] = 0;
			choices[this.monthlyText] = 0;
			choices[this.manuallyText] = 1;
			dialog.load({"autn" : false});
			dialog.load({"autd" : false});
			dialog.load({"naut" : true});
        }

        // widths 
        var date;
        if (freq == this.monthly || freq == this.inBackground)
        {
			if (!updater.MasterScript.PropertyIsDefined(updater, "nextCheckDate"))
			{
				updater.console.println("nextCheckDate not defined; define it now");
				updater.nextCheckDate = updater.MasterScript.CreateFirstUpdateTime();
				updater.console.println("nextCheckDate = " + updater.nextCheckDate);
			}
	        
			date = "" + updater.nextCheckDate;
			dialog.load({"nctx" : this.nextCheckText});		
			dialog.load({"ckAc" : (date)});
        }
        else
        {
			dialog.load({ "ckAc" : "" });
			dialog.load({"nctx" : ""});        
        }

        var type = updater.app.viewerType;
        if ( type == "Reader" ) {
          dialog.load( { "text" : this.updateDescTextReader } );
          dialog.load( { "UpAc" : this.updateReaderNowText } );
          }
        else {
          dialog.load( { "text" : this.updateDescText } );
          dialog.load( { "UpAc" : this.updateAcrobatNowText } );
          }

        dialog.load( { "ShNo" : updater.app.getShowStartupNotifDialog() } );
        dialog.load( { "ShIC" : updater.app.getShowInstallCompDialog() } );

        // FIXME: 
        // enable Notification button only when there are messages
        // downloaded AND not restart pending!
        var hasAllScripts = updater.MasterScript.getAllScriptsFromStore();

		var hasMsgFuncArgs = { "hasMsg": false };

        if (hasAllScripts) {
          updater.DataScript.entryPoint({func: "GetNumMessages", 
                                         args:{}, callBack: this.hasMsgFunc,
                                         callBackArgs: hasMsgFuncArgs});
        };

        var hasMsg = hasMsgFuncArgs["hasMsg"];

        updater.console.println("outter hasMsg = " + hasMsg);
        var notiEnable = hasAllScripts && 
                         (! updater.MasterScript.checkPendingRestart(true))
                         && hasMsg;

        var hasUpsEnable = hasAllScripts &&
                           (! updater.MasterScript.checkPendingRestart(true));
                           
        dialog.enable( { "Noti" : notiEnable } );
        dialog.enable( { "InUp" : hasUpsEnable });
        dialog.createNotifier( { "InUp" : 0, "Noti" : 0 } );
      },

      // TODO: need to unwind this to CodeScript
      commit: function(dialog) {
        updater.console.println("updaterPrefDialog commit");
        
        var isMan = dialog.store("naut")["naut"];
        var isMonth = dialog.store("autn")["autn"];
        var isBackground = dialog.store("autd")["autd"];

        updater.console.println(" isMan = " + isMan);
        updater.console.println(" isMon  = " + isMonth);
        updater.console.println(" isBackground  = " + isBackground);

        updater.app.setUpdateFrequency((isBackground == 1) ? this.inBackground: 
			((isMan == 1) ? this.manually : this.monthly));
        updater.app.setShowStartupNotifDialog(dialog.store("ShNo" )["ShNo"]);
        updater.app.setShowInstallCompDialog(dialog.store("ShIC")["ShIC"]);
      },
      
      show: function(dialog)
      {
		updater.console.println("updaterPrefDialog show");
		
		var ret = true;
		var errMsg;
		var title = updater.app.getUpdaterString("IDS_PREFS_PANEL_RESTARTREQ_TITLE");
		
		if(updater.app.getRestartRequired())
		{
			if (updater.app.viewerType == "Reader")
			{
				errMsg = updater.app.getUpdaterString("IDS_PREFS_PANEL_RESTARTREQ_RDR");
			}
			else
			{
				errMsg = updater.app.getUpdaterString("IDS_PREFS_PANEL_RESTARTREQ_VWR");
			
			}
			updater.app.alert({nIcon:3, nType:0, cTitle:title, cMsg:errMsg});
			
			// Set return value to indicate that the pref panel shouldn't show
			ret = false;
			updater.console.println("  restart required");
		}
		
		if (ret == true)
		{
			updater.app.setPrefPanelSeen();
			updater.console.println("  showing pref panel");
		}
		return ret;
      },

      // TODO: need to unwind this to CodeScript
      // manual update
      // "UpAc": function(dialog) {
      // updater.console.println("UpAc clicked");
      // updater.MasterScript.entryPoint(true);
      // },
      
      "InUp": function(dialog) {
        updater.console.println("InUp clicked");
        updater.DataScript.entryPoint({func:"ShowInstalledUpdatesDialog", args:{}});        
      },

      // TODO: need to unwind this to CodeScript
      "Noti": function(dialog) {
        updater.console.println("Noti clicked");
        updater.DataScript.entryPoint({func:"ShowNotifDialog", args:{}});
      },
      
	  radioClicked: function(dialog, val)
	  {
		updater.console.println("radioClicked() with " + val);
		if (val == this.monthly || val == this.inBackground)
		{
			var date;
			
			if (!updater.MasterScript.PropertyIsDefined(updater, "nextCheckDate"))
			{
				updater.console.println("nextCheckDate not defined; define it now");
				updater.nextCheckDate = updater.MasterScript.CreateFirstUpdateTime();
				updater.console.println("nextCheckDate = " + updater.nextCheckDate);
			}
	    
			date = "" + updater.nextCheckDate;
			dialog.load({"ckAc" : (date)});
			dialog.load({"nctx" : this.nextCheckText});		
		}
		else
		{
			dialog.load({ "ckAc" : "" });
			dialog.load({"nctx" : ""});
		}
	  },      
      "autn": function(dialog) 
      {
		updater.console.println("autn clicked");
		dialog.load({"autn" : true});
		dialog.load({"autd" : false});
		dialog.load({"naut" : false});
		this.radioClicked(dialog, this.monthly);
      },
      "autd": function(dialog) 
      {
		updater.console.println("autd clicked");
		dialog.load({"autn" : false});
		dialog.load({"autd" : true});
		dialog.load({"naut" : false});
		this.radioClicked(dialog, this.inBackground);
      },
      "naut": function(dialog) 
      {
		updater.console.println("naut clicked");
		dialog.load({"autn" : false});
		dialog.load({"autd" : false});
		dialog.load({"naut" : true});
		this.radioClicked(dialog, this.manually);
      }
    },
    

   // Pref Panel
   GetUpdaterPrefPanel: function() {
      var updatePanelName = updater.app.getUpdaterString("IDS_PREFS_PANEL_UPDATE");
      var radioAutoNotify = (updater.app.getUpdaterString("IDS_PREFS_PANEL_AUTONOTIFY"));
	  var radioAutoDL = (updater.app.getUpdaterString("IDS_PREFS_PANEL_AUTODL"));
	  var radioNoAuto = (updater.app.getUpdaterString("IDS_PREFS_PANEL_NOAUTO"));
	  var installedUpBtn = (updater.app.getUpdaterString("UIS_INUPS_INSTALLED_UPDATES"));

      var gStaticTextHeight = 13;
      var gPrefsPanelWidth = 550;
      var gPrefsPanelHeight = 390;

	  var updaterPrefPanel = updater.MasterScript.updaterPrefPanelSrc;

      updaterPrefPanel["description"] = 
      {
      name: "Update Prefs Panel",
      margin_height: 0,
      margin_width: 0,
      elements: [
          {
          type: "cluster",
          name: (updatePanelName),
          dwidth: (gPrefsPanelWidth),
          dheight: (gPrefsPanelHeight),
          align_children: "align_left",
          elements: [
              {  
              type: "static_text",
              item_id: "text",
              alignment: "align_fill",
              height: (gStaticTextHeight*3)
              },
              {
              type: "view",
              align_children: "align_left",
              alignment: "align_center",
              elements: [
                  {
					type: "view",
					align_children: "align_left",
					elements: 
					[
						{
						type: "static_text",
						item_id: "cfut",
						alignment: "align_left",
						name: (checkForUpdatesText)
						},
						{
							type: "radio",
							item_id: "autn",
							group_id: "grp1",
							name: (radioAutoNotify)
						},
						{
							type: "radio",
							item_id: "autd",
							group_id: "grp1",
							name: (radioAutoDL)
						},
						{
							type: "radio",
							item_id: "naut",
							group_id: "grp1",
							name: (radioNoAuto)
						}
						               
					]
                  }
                ]
              },
              { 
					type: "view",
					align_children: "align_top",
					alignment: "align_fill",
					elements: 
					[
						{ 
							type: "static_text",
							item_id: "nctx",
							name: (updaterPrefPanel.nextCheckText)
						},
										
						{
							type: "static_text",
							item_id: "ckAc",
							alignment: "align_fill",
							height: (gStaticTextHeight*3)
						}
					]
			  },
              {
                  type: "button",
                  item_id: "Noti",
                  width: (gUpdateNowWidth),
                  name: (updateNotificationBtn)
              },
			  {
				  type: "button",
				  item_id: "InUp",
				  width: (gUpdateNowWidth),
				  name: (installedUpBtn)
			  },		
              {
              type: "gap",
              height: (gStaticTextHeight)
              },            
              {
              type: "check_box",
              item_id: "ShNo",
              name: (displayStartupNotifDlgText)
              },
              
              {
              type: "check_box",
              item_id: "ShIC",
              name: (showInstallCompDlgText)
              }
            ]
          }
        ]
      };

      return updaterPrefPanel;
    }, // GetUpdaterPrefPanel
    
    LoadPrefPanel: function(panelIsEnabled)
    {
		try
		{
			updater.console.println("  MasterScript LoadPrefPanel()");
			
			if (!this.init(true)) 
			{
				return;
			}
			
			updater.MasterScript.UpdateFromStore();
			
			if (panelIsEnabled)
			{
				updater.prefPanel = updater.MasterScript.GetUpdaterPrefPanel();
			}
			else
			{
				updater.prefPanel = updater.MasterScript.GetUpdaterPrefPanelDisabled();
			}
			return updater.prefPanel;
		}
		catch(e)
		{
			updater.console.println("exception in LoadPrefPanel: " + e);
			return null;
		}
    },

    //
    // General way of creating a state machine 
    //
    SStartStateMachine : function(idleProc, idleArgs, 
                                  resumeProc, resumeArgs, 
                                  name, states)
    {
      if (this.PropertyIsDefined(updater, "gStateMachineLock") &&
          updater.gStateMachineLock == true) {
        updater.console.println("SStartStateMachine locked by " + 
                                updater.gStateMachine.name);
        throw "StateMachineInUse";
        }
      
      var timer = updater.MasterScript.CreateTimer("  SStartStateMachine() entry");
      updater.gStateMachineLock = true;

      // TODO: add singleton
      updater.gStateMachine = new Object();
      var sm = updater.gStateMachine;

      sm.name = name;
      sm.enumStates = states;
      sm.state = states.StInit;
      sm.idleProc = idleProc;
      sm.idleArgs = idleArgs;
      sm.resumeProc = resumeProc;
      sm.resumeArgs = resumeArgs;
      sm.done = false;
      sm.error = false;
      sm.exception = null;
      // TODO: the interval in ms MUST BE greater than the ADM idle proc
      // otherwise cancel event won't be received!
      sm.handle = updater.app.setInterval(idleProc, 60);
      updater.MasterScript.LogTimingInfo(timer, "    register JS download idle proc");
      updater.MasterScript.LogTimingInfo(timer, "  SStartStateMachine() exit");
    },


    // BgDl = Background downloading
    SBgDlStates : 
    {
      StInit: 0,
      StReadStart: 1,
      StReadWait: 2,
      StReadDone: 3,
      StShowUI: 4,
      StProcessUI: 5,
      StException: 800,
      StFinished: 999
    },

    //
    // This is the state machine for doing download MULTIPLE script, posting
    // progress dialog, cache script, etc.  this is a replacement for
    // loadDataScript() / downloadAndCacheScript()
    //
    SBgDlProcessState : function()
    {
		var timer = updater.MasterScript.CreateTimer("  Entering SBgDlProcessState()");
      var sm = updater.gStateMachine;

      try {
        var state = sm.state;
        var st_enum = sm.enumStates;
        
        //updater.console.println("= SBgDlProcessState =");
        //updater.MasterScript.DumpObject(sm.idleArgs, "idleArgs", true);

        // unpackage the args
        var loadScriptArgs = sm.idleArgs.loadScriptArgs;
        updater.MasterScript.assert(loadScriptArgs.constructor == Array, "loadScriptArgs not an Array!");

        // init idx
        if (! updater.MasterScript.PropertyIsDefined(sm, "scriptArgIdx") ) {
          sm.scriptArgIdx = 0;
          }

        // updater.console.println("scriptArgIdx = " + sm.scriptArgIdx);
        // updater.MasterScript.DumpArray(loadScriptArgs);

        var lsArgs = loadScriptArgs[sm.scriptArgIdx];
        
        // cannot check assert here since lsArgs MAYBE null for the last
        // element before proceeding to the StFinished state
        //updater.MasterScript.assert(lsArgs, "lsArgs is NULL");
        // updater.MasterScript.DumpObject(lsArgs, "lsArgs", true);

        var scriptURL = lsArgs? lsArgs["scriptURL"] : null;
        var serializationRoot = lsArgs ? lsArgs["serializationRoot"] : null;
        var scriptCacheName = lsArgs ? lsArgs["scriptCacheName"] : null;
        var callPoint = lsArgs ? lsArgs["callPoint"] : null;
        var dontMount = lsArgs ? lsArgs["dontMount"] : null;

        var suppressUI = sm.idleArgs["suppressUI"];
        var closeUI = sm.idleArgs["closeUI"];
 
        switch (state) {
          case st_enum.StInit : 
            try {
              updater.console.println("=== StInit ===");
              if (! scriptURL || scriptURL.length <=0 ) { 
                sm.exception =  "InvalidScriptURL"; 
                sm.state = sm.StException;
                break;
                }
              updater.console.println("  url = " + scriptURL);
              // setup private var in sm
              sm.cachedScriptObj = null;
              sm.cachedScriptObj = updater.MasterScript.GetCachedScriptObj(serializationRoot, scriptCacheName);
              
              sm.cachedScriptModDate = null;
              sm.cachedScriptModDate = updater.MasterScript.GetCachedScriptModDate(sm.cachedScriptObj);
              
              sm.state = sm.enumStates.StShowUI;
              }
            catch (e) {
              updater.console.println("Exception in StInit: " + e);
              sm.exception = e;
              sm.state = sm.StException;
              }
            break;

        case st_enum.StShowUI:
          try {
            updater.console.println("=== StShowUI ===");
            if (!suppressUI) {
              updater.app.showProgressDialog();
              }
            sm.uiCancelled = false;
            sm.state = sm.enumStates.StReadStart;
            }
          catch (e) {
              updater.console.println("Exception in StShowUI: " + e);
              sm.exception = e;
              sm.state = sm.StException;
            }
          break;
          
        case st_enum.StReadStart :
          try {
            updater.console.println("=== StReadStart ===");
            sm.response = null;

            if (sm.cachedScriptModDate) {
              updater.console.println(" calls updater.net.urlGetAsync() using if-mod-since");
              sm.urlMon = updater.net.urlGetAsync(scriptURL, sm.cachedScriptModDate.toUTCString());
              }
            else {
              updater.console.println(" calls updater.net.urlGetAsync()");
              sm.urlMon = updater.net.urlGetAsync(scriptURL);
              }
            sm.state = sm.enumStates.StProcessUI;
            }
          catch (e) {
            updater.console.println("Exception in StReadStart" + e);
            sm.exception = "URLGetException";
            sm.state = sm.enumStates.StException;
            }
          break;

        case st_enum.StProcessUI :
          try {
            updater.console.println("=== StProcessUI ===");
            // updater.app.processDialogEvent();
            sm.state = sm.enumStates.StUIWait; 
            }
          catch (e) {
              updater.console.println("Exception in StPocessUI: " + e);
              sm.exception = e;
              sm.state = sm.StException;            
            }
          break;

        case st_enum.StUIWait :
          try {
            updater.console.println("=== StUIWait ===");
            sm.uiCancelled = updater.app.isProgressDialogCancelled()
            sm.state = sm.enumStates.StReadWait; 
            }
          catch (e) {
              updater.console.println("Exception in StUIWait: " + e);
              sm.exception = e;
              sm.state = sm.StException;            
            }
          break;
              
        case st_enum.StReadWait :
          try {
            updater.console.println("=== StReadWait ===");

            if (sm.uiCancelled) {
              sm.urlMon.cancel();
              sm.exception = "DownloadCancelled";
              sm.state = sm.enumStates.StException;
              break;
              }

            if (sm.urlMon.done) {
              sm.state = st_enum.StReadDone;
              break;
              }

            sm.urlMon.wait();

            sm.state = st_enum.StProcessUI;
            }
          catch (e) {
            updater.console.println("Exception in StReadWait" + e);
            sm.exception = e;
            sm.state = sm.enumStates.StException;
            }
          break;

        case st_enum.StReadDone:
          try {
            updater.console.println("=== StReadDone ===");

            // should check again if ui is cancelled due to late click
            // latching
            if (updater.app.isProgressDialogCancelled()) {
              sm.exception = "DownloadCancelled";
              sm.state = sm.enumStates.StException;
              break;
              }

            if (! updater.MasterScript.PropertyIsDefined(sm, "scriptObjs")
                || (sm.scriptObjs.constructor != Array) ) {
              sm.scriptObjs = new Array();
              }

            var idx = sm.scriptArgIdx;

            // acrobat can raise in urlMon.response if the download scripts
            // not signed correctly
            sm.scriptObjs[idx] = 
                updater.MasterScript.GetAndCacheScriptObjFromResp(sm.cachedScriptObj, 
                   sm.urlMon.response, serializationRoot, scriptCacheName);

            // mount call point
            if (!dontMount) {
              updater[callPoint] = sm.scriptObjs[idx];
              }
              
            // initialize script obj
            if (updater.MasterScript.PropertyIsDefined(updater[callPoint], 
                                                       "Initialize")
                &&
                updater[callPoint]["Initialize"].constructor == Function) {
              updater[callPoint]["Initialize"]();
              }

            // done when all script in loadScriptArgs is iterated 
            sm.scriptArgIdx++;
            if (sm.scriptArgIdx < loadScriptArgs.length) {
              sm.state = st_enum.StInit;
            }
            else {
              if (closeUI) {
                updater.app.hideProgressDialog();
                }
              sm.state = st_enum.StFinished;
              }
            }
          catch (e) {
            updater.console.println("Exception in StReadDone : " + e);
            sm.exception = e;
            sm.state = sm.enumStates.StException;
            }
          break;

        case st_enum.StException:
          updater.console.println("=== StException ===");
          // always closes UI when exception happens
          updater.app.hideProgressDialog();

          var e = sm.exception;
          updater.console.println("Exception in " + sm.name + ": " + e);

          if (e == "URLGetException" || e == "URLContentNotFound" || e == "ServerError") {
            // These errors are probably network or server related.  don't
            // even need to bother user with the error.  just inform them no
            // update is available at this time
            var title = updater.app.getUpdaterString("uisJSNoUpdtAvailTitle");
            var errMsg = updater.app.getUpdaterString("uisJSNoUpdtAvailAtThisTimeText");

            if (! suppressUI) 
              updater.app.alert({nIcon:3, nType:0, cTitle:title, cMsg:errMsg});
            }
          else if (e == "NoDateHeader" || e == "BadData") {
            // probably should not pop dialog here since these are internal
            // errors from downloadAndCacheScript
            var title = updater.app.getUpdaterString("bsJSNoNetConnectTitle");
            var errMsg = updater.app.getUpdaterString("bsJSInternalErrorText");
            
            if (! suppressUI)
              updater.app.alert({nIcon:0, nType:0, cTitle:title, cMsg:errMsg});
            }
          else if (e == "DownloadCancelled" ) {
            updater.MasterScript.assert(false, "DownloadCancelled");
            updater.console.println("DownloadCancelled");
            // siliently swollow the exception if user cancelled out
            }

          sm.exception = "LoadScriptException";
          sm.state = st_enum.StFinished;
          break;

        case st_enum.StFinished:
          try {
            updater.console.println("=== StFinished ===");
            
            // should check again if ui is cancelled due to late click
            // latching
            if (!sm.uiCancelled && updater.app.isProgressDialogCancelled()) {
              updater.MasterScript.assert(false, "DownloadCancelled");
              updater.console.println("DownloadCancelled");
              sm.exception = "LoadScriptException";
              sm.state = sm.enumStates.StException;
              }

            // stop the state machine from advancing and release its lock
            // since we are done
            updater.app.clearInterval(sm.handle);
            sm.done = true;
            updater.gStateMachineLock = false;
          
            // call resume proc when done
            updater.MasterScript.assert(sm.resumeProc.constructor == Function, "resumeProc NOT a function");
            if (sm.resumeProc.constructor == Function) {
              sm.resumeProc(sm);
              }
            else {
              updater.console.println("sm.resumeProc not a function.  skip calling");
              }
            }
          catch (e) {
            updater.console.println("Exception in StFinished" + e);
            // close the UI 
            updater.app.hideProgressDialog();
            }
          break;
          
        default :
          updater.MasterScript.assert(false, "Shoule NOT reach default case - " + sm.name);
          throw "CannotHappen";
          break;
          }
        }
      catch (e) {
        sm.exception = e;

        updater.app.clearInterval(sm.handle);
        sm.done = true;
        updater.gStateMachineLock = false;

        updater.console.println("Exception in SBgDlProcessState: " + e);
      }

      updater.MasterScript.LogTimingInfo(timer, "  Exiting SBgDlProcessState()");
    },

    // 
    // Updater Script loading machinism
    // 
    // MasterScript: assumed always available (from bootstrap.js).  it
    // exports common functions and system functions to all other scripts.
    // It ONLY knows how to load DataScript.js.  Once DataScript is loaded,
    // MasterScript inquires DataScript about what other scripts DataScript
    // needs by calling DataScript.GetScriptDependencyList() and load all the
    // required scripts.  After each script is loaded, its Initialize()
    // function will be called if exists.
    //
    // DataScript: loaded only by MasterScript.  It has three piece of
    // information:
    //
    // 1) the available component data structure.  it contains all available
    // component for a given acrobat product configuration.
    //
    // 2) messages to be shown by the notification dialog
    //
    // 3) A function that returns a script depency list.  This allow future
    // expandability.  it initially contains only CodeScript and UIScript.
    //
    // DataScript Initialize() will have to check if the system has correct
    // version of MasterScript.  If not, it would call
    // LoadAndCacheScriptAsync() to get a new MasterScript.  A viewer restart
    // is required if new MasterScript is loaded.
    // 
    // CodeScript: Contains all procedures for doing Auto Update, Manual
    // Update, FincComponent, etc.
    //
    // UIScript: Contains all UI elements for Updater.
    //
    // Since MasterScript knows only DataScript, call-chaining is used so
    // that arbitrary scripts (potentially new scripts) can be used to
    // implement/replace Updater's functionality.  It works like this:
    // 
    // DataScript and the script contains the implementation of Updater's
    // functionality contains a common entry point, aptly named entryPoint().
    // it is called with an {} with "func", and "args" memebers.  DataScript
    // has the right to vector off the requests to the implementation script
    // (CodeScript) by simply calling entryPoint() with pass the arg along.
    // MasterScript.AutoUpdate() --> DataScript.entryPoint(arg)
    // --> CodeScript.entryPoint(arg) --> CodeScript.AutoUpdate(udArgs)
    // where arg is {func:AutoUpdate, arg:{udArgs}}
    // udArags is an {}
    //
    
    
    // 
    // LoadAndCacheScriptAsync
    // 
    // this is a single call for handling all the nasty details regarding
    // script downloading, mounting, caching, progress UI popping, etc
    // 
    // this is a non blocking call.  if suppressUI is true, no UI is shown by
    // default, the UI is not closed when operation is done unless closeUI is
    // true.  UI is always closed when exception happens
    // 
    // resumeProc will be called with resumeArgs when the script is
    // downloaded and mounted
    //
    // loadScriptArgs is an array of elements:
    // {
    //   "scriptURL" : this.getDSURL(),
    //   "serializationRoot" : updater.store.perUser,
    //   "scriptCacheName" : "cachedDataScript",
    //   "callPoint" : "DataScript"
    //   "dontMount" : false
    // }
    //
    // each element in the loadScriptArgs array will be processed sequentially
    //
    LoadAndCacheScriptAsync: function(loadScriptArgs,
                                      resumeProc, resumeArgs,
                                      suppressUI, closeUI)
    {
		var timer = updater.MasterScript.CreateTimer("  LoadAndCacheScriptAsync() entry");
      var idleProc =  "updater.MasterScript.SBgDlProcessState()";
      
      var idleArgs = { "loadScriptArgs" : loadScriptArgs,
                       "suppressUI" : suppressUI,
                       "closeUI" : closeUI };
                      
      this.SStartStateMachine(idleProc, idleArgs,
                              resumeProc, resumeArgs, 
                              "SBgDlProcessState", this.SBgDlStates);
		updater.MasterScript.LogTimingInfo(timer, "  LoadAndCacheScriptAsync() exit");
      return this.SStartStateMachine;
    },

    //
    // LoadScriptDepFinishProc is called after DataScripts AND its depending
    // scripts are all downloaded.  The update process continue by 
    //
    LoadScriptDepFinishProc: function(sm)
    {
      try {
        updater.console.println("LoadScriptDepFinishProc()"); 
        // something wrong in state machine, raise it
        if (sm.exception) { throw sm.exception };

        updater.MasterScript.assert(sm.resumeArgs["dsCallerResumeProc"], "dsCallerResumeProc undefined! ");
        
        updater.app.hideProgressDialog();

        var func = sm.resumeArgs["dsCallerResumeProc"];
        var args = sm.resumeArgs["dsCallerResumeArgs"];
        args.suppressUI = sm.idleArgs["suppressUI"];
        
        if (func.constructor == Function) {
          func(args);
          }
        }
      catch (e) {
        updater.console.println("Exception in LoadScriptDepFinishProc: " + e);
        }
    },

    //
    // LoadDataScriptFinishProc is called when DataScript is downloaded.  It
    // continue the script loading process by using DataScript's script
    // dependency list.
    //
    LoadDataScriptFinishProc: function(sm)
    {
      try {
        updater.console.println("LoadDataScriptFinishProc()"); 

        // something wrong in state machine, raise it
        if (sm.exception) { throw sm.exception };

        var depScriptsArgs = updater.DataScript.GetScriptDependency();

        // updater.MasterScript.DumpObject(depScriptsArgs,"depScriptsArgs", true);

        updater.MasterScript.assert(depScriptsArgs.constructor == Array);

        var suppressUI = sm.resumeArgs["dsCallerSuppressUI"];
        var closeUI = sm.resumeArgs["dsCallerCloseUI"];

        updater.MasterScript.LoadAndCacheScriptAsync(depScriptsArgs, 
                                                     updater.MasterScript.LoadScriptDepFinishProc,
                                                     sm.resumeArgs,
                                                     suppressUI, closeUI);
        }
      catch (e) {
        updater.console.println("Exception in LoadDataScriptFinishProc: " + e);
        }
    },
    
    // 
    // single call for loading DataScript and its depending scripts used by
    // AutoUpdateAsync, ManualUpdateAsync, FindComponentAsync, etc
    //
    LoadDataScriptAsync: function(resumeProc, resumeArgs, 
                                  suppressUI, closeUI)
    {
		var timer = updater.MasterScript.CreateTimer("  LoadDataScriptAsync");
      var loadScriptArgs = new Array();
      
      updater.MasterScript.LogTimingInfo(timer, "  start enuming resources to determine lang/tier");

      var dsLoadArgs = {
        "scriptURL" : this.getDSURL(),
        "serializationRoot" : updater.store.perUser,
        "scriptCacheName" : "cachedDataScript",
        "callPoint" : "DataScript"
        };
      
      updater.MasterScript.LogTimingInfo(timer, "  end enuming resources to determine lang/tier");
      
      loadScriptArgs[0] = dsLoadArgs;

      //
      // remember the orginal caller's resume proc and args so that after all
      // the DataScript depending are loaded, the update process know where
      // to continue.
      // 
      var dsFinishArg = {"dsCallerResumeProc": resumeProc,
                         "dsCallerResumeArgs" : resumeArgs,
                         "dsCallerSuppressUI" : suppressUI,
                         "dsCallerCloseUI" : closeUI};

      // before running resumeProc and resumeArgs, will call
      // LoadDataScriptFinishProc it load up all scripts DataScript requires
      this.LoadAndCacheScriptAsync(loadScriptArgs,
                                   this.LoadDataScriptFinishProc, dsFinishArg,
                                   suppressUI, closeUI);
		updater.MasterScript.LogTimingInfo(timer, "  Exiting LoadDataScriptAsync");
    },
    
    
    // Timer class
    
    nextTimerID: 0,
    
    TimerGetID: function()
    {
		return this.mID;
    },
    
    TimerReset: function()
    {
		this.mStartTicks = this.mElapsedTicks = this.Ticks();
    },
    
    TimerTicks: function()
    {
		var nowDate = new Date();
		var nowMS = nowDate.getTime();
		var nowTicks = (nowMS >> 4);
		return nowTicks;
    },
    
    TimerSetElapsed: function()
    {
		this.mElapsedTicks = this.Ticks();
    },
    
    TimerGetElapsedSinceStart: function()
    {
		return (this.Ticks() - this.mStartTicks);
    },
    
    TimerGetElapsedSinceLast: function()
    {
		return (this.Ticks() - this.mElapsedTicks);
    },
    
    Timer: function()
    {
		this.mID = updater.MasterScript.nextTimerID++;
		this.mStartTicks = 0;
		this.mElapsedTicks = 0;
		
		this.GetID = updater.MasterScript.TimerGetID;
		this.Reset = updater.MasterScript.TimerReset;
		this.Ticks = updater.MasterScript.TimerTicks;
		this.SetElapsed = updater.MasterScript.TimerSetElapsed;
		this.GetElapsedSinceStart = updater.MasterScript.TimerGetElapsedSinceStart;
		this.GetElapsedSinceLast = updater.MasterScript.TimerGetElapsedSinceLast;
		
		this.Reset();
    },
    
    CreateTimer: function(msg)
    {
		var timer = null;
		
		// avpref.get will raise if the reg key/value being accessed 
		// does not exist
		try
		{
			if (updater.avpref.get("Updater", "EnableTimingLog", 
								updater.avpref.type["Boolean"], false))
			{
				timer = new updater.MasterScript.Timer();
				var timerID = timer.GetID();
				var msgStr = "Creating JS Timer " + timerID + "\n" + msg;
				updater.console.println(msgStr);
			}
		}
		catch (e)
		{
			updater.console.println("Exception in updater.MasterScript.CreateTimer(): " + e);
		}
		
		return timer;
    },
    
    LogTimingInfo: function(timer, msg)
    {
		try
		{
			if ((timer != null) && (updater.avpref.get("Updater", "EnableTimingLog", 
				 updater.avpref.type["Boolean"], false)))
			{
				var prefixStr = "";

				if (msg.length > 0)
				{
					updater.console.println(msg);
					prefixStr = "\t";
				}
				
				var sinceStart = timer.GetElapsedSinceStart();
				updater.console.println(prefixStr + "Ticks since start of JS Timer " + timer.GetID() + ": " + sinceStart);
				var sinceLast = timer.GetElapsedSinceLast();
				updater.console.println(prefixStr + "Ticks since last SetElapsed() of JS Timer " + timer.GetID() + ": " + sinceLast);
				
				timer.SetElapsed();
			}
		}
		catch (e)
		{
			updater.console.println("Exception in updater.MasterScript.LogTimingInfo(): " + e);
		}
    },

    //
    // Manual Update Procs
    //
    // this is called when DataScript.entryPoint() is done
    ManualUpdateCallBackProc: function(args)
    {
      try {
        updater.console.println("ManualUpdateCallBackProc()");
        var isMSUpdated = (updater.MasterScript.PropertyIsDefined(args, "exception") &&
                          args["exception"] == "MSUpdatedAbort");
		if (isMSUpdated)
		{
			updater.MasterScript.saveState();  // can raise 
		}
		else
		{
			updater.MasterScript.UpdateStore();
		}
      }
      catch (e) {
        updater.console.println("Exception in ManualUpdateCallBackProc: " + e);
      }
    },

    // this is called when DataScript and its depending scripts has been downloaded
    ManualUpdateFinishProc: function(args)
    {
      // call datascript main entry point
      try {
        updater.console.println("ManualUpdateFinishProc()"); 

        args.callBack = updater.MasterScript.ManualUpdateCallBackProc;
        args.callBackArgs = null;
        updater.DataScript.entryPoint(args);
      }
      catch (e) {
        updater.console.println("Exception in ManualUpdateFinishProc: " + e);
      }
    },

    ManualUpdateAsync: function()
    {
      try {
        updater.console.println("ManualUpdateAsync()"); 
        if ( ! this.init(false) ) return;

        updater.MasterScript.UpdateFromStore();

        updater.isAutoUpdate = false;
        updater.useSyncRead = false;
        var finishProcArgs = {func:"doUpdate", 
							  msUD:updater.MasterScript.msUDStateManualUD,
                              args:{isAutoUpdate:false, 
									isBkground:false}};
        this.LoadDataScriptAsync(this.ManualUpdateFinishProc, finishProcArgs);
        }
      catch (e) {
          updater.console.println("Exception in ManualUpdateAsync: " + e);
          return false;
      }
    },


    //
    // AutoUpdate and Check DLM Procs
    // 

    // this is called when DataScript.entryPoint() and AutoUpdate's
    // implemention is call and done
    AutoUpdateCallBackProc: function(args)
    {
      try {
        updater.console.println("AutoUpdateCallBackProc()"); 
        var isMSUpdated = (updater.MasterScript.PropertyIsDefined(args, "exception") &&
                          args["exception"] == "MSUpdatedAbort");
                          
        
        var updateTime = false;
            
        if (updater.MasterScript.PropertyIsDefined(args, "updateTime"))
        {
        	updater.console.println("  updateTime = " + args["updateTime"]);
			updateTime = (args["updateTime"] == true && !isMSUpdated);
        }
        else
        {
			updateTime = !isMSUpdated;
		}
        
        if (updateTime)
        {
			updater.MasterScript.advanceNextCheckDate();
		}
		else if (!isMSUpdated)
		{
			updater.console.println("Background autoupdate -- skipping advanceNextCheckDate()");
		}
                  
        if ( ! isMSUpdated ) 
        {
			updater.MasterScript.popNotifications();
        }
        else
        {
			updater.console.println("MasterScript has been updated. skipping advanceNextCheckDate() and popNotifications()");
        }
        
        if (isMSUpdated)
        {
			updater.MasterScript.saveState();  // can raise 
        }
        else
        {
			updater.MasterScript.UpdateStore();
        }
      }
      catch (e) {
        updater.console.println("Exception in AutoUpdateCallBackProc: " + e);
      }
    },

    // this is called when DataScript has been downloaded
    AutoUpdateFinishProc: function(args)
    {
      // call datascript main entry point
      try {
        updater.console.println("AutoUpdateFinishProc()"); 

        args.callBack = updater.MasterScript.AutoUpdateCallBackProc;
        args.callBackArgs = {updateTime : args.updateTime};
        updater.DataScript.entryPoint(args);
      }
      catch (e) {
        updater.console.println("Exception in AutoUpdateFinishProc: " + e);
      }
    },
    
    CheckDLMStateCallBackProc: function(args)
    {
		updater.console.println("CheckDLMStateCallBackProc()");
		try
		{
			var isMSUpdated = (updater.MasterScript.PropertyIsDefined(args, "exception") &&
                          args["exception"] == "MSUpdatedAbort");
			if (isMSUpdated)
			{
				updater.MasterScript.saveState();  // can raise 
			}
			else
			{
				updater.MasterScript.UpdateStore();
			}
		}
		catch (e) 
		{
			updater.console.println("Exception in CheckDLMStateCallBackProc: " + e);
		}
    
    },

    CheckDLMStateFinishProc: function(args)
    {
      // call datascript main entry point
      try {
        updater.console.println("CheckDLMStateFinishProc()");
		// Calling updater.DataScript.entryPoint(args) could cause the MasterScript.js to get updated, so
		// you always need to provide a callbackproc to save the MasterScript.js to udstore.js
        args.callBack = updater.MasterScript.CheckDLMStateCallBackProc;
        args.callBackArgs = {};
        updater.DataScript.entryPoint(args);
        }
      catch (e) {
        updater.console.println("Exception in CheckDLMStateFinishProc: " + e);
        }
    },

    AutoUpdateAsync: function()
    {
		var timer = updater.MasterScript.CreateTimer("  AutoUpdateAsync() entry");
		
      try {
        updater.console.println("AutoUpdateAsync()"); 

        if ( ! this.init(true) ) return;
        
        // do a quick check before using CodeScript's CheckDLMState() which
        // has dialogs.
        var dlmState = updater.dlm.getState();
        updater.MasterScript.LogTimingInfo(timer, "  got aum state");
        updater.console.println("  updater.dlm.getState() returns " + dlmState);
        // DLM not running.  rdy to accept transaction
        // quickly return
        if (dlmState != 0) { 
          // DLM has pending stuff
          // ok.  now we must do some UI since there are pending updates
          // calls into CodeScript's CheckDLMState()
          
          if (! updater.MasterScript.checkNetConnection(true) ) {
            // if no net, try if all scripts are cached already.
            var hasAllScripts = updater.MasterScript.getAllScriptsFromStore();
            if (hasAllScripts) {
              updater.DataScript.entryPoint({func:"CheckDLMState", args:{showResumeDlg:true, doResume:true}}); 
              }
            }
          else {
            // has net connection            
            // finishProcArgs is the args for calling DataScript.entryPoint(args)
            var finishProcArgs = {func:"CheckDLMState",
								  msUD:updater.MasterScript.msUDStateAutoUD, 
                                  args:{showResumeDlg:true, doResume:true}};
            this.LoadDataScriptAsync(this.CheckDLMStateFinishProc, finishProcArgs, true);
            }
          return;
          }
        else {
			// continue AutoUpdate
			updater.MasterScript.UpdateFromStore();
			updater.MasterScript.LogTimingInfo(timer, "  loaded udstore.js");

			// check if pref is set the monthly
			// background: 2
			// monthly: 1
			// manually: 0
			var freq = updater.app.getUpdateFrequency();
			updater.console.println("  freq = " + freq);

			// If doing foreground/notification auto update 
			// do so when it is time.  If doing background updating, 
			// we always perform an autoupdate.
			
			// Always check the update time -- this will advance the check time
			// after downloading DS.js if MS.js wasn't updated.
			var bUpdateTime = this.isTimeToUpdate();
			if (bUpdateTime && (freq == 2 || freq == 1))
			{
				updater.console.println("Loading DataScript and performing update...");
				// silently returns if autoupdate and no net
				if (! updater.MasterScript.checkNetConnection(true) )
				{
					return false;
				}
				updater.MasterScript.LogTimingInfo(timer, "  checked net connection");
	            
				updater.isAutoUpdate = true;
				updater.useSyncRead = true;

				// finishProcArgs is the args for calling DataScript.entryPoint(args)            
				var finishProcArgs = {func:"doUpdate", 
									  msUD:updater.MasterScript.msUDStateAutoUD,
									  args:{isAutoUpdate:true, 
											isBkground:(freq == 2)},

									  updateTime: true};
				this.LoadDataScriptAsync(this.AutoUpdateFinishProc, finishProcArgs, true);
				updater.MasterScript.LogTimingInfo(timer, "  set up loaddatascript idle proc");
            }
            else {
				// Manual Update
				// pop new notifications. calls only finish auto-update check, not
				// manual check
				this.popNotifications();
				updater.MasterScript.UpdateStore();
            }
          }
        }
      catch (e) {
        updater.console.println("Exception in AutoUpdateAsync: " + e);
        return false;
        }
    },


    //
    // FindComponent Procs
    // 
    // this is called when DataScript.entryPoint() is done
    FindComponentCallBackProc: function(args)
    {
      try {
        updater.console.println("FindComponentCallBackProc()"); 

        var ret = args["returnVal"];
        updater.console.println("ComponentFound = " + ret); 
        var isMSUpdated = (updater.MasterScript.PropertyIsDefined(args, "exception") &&
                          args["exception"] == "MSUpdatedAbort");
		if (isMSUpdated)
		{
			updater.MasterScript.saveState();  // can raise 
		}
		else
		{
			updater.MasterScript.UpdateStore();
		}
      }
      catch (e) {
        updater.console.println("Exception in FindComponentCallBackProc: " + e);
      }
    },

    // this is called when DataScript has been downloaded
    FindComponentFinishProc: function(args)
    {
      // call datascript main entry point
      try {
        updater.console.println("FindComponentFinishProc()"); 

        args.callBack = updater.MasterScript.FindComponentCallBackProc;
        args.callBackArgs = null;
        updater.DataScript.entryPoint(args);
      }
      catch (e) {
        updater.console.println("Exception in FindComponentFinishProc: " + e);
      }
    },

    FindComponentAsync: function(type, name, desc, ver, params, silent)
    {
      try {
        updater.console.println("FindComponentAsync()"); 

        if ( ! this.init() ) return;

        updater.MasterScript.UpdateFromStore();

        var hasNet = updater.net.isConnected();
        var wellKnown = false;

        if ( !desc || desc.length == 0 ) {
          // no desc given by caller.  try lookup well known component
          // description
          var knownDesc = this.lookUpWellKnownCompDesc(type, name);
          if (knownDesc && knownDesc.length > 0) {
            wellKnown = true;
            desc = knownDesc;
            }
          else {
            wellKnown = false;
            desc = type + "/" + name;
            if (ver) desc = desc + ":" + ver;
            }
          }

        updater.console.println("  wellKnown = " + wellKnown);
        updater.console.println("  desc      = " + desc);
        // if no net, prompt user if connecting to net is ok
        if (! hasNet) {
          var cont;
          if (wellKnown) {
            cont = this.showMissingCompDialog(desc);
            }
          else {
            cont = this.showMissingCompDialog();
            }
          // no = 3,  yes =4
          if (cont == 3) return false;
          }
        
        // user wants to continue.  forward request to CodeScript via DataScript
        // download the datascript (may need to connect to net)


        var finishProcArgs = {func:"findComponent", 
							  msUD:updater.MasterScript.msUDStateFindComp,
                              args:{type:type, 
									name:name, 
									desc:desc, 
									ver:ver, 
									params:params, 
									silent:silent}};
        this.LoadDataScriptAsync(this.FindComponentFinishProc, finishProcArgs, silent);
        return true;
        }
      catch (e) {
          updater.console.println("Exception in FindComponentAsync: " + e);
          return false;
      }
    },

	//
    // CheckForUpdate Procs
    // 
    // this is called when DataScript.entryPoint() is done
    
    // These must be in sync with enum in UpdaterExpT.h
	UDCheckUpdate_NotChecked: 0,
	UDCheckUpdate_NetFound: 1,
	UDCheckUpdate_NetNotFound: 2,
	UDCheckUpdate_NoNetFound: 3,
	UDCheckUpdate_NoNetNotFound: 4,
    
	CheckForUpdateCallBackProc: function(args)
    {
		try 
		{
			updater.console.println("CheckForUpdateCallBackProc()"); 

			var ret = args["returnVal"];
			updater.console.println("  Component found = " + ret); 
			var isMSUpdated = (updater.MasterScript.PropertyIsDefined(args, "exception") &&
                          args["exception"] == "MSUpdatedAbort");
			if (isMSUpdated)
			{
				updater.MasterScript.saveState();  // can raise 
			}
			else
			{
				updater.MasterScript.UpdateStore();
			}
		}
		catch (e) 
		{
			updater.console.println("Exception in CheckForUpdateCallBackProc(): " + e);
		}
    },

	CheckForUpdateFinishProc: function(args)
	{
		// call datascript main entry point
		try 
		{
			updater.console.println("CheckForUpdateFinishProc()"); 

			args.callBack = updater.MasterScript.CheckForUpdateCallBackProc;
			args.callBackArgs = null;
			updater.DataScript.entryPoint(args);
		}
		catch (e) 
		{
			updater.console.println("Exception in CheckForUpdateFinishProc(): " + e);
		}
	},
	
	CallCheckedUpdateCB: function()
	{
		updater.console.println("CallCheckedUpdateCB()");
		var cuData = updater.gCheckedUpdateData;
		
		if (cuData.hasScripts)
		{
			updater.DataScript.entryPoint(cuData.funcObj);
		}
		else
		{
			updater.app.execCheckedUpdateCallback(updater.MasterScript.UDCheckUpdate_NotChecked);
		}
		
		updater.app.clearInterval(cuData.handle);
		cuData.lock = false;
	},

	CheckForUpdateAsync: function(type, name, msg, ver, params, silent)
	{
		try
		{
			updater.console.println("CheckForUpdateAsync()"); 

			if (!this.init())
			{
				return false;
			}

			updater.MasterScript.UpdateFromStore();

			var hasNet = updater.net.isConnected();

			// If no net, check to see if has cached script files.
			if (!hasNet) 
			{
				
				if (this.PropertyIsDefined(updater, "gCheckedUpdateData") &&
					updater.gCheckedUpdateData.lock == true) 
				{
					updater.console.println("updater.gCheckedUpdateData is locked");
					return false;
				}
				
				updater.gCheckedUpdateData = new Object();
				var cuData = updater.gCheckedUpdateData;
				var checkUpdateDelayedIdleProc = "";
				
				cuData.funcObj = {func:"checkForUpdate", 
						args:{type:type, name:name, msg:msg, ver:ver, params:params, hasNet:false}};
										
				cuData.hasScripts = updater.MasterScript.getAllScriptsFromStore();
				
				checkUpdateDelayedIdleProc = "updater.MasterScript.CallCheckedUpdateCB()";
				cuData.handle = updater.app.setInterval(checkUpdateDelayedIdleProc, 60);
				cuData.lock = true;
				return true;
			}
        
			// Forward request to CodeScript via DataScript download the datascript (may need to connect to net)

			var finishProcArgs = {func:"checkForUpdate", 
								  msUD:updater.MasterScript.msUDStateCheckForUD,
								  args:{type:type, 
										name:name, 
										msg:msg, 
										ver:ver, 
										params:params, 
										hasNet:true}};
			this.LoadDataScriptAsync(this.CheckForUpdateFinishProc, finishProcArgs, silent);
			return true;
		}
		catch (e)
		{
			updater.console.println("Exception in CheckForUpdateAsync(): " + e);
			return false;
		}
	},
	
	// ********************* //
	// ********************* //
	//      NewPDFCheck      //
	// ********************* //
	// ********************* //
	
	// This function is called when DataScript.entryPoint() has had a chance to update updater.MasterScript.
    NewPDFCheckCallBackProc: function(args)
    {
      try {
        updater.console.println("NewPDFCheckCallBackProc()"); 

        var ret = args["returnVal"];
        var isMSUpdated = (updater.MasterScript.PropertyIsDefined(args, "exception") &&
                          args["exception"] == "MSUpdatedAbort");
		if (isMSUpdated)
		{
			updater.MasterScript.saveState();  // can raise 
		}
		else
		{
			updater.MasterScript.UpdateStore();
		}
      }
      catch (e) {
        updater.console.println("Exception in NewPDFCheckCallBackProc: " + e);
      }
    },
	
	// When all the scripts have been downloaded, this function gets called to call the DataScript main entry point.
	// If the MasterScript.js has been updated, the entry point will re-load updater.MasterScript.  The entry point
	// will then call updater.MasterScript.NewPDFCheckCallBackProc() for post MS update processing.
	NewPDFCheckFinishProc: function(args)
	{
		try 
		{
			updater.console.println("NewPDFCheckFinishProc()"); 

			args.callBack = updater.MasterScript.NewPDFCheckCallBackProc;
			args.callBackArgs = null;
			updater.DataScript.entryPoint(args);
		}
		catch (e) 
		{
			updater.console.println("Exception in CheckForUpdateFinishProc(): " + e);
		}
	},
	
	NewPDFCheckAsync: function(fileSizeHi, fileSizeLo, headBuf, headSize, tailBuf, tailSize, silent)
	{
		var ret = false;
		
		try
		{
			updater.console.println("NewPDFCheckAsync()");

			if (this.init())
			{
				updater.MasterScript.UpdateFromStore();
				var fileSize = (fileSizeHi << 32) + fileSizeLo;

				if (false)
				{
					var finishProcArgs = {func:"newPDFCheck", 
										msUD:updater.MasterScript.msUDStateNewPDF,
										args:{fileSizeHi:fileSizeHi, 
											  fileSizeLo:fileSizeLo,
											  headBuf:headBuf, 
											  headSize:headSize, 
											  tailBuf:tailBuf, 
											  tailSize:tailSize, 
											  silent:silent}};

					updater.MasterScript.LoadDataScriptAsync(this.NewPDFCheckFinishProc, finishProcArgs, silent);
					ret = true;	
				}
			}
		}
		catch (e)
		{
			updater.console.println("Exception in NewPDFCheckAsync(): " + e);
			ret = false;
		}
		
		return ret;
	},

    __ENDER__:"END"
};

ScriptObj;
// ==========================================================================
//	Module: StartUp.js
// ==========================================================================
//	JS functionality for Updater
// ==========================================================================
//  The Software, including this file, is subject to the End User License 
//  Agreement.
//  Copyright (c) 2003-2004, Adobe Systems Incorporated, All Rights Reserved.
// ==========================================================================
//
// StartUp.js
// 
// 
// the sole purpose of bootstrap script is to load up the masterscript and
// make sure it exists in Updater JS runtime (ie: defines
// updater.MasterScript).  it must also define a updater.scriptRootURL which
// can be used to defer the location of MasterScript on the server.
//
// during developement, the MasterScript is always loaded from a server by
// doing urlGet().  For release, there should be a presisted MasterScript in
// user's dir and the bootstrap script should load up the presist file and
// bring the cached MasterScript back to life.
//
// 
// Here is how MasterScript would be loaded up: 
// 
// 1) First check if an presisted version in updater.store is available.  If
// so, check its version againt updater.MasterScript( if it is even defined
// in the runtime probably because of bootstrap.js evaluation)
//
// Then set updater.MasterScript to the newer version.  This allow us to
// update and override MasterScript later on.
//
// 2) if NOT, use darn simple urlGet() to fetch it from
// updater.masterScriptURL the newly fetched script will be presisted so that
// it doesn't need to be refetch next time
// 
//
// updater.bootStrapped is a globle which C++ code can check if bootstrapping
// has happened.  it is for optimization so that bootstrip.js will only be
// opened and eval'ed once per app launch.
//
// updater.scriptRootURL is the base of where all scripts will live on server
//

updater.scriptRootURL = "http://update.adobe.com/pub/adobe/acrobat/js/7x/";

// updater.bootStrapped = false;

// Code for dumping all objects in the global space (except the app obj).
// It dumps all objs before update code has modified the initial state of the globals.
// Set the guard to true to use this code.
// Places a menu item in "Tools" for dumping the objects to udlog.txt also.
if (false)
{
	var DumpObject =
	function (obj, str, vals)
	{
		if (! str ) str = "";
		else str = str + ".";
	      
		var pStr = str + "(" + obj + ") [" + typeof(obj) + "]";
		updater.console.println(pStr);
		pStr = "";
		  
		for (var p in obj) {
			// Skip app global object since it causes all kinds of problems and it is not relevant.
			if (str == "this." && p == "app")
			{
				continue;
			}
			
			pStr = str + p;
			
			if (p != "fromPDFConverters")
			{
				var prop = obj[p];

				if (prop != null && typeof(prop) == "object")
				{
					//updater.console.println("dumping obj " + p);
					this.DumpObject(prop, pStr, true);
				}
				else
				{
					pStr += ( vals ? ": " + obj[p] : "");
					updater.console.println(pStr);
					pStr = "";

				}
			}
		}
	}

	var DumpGlobals =
	function()
	{
		updater.console.println("===DUMPING ALL GLOBAL OBJS===");
		this.DumpObject(this, "this", true);
		updater.console.println("===END DUMPING ALL GLOBAL OBJS===");
	}

	if (!updater.dumped)
	{
	/*
	Not working anymore -- do it manually.
		app.addMenuItem({ cName: "dump globals", 
			cParent: "Tools",
			cExec: "this.DumpGlobals();", 
			cEnable: "true",
			nPos: 0});
	*/

		this.DumpGlobals();
		updater.dumped = true;
	}
}

var StartUp = function(useESDUpdate)
{
if (! updater.bootStrapped) {

  try {
    updater.console.println("===== Loading bootstrap.js =====");

    // Since MasterScript WILL be prepended this file to form bootstrap.js, need
    // to check if ScriptObj exists and and pick it up as MasterScript
    if (typeof(ScriptObj) != "undefined") {
      updater.console.println("  picked up default MasterScript from bootstrap.js");
      updater.MasterScript = ScriptObj;
    }

    // overrideable scriptRootURL by Pref
    // updater.avpref can raise if the pref section/key not defined
    try {
      updater.scriptRootURL = updater.avpref.get("Updater", 
                                                 "ScriptRootURL", 
                                                 updater.avpref.type["String"], 
                                                 updater.scriptRootURL);
      updater.console.println("  overriding scriptRootURL from pref");
    } catch (e) {}  // safely ignore the raise by avpref 

    updater.console.println("  updater.scriptRootURL = " + updater.scriptRootURL);

    // first check if there is a presisted version (the following code has
    // CANNOT assume existence of MasterScripts and its friends of utilities
    // functions)

    try {
      updater.store.load();
      }
    catch (e) {
      updater.console.println("Exception in trying to do updater.store.load(): " + e);
      updater.console.println("  --> skip loading store");
      }

    var cachedMSVersion;
    var cachedMSName = "cachedMasterScript";
    cachedMSName = cachedMSName + "_" + updater.app.viewerType + "_" + updater.app.language;
    var curRunTimeMSVersion;

    var gotMasterScript = false;

    
    // version checking 

    // finding out cached MasterScript version
    if ( typeof(updater.store.perUser[cachedMSName]) != "undefined" &&
         typeof(updater.store.perUser[cachedMSName]["scriptObj"]) != "undefined" )
      {
      cachedMSVersion = updater.store.perUser[cachedMSName].scriptObj.version + 0.0;
      updater.console.println(" checking cachedMSVersion = " + cachedMSVersion);
      }
    else {
      cachedMSVersion = 0.0;
      }

    // finding out current MasterScript version
    if ( (typeof(updater["MasterScript"]) != "undefined" ) &&
         (typeof(updater.MasterScript["version"]) != "undefined") ) 
      {
      curRunTimeMSVersion = updater.MasterScript.version + 0.0;
      updater.console.println(" checking curRunTimeMSVersion = " + curRunTimeMSVersion);
      }
    else {
      curRunTimeMSVersion = 0.0;
      }

    if ( (curRunTimeMSVersion != 0) && (curRunTimeMSVersion > cachedMSVersion) ) {
      updater.console.println("--> Using MasterScript from current runtime.  version = " + curRunTimeMSVersion);
      gotMasterScript = true;
      }

    // MUST use >= to compare cached verion with the current one in runtime
    // since current one in runtime may well be the cached one when bootstrip
    // is eval the second time
    if ( !gotMasterScript &&
         (cachedMSVersion != 0) && (cachedMSVersion >= curRunTimeMSVersion) ) {
      updater.MasterScript = updater.store.perUser[cachedMSName].scriptObj;
      updater.console.println("--> Using MasterScript from cache.  version = " + cachedMSVersion);
      gotMasterScript = true;
      }

    if (!gotMasterScript) {
      // no existing MasterScript....do a urlGet();
      // first check if there is net connection
      var hasNet; 
      if ( typeof(updater.net["isConnected"]) != "undefined") 
      {
        hasNet = updater.net.isConnected();
      }
      else if (useESDUpdate)
      {
        hasNet = updater.dlm.hasNetAccess();
      }
      else
      {
		hasNet = false;
      }
      
      if (! hasNet) {
        // should use UIScript's showErrorDialog() when UIScript is presisted.
        // for now just do app.alert.
        var title = updater.app.getUpdaterString("bsJSNoNetConnectTitle");
        var errMsg = updater.app.getUpdaterString("bsJSNoNetConnectText");
        updater.app.alert({nIcon:0, nType:0, cTitle:title, cMsg:errMsg});
        }
      else {
        updater.console.println("  fetching MasterScript with urlGet()");

        // should add prod config into creating the URL later
        var masterScriptURL = updater.scriptRootURL + "MasterScript.js";

        var resp = updater.net.urlGet(masterScriptURL);

        updater.console.println("  eval MasterScript");
		var newScriptObj = eval( resp.Content );
        updater.console.println("  eval MasterScript doone");
		if ( newScriptObj == null ) {
          updater.console.println("  something wrong while eval resp.Content.  throws!");
          throw "badData";
          }
        updater.MasterScript = newScriptObj;
        
        updater.console.println("  saving MasterScript");
        // can null be used for cachedDate?
        updater.store.perUser[cachedMSName] = { cachedDate : null,
                                                scriptObj : newScriptObj };

        // save user store
        try {
          updater.store.save();
          }
        catch (e) {
          updater.console.println("Exception in trying to updater.store.save(): " + e);
          throw new Error("SaveStateException");
          }
        
        gotMasterScript = true;
        }
      }

    updater.bootStrapped = gotMasterScript;
    
    updater.console.println("===== bootstrap.js done =====");
    }
  catch (e) {
    updater.console.println("  Exception during bootstrap - " + e);
    }
}
else {
  updater.console.println("===== bootstrap.js already loaded =====");
}

return updater.bootStrapped;
}
