Subversion Repositories Applications.papyrus

Rev

Blame | Last modification | View Log | RSS feed

/*
        Copyright (c) 2004-2007, The Dojo Foundation
        All Rights Reserved.

        Licensed under the Academic Free License version 2.1 or above OR the
        modified BSD license. For more information on Dojo licensing, see:

                http://dojotoolkit.org/book/dojo-book-0-9/introduction/licensing
*/

/*
        This is a compiled version of Dojo, built for deployment and not for
        development. To get an editable version, please visit:

                http://dojotoolkit.org

        for documentation and information on getting the source.
*/

if(!dojo._hasResource["dojox.storage.Provider"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code.
dojo._hasResource["dojox.storage.Provider"] = true;
dojo.provide("dojox.storage.Provider");

dojo.declare("dojox.storage.Provider", null, {
        // summary: A singleton for working with dojox.storage.
        // description:
        //              dojox.storage exposes the current available storage provider on this
        //              platform. It gives you methods such as dojox.storage.put(),
        //              dojox.storage.get(), etc.
        //              
        //              For more details on dojox.storage, see the primary documentation
        //              page at
        //                      http://manual.dojotoolkit.org/storage.html
        //              
        //              Note for storage provider developers who are creating subclasses-
        //              This is the base class for all storage providers Specific kinds of
        //              Storage Providers should subclass this and implement these methods.
        //              You should avoid initialization in storage provider subclass's
        //              constructor; instead, perform initialization in your initialize()
        //              method. 
        constructor: function(){
        },
        
        // SUCCESS: String
        //      Flag that indicates a put() call to a 
        //      storage provider was succesful.
        SUCCESS: "success",
        
        // FAILED: String
        //      Flag that indicates a put() call to 
        //      a storage provider failed.
        FAILED: "failed",
        
        // PENDING: String
        //      Flag that indicates a put() call to a 
        //      storage provider is pending user approval.
        PENDING: "pending",
        
        // SIZE_NOT_AVAILABLE: String
        //      Returned by getMaximumSize() if this storage provider can not determine
        //      the maximum amount of data it can support. 
        SIZE_NOT_AVAILABLE: "Size not available",
        
        // SIZE_NO_LIMIT: String
        //      Returned by getMaximumSize() if this storage provider has no theoretical
        //      limit on the amount of data it can store. 
        SIZE_NO_LIMIT: "No size limit",

        // DEFAULT_NAMESPACE: String
        //      The namespace for all storage operations. This is useful if several
        //      applications want access to the storage system from the same domain but
        //      want different storage silos. 
        DEFAULT_NAMESPACE: "default",
        
        // onHideSettingsUI: Function
        //      If a function is assigned to this property, then when the settings
        //      provider's UI is closed this function is called. Useful, for example,
        //      if the user has just cleared out all storage for this provider using
        //      the settings UI, and you want to update your UI.
        onHideSettingsUI: null,

        initialize: function(){
                // summary: 
                //              Allows this storage provider to initialize itself. This is
                //              called after the page has finished loading, so you can not do
                //              document.writes(). Storage Provider subclasses should initialize
                //              themselves inside of here rather than in their function
                //              constructor.
                console.warn("dojox.storage.initialize not implemented");
        },
        
        isAvailable: function(){ /*Boolean*/
                // summary: 
                //              Returns whether this storage provider is available on this
                //              platform. 
                console.warn("dojox.storage.isAvailable not implemented");
        },

        put: function(  /*string*/ key,
                                        /*object*/ value, 
                                        /*function*/ resultsHandler,
                                        /*string?*/ namespace){
                // summary:
                //              Puts a key and value into this storage system.
                // description:
                //              Example-
                //                      var resultsHandler = function(status, key, message){
                //                        alert("status="+status+", key="+key+", message="+message);
                //                      };
                //                      dojox.storage.put("test", "hello world", resultsHandler);
                //      
                //              Important note: if you are using Dojo Storage in conjunction with
                //              Dojo Offline, then you don't need to provide
                //              a resultsHandler; this is because for Dojo Offline we 
                //              use Google Gears to persist data, which has unlimited data
                //              once the user has given permission. If you are using Dojo
                //              Storage apart from Dojo Offline, then under the covers hidden
                //              Flash might be used, which is both asychronous and which might
                //              get denied; in this case you must provide a resultsHandler.
                // key:
                //              A string key to use when retrieving this value in the future.
                // value:
                //              A value to store; this can be any JavaScript type.
                // resultsHandler:
                //              A callback function that will receive three arguments. The
                //              first argument is one of three values: dojox.storage.SUCCESS,
                //              dojox.storage.FAILED, or dojox.storage.PENDING; these values
                //              determine how the put request went. In some storage systems
                //              users can deny a storage request, resulting in a
                //              dojox.storage.FAILED, while in other storage systems a storage
                //              request must wait for user approval, resulting in a
                //              dojox.storage.PENDING status until the request is either
                //              approved or denied, resulting in another call back with
                //              dojox.storage.SUCCESS. 
                //              The second argument in the call back is the key name that was being stored.
                //              The third argument in the call back is an optional message that
                //              details possible error messages that might have occurred during
                //              the storage process.
                //      namespace:
                //              Optional string namespace that this value will be placed into;
                //              if left off, the value will be placed into dojox.storage.DEFAULT_NAMESPACE
                
                console.warn("dojox.storage.put not implemented");
        },

        get: function(/*string*/ key, /*string?*/ namespace){ /*Object*/
                // summary:
                //              Gets the value with the given key. Returns null if this key is
                //              not in the storage system.
                // key:
                //              A string key to get the value of.
                //      namespace:
                //              Optional string namespace that this value will be retrieved from;
                //              if left off, the value will be retrieved from dojox.storage.DEFAULT_NAMESPACE
                // return: Returns any JavaScript object type; null if the key is not present
                console.warn("dojox.storage.get not implemented");
        },

        hasKey: function(/*string*/ key, /*string?*/ namespace){ /*Boolean*/
                // summary: Determines whether the storage has the given key. 
                return (this.get(key) != null);
        },

        getKeys: function(/*string?*/ namespace){ /*Array*/
                // summary: Enumerates all of the available keys in this storage system.
                // return: Array of available keys
                console.warn("dojox.storage.getKeys not implemented");
        },
        
        clear: function(/*string?*/ namespace){
                // summary: 
                //              Completely clears this storage system of all of it's values and
                //              keys. If 'namespace' is provided just clears the keys in that
                //              namespace.
                console.warn("dojox.storage.clear not implemented");
        },
  
        remove: function(/*string*/ key, /*string?*/ namespace){
                // summary: Removes the given key from this storage system.
                console.warn("dojox.storage.remove not implemented");
        },
        
        getNamespaces: function(){ /*string[]*/
                console.warn("dojox.storage.getNamespaces not implemented");
        },

        isPermanent: function(){ /*Boolean*/
                // summary:
                //              Returns whether this storage provider's values are persisted
                //              when this platform is shutdown. 
                console.warn("dojox.storage.isPermanent not implemented");
        },

        getMaximumSize: function(){ /* mixed */
                // summary: The maximum storage allowed by this provider
                // returns: 
                //      Returns the maximum storage size 
                //      supported by this provider, in 
                //      thousands of bytes (i.e., if it 
                //      returns 60 then this means that 60K 
                //      of storage is supported).
                //
                //      If this provider can not determine 
                //      it's maximum size, then 
                //      dojox.storage.SIZE_NOT_AVAILABLE is 
                //      returned; if there is no theoretical
                //      limit on the amount of storage 
                //      this provider can return, then
                //      dojox.storage.SIZE_NO_LIMIT is 
                //      returned
                console.warn("dojox.storage.getMaximumSize not implemented");
        },
                
        putMultiple: function(  /*array*/ keys,
                                                        /*array*/ values, 
                                                        /*function*/ resultsHandler,
                                                        /*string?*/ namespace){
                // summary:
                //              Puts multiple keys and values into this storage system.
                // description:
                //              Example-
                //                      var resultsHandler = function(status, key, message){
                //                        alert("status="+status+", key="+key+", message="+message);
                //                      };
                //                      dojox.storage.put(["test"], ["hello world"], resultsHandler);
                //      
                //              Important note: if you are using Dojo Storage in conjunction with
                //              Dojo Offline, then you don't need to provide
                //              a resultsHandler; this is because for Dojo Offline we 
                //              use Google Gears to persist data, which has unlimited data
                //              once the user has given permission. If you are using Dojo
                //              Storage apart from Dojo Offline, then under the covers hidden
                //              Flash might be used, which is both asychronous and which might
                //              get denied; in this case you must provide a resultsHandler.
                // keys:
                //              An array of string keys to use when retrieving this value in the future,
                //              one per value to be stored
                // values:
                //              An array of values to store; this can be any JavaScript type, though the
                //              performance of plain strings is considerably better
                // resultsHandler:
                //              A callback function that will receive three arguments. The
                //              first argument is one of three values: dojox.storage.SUCCESS,
                //              dojox.storage.FAILED, or dojox.storage.PENDING; these values
                //              determine how the put request went. In some storage systems
                //              users can deny a storage request, resulting in a
                //              dojox.storage.FAILED, while in other storage systems a storage
                //              request must wait for user approval, resulting in a
                //              dojox.storage.PENDING status until the request is either
                //              approved or denied, resulting in another call back with
                //              dojox.storage.SUCCESS. 
                //              The second argument in the call back is the key name that was being stored.
                //              The third argument in the call back is an optional message that
                //              details possible error messages that might have occurred during
                //              the storage process.
                //      namespace:
                //              Optional string namespace that this value will be placed into;
                //              if left off, the value will be placed into dojox.storage.DEFAULT_NAMESPACE
                
                console.warn("dojox.storage.putMultiple not implemented");
                //      JAC: We could implement a 'default' puMultiple here by just doing each put individually
        },

        getMultiple: function(/*array*/ keys, /*string?*/ namespace){ /*Object*/
                // summary:
                //              Gets the valuse corresponding to each of the given keys. 
                //              Returns a null array element for each given key that is
                //              not in the storage system.
                // keys:
                //              An array of string keys to get the value of.
                //      namespace:
                //              Optional string namespace that this value will be retrieved from;
                //              if left off, the value will be retrieved from dojox.storage.DEFAULT_NAMESPACE
                // return: Returns any JavaScript object type; null if the key is not present

                console.warn("dojox.storage.getMultiple not implemented");
                //      JAC: We could implement a 'default' getMultiple here by just doing each get individually
        },

        removeMultiple: function(/*array*/ keys, /*string?*/ namespace) {
                // summary: Removes the given keys from this storage system.

                //      JAC: We could implement a 'default' removeMultiple here by just doing each remove individually
                console.warn("dojox.storage.remove not implemented");
        },
        
        isValidKeyArray: function( keys) {
                if(keys === null || typeof keys === "undefined" || ! keys instanceof Array){
                        return false;
                }
                
                //      JAC: This could be optimized by running the key validity test directly over a joined string
                for(var k=0;k<keys.length;k++){
                        if(!this.isValidKey(keys[k])){
                                return false;
                        }
                }
                return true;
        },

        hasSettingsUI: function(){ /*Boolean*/
                // summary: Determines whether this provider has a settings UI.
                return false;
        },

        showSettingsUI: function(){
                // summary: If this provider has a settings UI, determined
                // by calling hasSettingsUI(), it is shown. 
                console.warn("dojox.storage.showSettingsUI not implemented");
        },

        hideSettingsUI: function(){
                // summary: If this provider has a settings UI, hides it.
                console.warn("dojox.storage.hideSettingsUI not implemented");
        },
        
        isValidKey: function(/*string*/ keyName){ /*Boolean*/
                // summary:
                //              Subclasses can call this to ensure that the key given is valid
                //              in a consistent way across different storage providers. We use
                //              the lowest common denominator for key values allowed: only
                //              letters, numbers, and underscores are allowed. No spaces. 
                if((keyName == null)||(typeof keyName == "undefined")){
                        return false;
                }
                        
                return /^[0-9A-Za-z_]*$/.test(keyName);
        },
        
        getResourceList: function(){ /* Array[] */
                // summary:
                //      Returns a list of URLs that this
                //      storage provider might depend on.
                // description:
                //      This method returns a list of URLs that this
                //      storage provider depends on to do its work.
                //      This list is used by the Dojo Offline Toolkit
                //      to cache these resources to ensure the machinery
                //      used by this storage provider is available offline.
                //      What is returned is an array of URLs.
                
                return [];
        }
});

}

if(!dojo._hasResource["dojox.storage.manager"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code.
dojo._hasResource["dojox.storage.manager"] = true;
dojo.provide("dojox.storage.manager");
//dojo.require("dojo.AdapterRegistry");
// FIXME: refactor this to use an AdapterRegistry

dojox.storage.manager = new function(){
        // summary: A singleton class in charge of the dojox.storage system
        // description:
        //              Initializes the storage systems and figures out the best available 
        //              storage options on this platform.       
        
        // currentProvider: Object
        //      The storage provider that was automagically chosen to do storage
        //      on this platform, such as dojox.storage.FlashStorageProvider.
        this.currentProvider = null;
        
        // available: Boolean
        //      Whether storage of some kind is available.
        this.available = false;
        
        this._initialized = false;
        
        this._providers = [];
        this._onLoadListeners = [];
        
        this.initialize = function(){
                // summary: 
                //              Initializes the storage system and autodetects the best storage
                //              provider we can provide on this platform
                this.autodetect();
        };
        
        this.register = function(/*string*/ name, /*Object*/ instance){
                // summary:
                //              Registers the existence of a new storage provider; used by
                //              subclasses to inform the manager of their existence. The
                //              storage manager will select storage providers based on 
                //              their ordering, so the order in which you call this method
                //              matters. 
                // name:
                //              The full class name of this provider, such as
                //              "dojox.storage.FlashStorageProvider".
                // instance:
                //              An instance of this provider, which we will use to call
                //              isAvailable() on. 
                this._providers[this._providers.length] = instance; //FIXME: push?
                this._providers[name] = instance; // FIXME: this._providers is an array, not a hash
        };
        
        this.setProvider = function(storageClass){
                // summary:
                //              Instructs the storageManager to use the given storage class for
                //              all storage requests.
                // description:
                //              Example-
                //                      dojox.storage.setProvider(
                //                              dojox.storage.IEStorageProvider)
        
        };
        
        this.autodetect = function(){
                // summary:
                //              Autodetects the best possible persistent storage provider
                //              available on this platform. 
                
                //console.debug("dojox.storage.manager.autodetect");
                
                if(this._initialized){ // already finished
                        //console.debug("dojox.storage.manager already initialized; returning");
                        return;
                }

                // a flag to force the storage manager to use a particular 
                // storage provider type, such as 
                // djConfig = {forceStorageProvider: "dojox.storage.WhatWGStorageProvider"};
                var forceProvider = djConfig["forceStorageProvider"]||false;

                // go through each provider, seeing if it can be used
                var providerToUse;
                //FIXME: use dojo.some
                for(var i = 0; i < this._providers.length; i++){
                        providerToUse = this._providers[i];
                        if(forceProvider == providerToUse.declaredClass){
                                // still call isAvailable for this provider, since this helps some
                                // providers internally figure out if they are available
                                // FIXME: This should be refactored since it is non-intuitive
                                // that isAvailable() would initialize some state
                                providerToUse.isAvailable();
                                break;
                        }else if(providerToUse.isAvailable()){
                                break;
                        }
                }
                
                if(!providerToUse){ // no provider available
                        this._initialized = true;
                        this.available = false;
                        this.currentProvider = null;
                        console.warn("No storage provider found for this platform");
                        this.loaded();
                        return;
                }
                        
                // create this provider and mix in it's properties
                // so that developers can do dojox.storage.put rather
                // than dojox.storage.currentProvider.put, for example
                this.currentProvider = providerToUse;
                dojo.mixin(dojox.storage, this.currentProvider);
                
                // have the provider initialize itself
                dojox.storage.initialize();
                
                this._initialized = true;
                this.available = true;
        };
        
        this.isAvailable = function(){ /*Boolean*/
                // summary: Returns whether any storage options are available.
                return this.available;
        };
        
        this.addOnLoad = function(func){ /* void */
                // summary:
                //              Adds an onload listener to know when Dojo Offline can be used.
                // description:
                //              Adds a listener to know when Dojo Offline can be used. This
                //              ensures that the Dojo Offline framework is loaded and that the
                //              local dojox.storage system is ready to be used. This method is
                //              useful if you don't want to have a dependency on Dojo Events
                //              when using dojox.storage.
                // func: Function
                //              A function to call when Dojo Offline is ready to go
                this._onLoadListeners.push(func);
                
                if(this.isInitialized()){
                        this._fireLoaded();
                }
        };
        
        this.removeOnLoad = function(func){ /* void */
                // summary: Removes the given onLoad listener
                for(var i = 0; i < this._onLoadListeners.length; i++){
                        if(func == this._onLoadListeners[i]){
                                this._onLoadListeners = this._onLoadListeners.splice(i, 1);
                                break;
                        }
                }
        };
        
        this.isInitialized = function(){ /*Boolean*/
                // summary:
                //              Returns whether the storage system is initialized and ready to
                //              be used. 

                // FIXME: This should REALLY not be in here, but it fixes a tricky
                // Flash timing bug
                if(this.currentProvider != null
                        && this.currentProvider.declaredClass == "dojox.storage.FlashStorageProvider" 
                        && dojox.flash.ready == false){
                        return false;
                }else{
                        return this._initialized;
                }
        };

        this.supportsProvider = function(/*string*/ storageClass){ /* Boolean */
                // summary: Determines if this platform supports the given storage provider.
                // description:
                //              Example-
                //                      dojox.storage.manager.supportsProvider(
                //                              "dojox.storage.InternetExplorerStorageProvider");

                // construct this class dynamically
                try{
                        // dynamically call the given providers class level isAvailable()
                        // method
                        var provider = eval("new " + storageClass + "()");
                        var results = provider.isAvailable();
                        if(!results){ return false; }
                        return results;
                }catch(e){
                        return false;
                }
        };

        this.getProvider = function(){ /* Object */
                // summary: Gets the current provider
                return this.currentProvider;
        };
        
        this.loaded = function(){
                // summary:
                //              The storage provider should call this method when it is loaded
                //              and ready to be used. Clients who will use the provider will
                //              connect to this method to know when they can use the storage
                //              system. You can either use dojo.connect to connect to this
                //              function, or can use dojox.storage.manager.addOnLoad() to add
                //              a listener that does not depend on the dojo.event package.
                // description:
                //              Example 1-
                //                      if(dojox.storage.manager.isInitialized() == false){ 
                //                              dojo.connect(dojox.storage.manager, "loaded", TestStorage, "initialize");
                //                      }else{
                //                              dojo.connect(dojo, "loaded", TestStorage, "initialize");
                //                      }
                //              Example 2-
                //                      dojox.storage.manager.addOnLoad(someFunction);


                // FIXME: we should just provide a Deferred for this. That way you
                // don't care when this happens or has happened. Deferreds are in Base
                this._fireLoaded();
        };
        
        this._fireLoaded = function(){
                //console.debug("dojox.storage.manager._fireLoaded");
                
                dojo.forEach(this._onLoadListeners, function(i){ 
                        try{ 
                                i(); 
                        }catch(e){ console.debug(e); } 
                });
        };
        
        this.getResourceList = function(){
                // summary:
                //              Returns a list of whatever resources are necessary for storage
                //              providers to work. 
                // description:
                //              This will return all files needed by all storage providers for
                //              this particular environment type. For example, if we are in the
                //              browser environment, then this will return the hidden SWF files
                //              needed by the FlashStorageProvider, even if we don't need them
                //              for the particular browser we are working within. This is meant
                //              to faciliate Dojo Offline, which must retrieve all resources we
                //              need offline into the offline cache -- we retrieve everything
                //              needed, in case another browser that requires different storage
                //              mechanisms hits the local offline cache. For example, if we
                //              were to sync against Dojo Offline on Firefox 2, then we would
                //              not grab the FlashStorageProvider resources needed for Safari.
                var results = [];
                dojo.forEach(dojox.storage.manager._providers, function(currentProvider){
                        results = results.concat(currentProvider.getResourceList());
                });
                
                return results;
        }
};

}

if(!dojo._hasResource["dojox._sql._crypto"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code.
dojo._hasResource["dojox._sql._crypto"] = true;
// Taken from http://www.movable-type.co.uk/scripts/aes.html by
// Chris Veness (CLA signed); adapted for Dojo and Google Gears Worker Pool
// by Brad Neuberg, bkn3@columbia.edu

dojo.provide("dojox._sql._crypto");

dojo.mixin(dojox._sql._crypto,{
        // _POOL_SIZE:
        //      Size of worker pool to create to help with crypto
        _POOL_SIZE: 100,
        
        encrypt: function(plaintext, password, callback){
                // summary:
                //      Use Corrected Block TEA to encrypt plaintext using password
                //      (note plaintext & password must be strings not string objects).
                //      Results will be returned to the 'callback' asychronously.       
                this._initWorkerPool();
        
                var msg ={plaintext: plaintext, password: password};
                msg = dojo.toJson(msg);
                msg = "encr:" + String(msg);
        
                this._assignWork(msg, callback);
        },

        decrypt: function(ciphertext, password, callback){
                // summary:
                //      Use Corrected Block TEA to decrypt ciphertext using password
                //      (note ciphertext & password must be strings not string objects).
                //      Results will be returned to the 'callback' asychronously.
                this._initWorkerPool();
        
                var msg ={ciphertext: ciphertext, password: password};
                msg = dojo.toJson(msg);
                msg = "decr:" + String(msg);
        
                this._assignWork(msg, callback);
        },
        
        _initWorkerPool: function(){
                // bugs in Google Gears prevents us from dynamically creating
                // and destroying workers as we need them -- the worker
                // pool functionality stops working after a number of crypto
                // cycles (probably related to a memory leak in Google Gears).
                // this is too bad, since it results in much simpler code.
        
                // instead, we have to create a pool of workers and reuse them. we
                // keep a stack of 'unemployed' Worker IDs that are currently not working.
                // if a work request comes in, we pop off the 'unemployed' stack
                // and put them to work, storing them in an 'employed' hashtable,
                // keyed by their Worker ID with the value being the callback function
                // that wants the result. when an employed worker is done, we get
                // a message in our 'manager' which adds this worker back to the 
                // unemployed stack and routes the result to the callback that
                // wanted it. if all the workers were employed in the past but
                // more work needed to be done (i.e. it's a tight labor pool ;) 
                // then the work messages are pushed onto
                // a 'handleMessage' queue as an object tuple{msg: msg, callback: callback}
        
                if(!this._manager){
                        try{
                                this._manager = google.gears.factory.create("beta.workerpool", "1.0");
                                this._unemployed = [];
                                this._employed ={};
                                this._handleMessage = [];
                        
                                var self = this;
                                this._manager.onmessage = function(msg, sender){
                                        // get the callback necessary to serve this result
                                        var callback = self._employed["_" + sender];
                                
                                        // make this worker unemployed
                                        self._employed["_" + sender] = undefined;
                                        self._unemployed.push("_" + sender);
                                
                                        // see if we need to assign new work
                                        // that was queued up needing to be done
                                        if(self._handleMessage.length){
                                                var handleMe = self._handleMessage.shift();
                                                self._assignWork(handleMe.msg, handleMe.callback);
                                        }
                                
                                        // return results
                                        callback(msg);
                                }
                                
                                var workerInit = "function _workerInit(){"
                                                                        + "gearsWorkerPool.onmessage = "
                                                                                + String(this._workerHandler)
                                                                        + ";"
                                                                + "}";
                        
                                var code = workerInit + " _workerInit();";
        
                                // create our worker pool
                                for(var i = 0; i < this._POOL_SIZE; i++){
                                        this._unemployed.push("_" + this._manager.createWorker(code));
                                }
                        }catch(exp){
                                throw exp.message||exp;
                        }
                }
        },

        _assignWork: function(msg, callback){
                // can we immediately assign this work?
                if(!this._handleMessage.length && this._unemployed.length){
                        // get an unemployed worker
                        var workerID = this._unemployed.shift().substring(1); // remove _
                
                        // list this worker as employed
                        this._employed["_" + workerID] = callback;
                
                        // do the worke
                        this._manager.sendMessage(msg, workerID);
                }else{
                        // we have to queue it up
                        this._handleMessage ={msg: msg, callback: callback};
                }
        },

        _workerHandler: function(msg, sender){
                
                /* Begin AES Implementation */
                
                /* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -  */
                
                // Sbox is pre-computed multiplicative inverse in GF(2^8) used in SubBytes and KeyExpansion [§5.1.1]
                var Sbox =      [0x63,0x7c,0x77,0x7b,0xf2,0x6b,0x6f,0xc5,0x30,0x01,0x67,0x2b,0xfe,0xd7,0xab,0x76,
                                         0xca,0x82,0xc9,0x7d,0xfa,0x59,0x47,0xf0,0xad,0xd4,0xa2,0xaf,0x9c,0xa4,0x72,0xc0,
                                         0xb7,0xfd,0x93,0x26,0x36,0x3f,0xf7,0xcc,0x34,0xa5,0xe5,0xf1,0x71,0xd8,0x31,0x15,
                                         0x04,0xc7,0x23,0xc3,0x18,0x96,0x05,0x9a,0x07,0x12,0x80,0xe2,0xeb,0x27,0xb2,0x75,
                                         0x09,0x83,0x2c,0x1a,0x1b,0x6e,0x5a,0xa0,0x52,0x3b,0xd6,0xb3,0x29,0xe3,0x2f,0x84,
                                         0x53,0xd1,0x00,0xed,0x20,0xfc,0xb1,0x5b,0x6a,0xcb,0xbe,0x39,0x4a,0x4c,0x58,0xcf,
                                         0xd0,0xef,0xaa,0xfb,0x43,0x4d,0x33,0x85,0x45,0xf9,0x02,0x7f,0x50,0x3c,0x9f,0xa8,
                                         0x51,0xa3,0x40,0x8f,0x92,0x9d,0x38,0xf5,0xbc,0xb6,0xda,0x21,0x10,0xff,0xf3,0xd2,
                                         0xcd,0x0c,0x13,0xec,0x5f,0x97,0x44,0x17,0xc4,0xa7,0x7e,0x3d,0x64,0x5d,0x19,0x73,
                                         0x60,0x81,0x4f,0xdc,0x22,0x2a,0x90,0x88,0x46,0xee,0xb8,0x14,0xde,0x5e,0x0b,0xdb,
                                         0xe0,0x32,0x3a,0x0a,0x49,0x06,0x24,0x5c,0xc2,0xd3,0xac,0x62,0x91,0x95,0xe4,0x79,
                                         0xe7,0xc8,0x37,0x6d,0x8d,0xd5,0x4e,0xa9,0x6c,0x56,0xf4,0xea,0x65,0x7a,0xae,0x08,
                                         0xba,0x78,0x25,0x2e,0x1c,0xa6,0xb4,0xc6,0xe8,0xdd,0x74,0x1f,0x4b,0xbd,0x8b,0x8a,
                                         0x70,0x3e,0xb5,0x66,0x48,0x03,0xf6,0x0e,0x61,0x35,0x57,0xb9,0x86,0xc1,0x1d,0x9e,
                                         0xe1,0xf8,0x98,0x11,0x69,0xd9,0x8e,0x94,0x9b,0x1e,0x87,0xe9,0xce,0x55,0x28,0xdf,
                                         0x8c,0xa1,0x89,0x0d,0xbf,0xe6,0x42,0x68,0x41,0x99,0x2d,0x0f,0xb0,0x54,0xbb,0x16];

                // Rcon is Round Constant used for the Key Expansion [1st col is 2^(r-1) in GF(2^8)] [§5.2]
                var Rcon = [ [0x00, 0x00, 0x00, 0x00],
                                         [0x01, 0x00, 0x00, 0x00],
                                         [0x02, 0x00, 0x00, 0x00],
                                         [0x04, 0x00, 0x00, 0x00],
                                         [0x08, 0x00, 0x00, 0x00],
                                         [0x10, 0x00, 0x00, 0x00],
                                         [0x20, 0x00, 0x00, 0x00],
                                         [0x40, 0x00, 0x00, 0x00],
                                         [0x80, 0x00, 0x00, 0x00],
                                         [0x1b, 0x00, 0x00, 0x00],
                                         [0x36, 0x00, 0x00, 0x00] ]; 

                /*
                 * AES Cipher function: encrypt 'input' with Rijndael algorithm
                 *
                 *       takes   byte-array 'input' (16 bytes)
                 *                       2D byte-array key schedule 'w' (Nr+1 x Nb bytes)
                 *
                 *       applies Nr rounds (10/12/14) using key schedule w for 'add round key' stage
                 *
                 *       returns byte-array encrypted value (16 bytes)
                 */
                function Cipher(input, w) {        // main Cipher function [§5.1]
                  var Nb = 4;                           // block size (in words): no of columns in state (fixed at 4 for AES)
                  var Nr = w.length/Nb - 1; // no of rounds: 10/12/14 for 128/192/256-bit keys

                  var state = [[],[],[],[]];  // initialise 4xNb byte-array 'state' with input [§3.4]
                  for (var i=0; i<4*Nb; i++) state[i%4][Math.floor(i/4)] = input[i];

                  state = AddRoundKey(state, w, 0, Nb);

                  for (var round=1; round<Nr; round++) {
                        state = SubBytes(state, Nb);
                        state = ShiftRows(state, Nb);
                        state = MixColumns(state, Nb);
                        state = AddRoundKey(state, w, round, Nb);
                  }

                  state = SubBytes(state, Nb);
                  state = ShiftRows(state, Nb);
                  state = AddRoundKey(state, w, Nr, Nb);

                  var output = new Array(4*Nb);  // convert state to 1-d array before returning [§3.4]
                  for (var i=0; i<4*Nb; i++) output[i] = state[i%4][Math.floor(i/4)];
                  return output;
                }


                function SubBytes(s, Nb) {        // apply SBox to state S [§5.1.1]
                  for (var r=0; r<4; r++) {
                        for (var c=0; c<Nb; c++) s[r][c] = Sbox[s[r][c]];
                  }
                  return s;
                }


                function ShiftRows(s, Nb) {        // shift row r of state S left by r bytes [§5.1.2]
                  var t = new Array(4);
                  for (var r=1; r<4; r++) {
                        for (var c=0; c<4; c++) t[c] = s[r][(c+r)%Nb];  // shift into temp copy
                        for (var c=0; c<4; c++) s[r][c] = t[c];                 // and copy back
                  }                      // note that this will work for Nb=4,5,6, but not 7,8 (always 4 for AES):
                  return s;      // see fp.gladman.plus.com/cryptography_technology/rijndael/aes.spec.311.pdf 
                }


                function MixColumns(s, Nb) {   // combine bytes of each col of state S [§5.1.3]
                  for (var c=0; c<4; c++) {
                        var a = new Array(4);  // 'a' is a copy of the current column from 's'
                        var b = new Array(4);  // 'b' is a•{02} in GF(2^8)
                        for (var i=0; i<4; i++) {
                          a[i] = s[i][c];
                          b[i] = s[i][c]&0x80 ? s[i][c]<<1 ^ 0x011b : s[i][c]<<1;
                        }
                        // a[n] ^ b[n] is a•{03} in GF(2^8)
                        s[0][c] = b[0] ^ a[1] ^ b[1] ^ a[2] ^ a[3]; // 2*a0 + 3*a1 + a2 + a3
                        s[1][c] = a[0] ^ b[1] ^ a[2] ^ b[2] ^ a[3]; // a0 * 2*a1 + 3*a2 + a3
                        s[2][c] = a[0] ^ a[1] ^ b[2] ^ a[3] ^ b[3]; // a0 + a1 + 2*a2 + 3*a3
                        s[3][c] = a[0] ^ b[0] ^ a[1] ^ a[2] ^ b[3]; // 3*a0 + a1 + a2 + 2*a3
                  }
                  return s;
                }


                function AddRoundKey(state, w, rnd, Nb) {  // xor Round Key into state S [§5.1.4]
                  for (var r=0; r<4; r++) {
                        for (var c=0; c<Nb; c++) state[r][c] ^= w[rnd*4+c][r];
                  }
                  return state;
                }


                function KeyExpansion(key) {  // generate Key Schedule (byte-array Nr+1 x Nb) from Key [§5.2]
                  var Nb = 4;                    // block size (in words): no of columns in state (fixed at 4 for AES)
                  var Nk = key.length/4  // key length (in words): 4/6/8 for 128/192/256-bit keys
                  var Nr = Nk + 6;               // no of rounds: 10/12/14 for 128/192/256-bit keys

                  var w = new Array(Nb*(Nr+1));
                  var temp = new Array(4);

                  for (var i=0; i<Nk; i++) {
                        var r = [key[4*i], key[4*i+1], key[4*i+2], key[4*i+3]];
                        w[i] = r;
                  }

                  for (var i=Nk; i<(Nb*(Nr+1)); i++) {
                        w[i] = new Array(4);
                        for (var t=0; t<4; t++) temp[t] = w[i-1][t];
                        if (i % Nk == 0) {
                          temp = SubWord(RotWord(temp));
                          for (var t=0; t<4; t++) temp[t] ^= Rcon[i/Nk][t];
                        } else if (Nk > 6 && i%Nk == 4) {
                          temp = SubWord(temp);
                        }
                        for (var t=0; t<4; t++) w[i][t] = w[i-Nk][t] ^ temp[t];
                  }

                  return w;
                }

                function SubWord(w) {    // apply SBox to 4-byte word w
                  for (var i=0; i<4; i++) w[i] = Sbox[w[i]];
                  return w;
                }

                function RotWord(w) {    // rotate 4-byte word w left by one byte
                  w[4] = w[0];
                  for (var i=0; i<4; i++) w[i] = w[i+1];
                  return w;
                }

                /* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -  */

                /* 
                 * Use AES to encrypt 'plaintext' with 'password' using 'nBits' key, in 'Counter' mode of operation
                 *                                                       - see http://csrc.nist.gov/publications/nistpubs/800-38a/sp800-38a.pdf
                 *       for each block
                 *       - outputblock = cipher(counter, key)
                 *       - cipherblock = plaintext xor outputblock
                 */
                function AESEncryptCtr(plaintext, password, nBits) {
                  if (!(nBits==128 || nBits==192 || nBits==256)) return '';      // standard allows 128/192/256 bit keys
        
                  // for this example script, generate the key by applying Cipher to 1st 16/24/32 chars of password; 
                  // for real-world applications, a more secure approach would be to hash the password e.g. with SHA-1
                  var nBytes = nBits/8;  // no bytes in key
                  var pwBytes = new Array(nBytes);
                  for (var i=0; i<nBytes; i++) pwBytes[i] = password.charCodeAt(i) & 0xff;

                  var key = Cipher(pwBytes, KeyExpansion(pwBytes));

                  key = key.concat(key.slice(0, nBytes-16));  // key is now 16/24/32 bytes long

                  // initialise counter block (NIST SP800-38A §B.2): millisecond time-stamp for nonce in 1st 8 bytes,
                  // block counter in 2nd 8 bytes
                  var blockSize = 16;  // block size fixed at 16 bytes / 128 bits (Nb=4) for AES
                  var counterBlock = new Array(blockSize);      // block size fixed at 16 bytes / 128 bits (Nb=4) for AES
                  var nonce = (new Date()).getTime();  // milliseconds since 1-Jan-1970

                  // encode nonce in two stages to cater for JavaScript 32-bit limit on bitwise ops
                  for (var i=0; i<4; i++) counterBlock[i] = (nonce >>> i*8) & 0xff;
                  for (var i=0; i<4; i++) counterBlock[i+4] = (nonce/0x100000000 >>> i*8) & 0xff; 

                  // generate key schedule - an expansion of the key into distinct Key Rounds for each round
                  var keySchedule = KeyExpansion(key);

                  var blockCount = Math.ceil(plaintext.length/blockSize);
                  var ciphertext = new Array(blockCount);  // ciphertext as array of strings
  
                  for (var b=0; b<blockCount; b++) {
                        // set counter (block #) in last 8 bytes of counter block (leaving nonce in 1st 8 bytes)
                        // again done in two stages for 32-bit ops
                        for (var c=0; c<4; c++) counterBlock[15-c] = (b >>> c*8) & 0xff;
                        for (var c=0; c<4; c++) counterBlock[15-c-4] = (b/0x100000000 >>> c*8)

                        var cipherCntr = Cipher(counterBlock, keySchedule);      // -- encrypt counter block --
        
                        // calculate length of final block:
                        var blockLength = b<blockCount-1 ? blockSize : (plaintext.length-1)%blockSize+1;

                        var ct = '';
                        for (var i=0; i<blockLength; i++) {      // -- xor plaintext with ciphered counter byte-by-byte --
                          var plaintextByte = plaintext.charCodeAt(b*blockSize+i);
                          var cipherByte = plaintextByte ^ cipherCntr[i];
                          ct += String.fromCharCode(cipherByte);
                        }
                        // ct is now ciphertext for this block

                        ciphertext[b] = escCtrlChars(ct);  // escape troublesome characters in ciphertext
                  }

                  // convert the nonce to a string to go on the front of the ciphertext
                  var ctrTxt = '';
                  for (var i=0; i<8; i++) ctrTxt += String.fromCharCode(counterBlock[i]);
                  ctrTxt = escCtrlChars(ctrTxt);

                  // use '-' to separate blocks, use Array.join to concatenate arrays of strings for efficiency
                  return ctrTxt + '-' + ciphertext.join('-');
                }


                /* 
                 * Use AES to decrypt 'ciphertext' with 'password' using 'nBits' key, in Counter mode of operation
                 *
                 *       for each block
                 *       - outputblock = cipher(counter, key)
                 *       - cipherblock = plaintext xor outputblock
                 */
                function AESDecryptCtr(ciphertext, password, nBits) {
                  if (!(nBits==128 || nBits==192 || nBits==256)) return '';      // standard allows 128/192/256 bit keys

                  var nBytes = nBits/8;  // no bytes in key
                  var pwBytes = new Array(nBytes);
                  for (var i=0; i<nBytes; i++) pwBytes[i] = password.charCodeAt(i) & 0xff;
                  var pwKeySchedule = KeyExpansion(pwBytes);
                  var key = Cipher(pwBytes, pwKeySchedule);
                  key = key.concat(key.slice(0, nBytes-16));  // key is now 16/24/32 bytes long

                  var keySchedule = KeyExpansion(key);

                  ciphertext = ciphertext.split('-');  // split ciphertext into array of block-length strings 

                  // recover nonce from 1st element of ciphertext
                  var blockSize = 16;  // block size fixed at 16 bytes / 128 bits (Nb=4) for AES
                  var counterBlock = new Array(blockSize);
                  var ctrTxt = unescCtrlChars(ciphertext[0]);
                  for (var i=0; i<8; i++) counterBlock[i] = ctrTxt.charCodeAt(i);

                  var plaintext = new Array(ciphertext.length-1);

                  for (var b=1; b<ciphertext.length; b++) {
                        // set counter (block #) in last 8 bytes of counter block (leaving nonce in 1st 8 bytes)
                        for (var c=0; c<4; c++) counterBlock[15-c] = ((b-1) >>> c*8) & 0xff;
                        for (var c=0; c<4; c++) counterBlock[15-c-4] = ((b/0x100000000-1) >>> c*8) & 0xff;

                        var cipherCntr = Cipher(counterBlock, keySchedule);      // encrypt counter block

                        ciphertext[b] = unescCtrlChars(ciphertext[b]);

                        var pt = '';
                        for (var i=0; i<ciphertext[b].length; i++) {
                          // -- xor plaintext with ciphered counter byte-by-byte --
                          var ciphertextByte = ciphertext[b].charCodeAt(i);
                          var plaintextByte = ciphertextByte ^ cipherCntr[i];
                          pt += String.fromCharCode(plaintextByte);
                        }
                        // pt is now plaintext for this block

                        plaintext[b-1] = pt;  // b-1 'cos no initial nonce block in plaintext
                  }

                  return plaintext.join('');
                }

                /* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -  */

                function escCtrlChars(str) {  // escape control chars which might cause problems handling ciphertext
                  return str.replace(/[\0\t\n\v\f\r\xa0!-]/g, function(c) { return '!' + c.charCodeAt(0) + '!'; });
                }  // \xa0 to cater for bug in Firefox; include '-' to leave it free for use as a block marker

                function unescCtrlChars(str) {  // unescape potentially problematic control characters
                  return str.replace(/!\d\d?\d?!/g, function(c) { return String.fromCharCode(c.slice(1,-1)); });
                }

                /* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -  */
                
                function encrypt(plaintext, password){
                        return AESEncryptCtr(plaintext, password, 256);
                }

                function decrypt(ciphertext, password){ 
                        return AESDecryptCtr(ciphertext, password, 256);
                }
                
                /* End AES Implementation */
                
                var cmd = msg.substr(0,4);
                var arg = msg.substr(5);
                if(cmd == "encr"){
                        arg = eval("(" + arg + ")");
                        var plaintext = arg.plaintext;
                        var password = arg.password;
                        var results = encrypt(plaintext, password);
                        gearsWorkerPool.sendMessage(String(results), sender);
                }else if(cmd == "decr"){
                        arg = eval("(" + arg + ")");
                        var ciphertext = arg.ciphertext;
                        var password = arg.password;
                        var results = decrypt(ciphertext, password);
                        gearsWorkerPool.sendMessage(String(results), sender);
                }
        }
});

}

if(!dojo._hasResource["dojox._sql.common"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code.
dojo._hasResource["dojox._sql.common"] = true;
dojo.provide("dojox._sql.common");



// summary:
//      Executes a SQL expression.
// description:
//      There are four ways to call this:
//      1) Straight SQL: dojox.sql("SELECT * FROM FOOBAR");
//      2) SQL with parameters: dojox.sql("INSERT INTO FOOBAR VALUES (?)", someParam)
//      3) Encrypting particular values: 
//                      dojox.sql("INSERT INTO FOOBAR VALUES (ENCRYPT(?))", someParam, "somePassword", callback)
//      4) Decrypting particular values:
//                      dojox.sql("SELECT DECRYPT(SOMECOL1), DECRYPT(SOMECOL2) FROM
//                                      FOOBAR WHERE SOMECOL3 = ?", someParam,
//                                      "somePassword", callback)
//
//      For encryption and decryption the last two values should be the the password for
//      encryption/decryption, and the callback function that gets the result set.
//
//      Note: We only support ENCRYPT(?) statements, and
//      and DECRYPT(*) statements for now -- you can not have a literal string
//      inside of these, such as ENCRYPT('foobar')
//
//      Note: If you have multiple columns to encrypt and decrypt, you can use the following
//      convenience form to not have to type ENCRYPT(?)/DECRYPT(*) many times:
//
//      dojox.sql("INSERT INTO FOOBAR VALUES (ENCRYPT(?, ?, ?))", 
//                                      someParam1, someParam2, someParam3, 
//                                      "somePassword", callback)
//
//      dojox.sql("SELECT DECRYPT(SOMECOL1, SOMECOL2) FROM
//                                      FOOBAR WHERE SOMECOL3 = ?", someParam,
//                                      "somePassword", callback)
dojox.sql = new Function("return dojox.sql._exec(arguments);");

dojo.mixin(dojox.sql, {
        dbName: null,
        
        // summary:
        //      If true, then we print out any SQL that is executed
        //      to the debug window
        debug: (dojo.exists("dojox.sql.debug")?dojox.sql.debug:false),

        open: function(dbName){
                if(this._dbOpen && (!dbName || dbName == this.dbName)){
                        return;
                }
                
                if(!this.dbName){
                        this.dbName = "dot_store_" 
                                + window.location.href.replace(/[^0-9A-Za-z_]/g, "_");
                        //console.debug("Using Google Gears database " + this.dbName);
                }
                
                if(!dbName){
                        dbName = this.dbName;
                }
                
                try{
                        this._initDb();
                        this.db.open(dbName);
                        this._dbOpen = true;
                }catch(exp){
                        throw exp.message||exp;
                }
        },

        close: function(dbName){
                // on Internet Explorer, Google Gears throws an exception
                // "Object not a collection", when we try to close the
                // database -- just don't close it on this platform
                // since we are running into a Gears bug; the Gears team
                // said it's ok to not close a database connection
                if(dojo.isIE){ return; }
                
                if(!this._dbOpen && (!dbName || dbName == this.dbName)){
                        return;
                }
                
                if(!dbName){
                        dbName = this.dbName;
                }
                
                try{
                        this.db.close(dbName);
                        this._dbOpen = false;
                }catch(exp){
                        throw exp.message||exp;
                }
        },
        
        _exec: function(params){
                try{    
                        // get the Gears Database object
                        this._initDb();
                
                        // see if we need to open the db; if programmer
                        // manually called dojox.sql.open() let them handle
                        // it; otherwise we open and close automatically on
                        // each SQL execution
                        if(!this._dbOpen){
                                this.open();
                                this._autoClose = true;
                        }
                
                        // determine our parameters
                        var sql = null;
                        var callback = null;
                        var password = null;

                        var args = dojo._toArray(params);

                        sql = args.splice(0, 1)[0];

                        // does this SQL statement use the ENCRYPT or DECRYPT
                        // keywords? if so, extract our callback and crypto
                        // password
                        if(this._needsEncrypt(sql) || this._needsDecrypt(sql)){
                                callback = args.splice(args.length - 1, 1)[0];
                                password = args.splice(args.length - 1, 1)[0];
                        }

                        // 'args' now just has the SQL parameters

                        // print out debug SQL output if the developer wants that
                        if(this.debug){
                                this._printDebugSQL(sql, args);
                        }

                        // handle SQL that needs encryption/decryption differently
                        // do we have an ENCRYPT SQL statement? if so, handle that first
                        if(this._needsEncrypt(sql)){
                                var crypto = new dojox.sql._SQLCrypto("encrypt", sql, 
                                                                                                        password, args, 
                                                                                                        callback);
                                return; // encrypted results will arrive asynchronously
                        }else if(this._needsDecrypt(sql)){ // otherwise we have a DECRYPT statement
                                var crypto = new dojox.sql._SQLCrypto("decrypt", sql, 
                                                                                                        password, args, 
                                                                                                        callback);
                                return; // decrypted results will arrive asynchronously
                        }

                        // execute the SQL and get the results
                        var rs = this.db.execute(sql, args);
                        
                        // Gears ResultSet object's are ugly -- normalize
                        // these into something JavaScript programmers know
                        // how to work with, basically an array of 
                        // JavaScript objects where each property name is
                        // simply the field name for a column of data
                        rs = this._normalizeResults(rs);
                
                        if(this._autoClose){
                                this.close();
                        }
                
                        return rs;
                }catch(exp){
                        exp = exp.message||exp;
                        
                        console.debug("SQL Exception: " + exp);
                        
                        if(this._autoClose){
                                try{ 
                                        this.close(); 
                                }catch(e){
                                        console.debug("Error closing database: " 
                                                                        + e.message||e);
                                }
                        }
                
                        throw exp;
                }
        },

        _initDb: function(){
                if(!this.db){
                        try{
                                this.db = google.gears.factory.create('beta.database', '1.0');
                        }catch(exp){
                                dojo.setObject("google.gears.denied", true);
                                dojox.off.onFrameworkEvent("coreOperationFailed");
                                throw "Google Gears must be allowed to run";
                        }
                }
        },

        _printDebugSQL: function(sql, args){
                var msg = "dojox.sql(\"" + sql + "\"";
                for(var i = 0; i < args.length; i++){
                        if(typeof args[i] == "string"){
                                msg += ", \"" + args[i] + "\"";
                        }else{
                                msg += ", " + args[i];
                        }
                }
                msg += ")";
        
                console.debug(msg);
        },

        _normalizeResults: function(rs){
                var results = [];
                if(!rs){ return []; }
        
                while(rs.isValidRow()){
                        var row = {};
                
                        for(var i = 0; i < rs.fieldCount(); i++){
                                var fieldName = rs.fieldName(i);
                                var fieldValue = rs.field(i);
                                row[fieldName] = fieldValue;
                        }
                
                        results.push(row);
                
                        rs.next();
                }
        
                rs.close();
                
                return results;
        },

        _needsEncrypt: function(sql){
                return /encrypt\([^\)]*\)/i.test(sql);
        },

        _needsDecrypt: function(sql){
                return /decrypt\([^\)]*\)/i.test(sql);
        }
});

// summary:
//      A private class encapsulating any cryptography that must be done
//      on a SQL statement. We instantiate this class and have it hold
//      it's state so that we can potentially have several encryption
//      operations happening at the same time by different SQL statements.
dojo.declare("dojox.sql._SQLCrypto", null, {
        constructor: function(action, sql, password, args, callback){
                if(action == "encrypt"){
                        this._execEncryptSQL(sql, password, args, callback);
                }else{
                        this._execDecryptSQL(sql, password, args, callback);
                }               
        }, 
        
        _execEncryptSQL: function(sql, password, args, callback){
                // strip the ENCRYPT/DECRYPT keywords from the SQL
                var strippedSQL = this._stripCryptoSQL(sql);
        
                // determine what arguments need encryption
                var encryptColumns = this._flagEncryptedArgs(sql, args);
        
                // asynchronously encrypt each argument that needs it
                var self = this;
                this._encrypt(strippedSQL, password, args, encryptColumns, function(finalArgs){
                        // execute the SQL
                        var error = false;
                        var resultSet = [];
                        var exp = null;
                        try{
                                resultSet = dojox.sql.db.execute(strippedSQL, finalArgs);
                        }catch(execError){
                                error = true;
                                exp = execError.message||execError;
                        }
                
                        // was there an error during SQL execution?
                        if(exp != null){
                                if(dojox.sql._autoClose){
                                        try{ dojox.sql.close(); }catch(e){}
                                }
                        
                                callback(null, true, exp.toString());
                                return;
                        }
                
                        // normalize SQL results into a JavaScript object 
                        // we can work with
                        resultSet = dojox.sql._normalizeResults(resultSet);
                
                        if(dojox.sql._autoClose){
                                dojox.sql.close();
                        }
                                
                        // are any decryptions necessary on the result set?
                        if(dojox.sql._needsDecrypt(sql)){
                                // determine which of the result set columns needs decryption
                                var needsDecrypt = self._determineDecryptedColumns(sql);

                                // now decrypt columns asynchronously
                                // decrypt columns that need it
                                self._decrypt(resultSet, needsDecrypt, password, function(finalResultSet){
                                        callback(finalResultSet, false, null);
                                });
                        }else{
                                callback(resultSet, false, null);
                        }
                });
        },

        _execDecryptSQL: function(sql, password, args, callback){
                // strip the ENCRYPT/DECRYPT keywords from the SQL
                var strippedSQL = this._stripCryptoSQL(sql);
        
                // determine which columns needs decryption; this either
                // returns the value *, which means all result set columns will
                // be decrypted, or it will return the column names that need
                // decryption set on a hashtable so we can quickly test a given
                // column name; the key is the column name that needs
                // decryption and the value is 'true' (i.e. needsDecrypt["someColumn"] 
                // would return 'true' if it needs decryption, and would be 'undefined'
                // or false otherwise)
                var needsDecrypt = this._determineDecryptedColumns(sql);
        
                // execute the SQL
                var error = false;
                var resultSet = [];
                var exp = null;
                try{
                        resultSet = dojox.sql.db.execute(strippedSQL, args);
                }catch(execError){
                        error = true;
                        exp = execError.message||execError;
                }
        
                // was there an error during SQL execution?
                if(exp != null){
                        if(dojox.sql._autoClose){
                                try{ dojox.sql.close(); }catch(e){}
                        }
                
                        callback(resultSet, true, exp.toString());
                        return;
                }
        
                // normalize SQL results into a JavaScript object 
                // we can work with
                resultSet = dojox.sql._normalizeResults(resultSet);
        
                if(dojox.sql._autoClose){
                        dojox.sql.close();
                }
        
                // decrypt columns that need it
                this._decrypt(resultSet, needsDecrypt, password, function(finalResultSet){
                        callback(finalResultSet, false, null);
                });
        },

        _encrypt: function(sql, password, args, encryptColumns, callback){
                //console.debug("_encrypt, sql="+sql+", password="+password+", encryptColumns="+encryptColumns+", args="+args);
        
                this._totalCrypto = 0;
                this._finishedCrypto = 0;
                this._finishedSpawningCrypto = false;
                this._finalArgs = args;
        
                for(var i = 0; i < args.length; i++){
                        if(encryptColumns[i]){
                                // we have an encrypt() keyword -- get just the value inside
                                // the encrypt() parantheses -- for now this must be a ?
                                var sqlParam = args[i];
                                var paramIndex = i;
                        
                                // update the total number of encryptions we know must be done asynchronously
                                this._totalCrypto++;
                        
                                // FIXME: This currently uses DES as a proof-of-concept since the
                                // DES code used is quite fast and was easy to work with. Modify dojox.sql
                                // to be able to specify a different encryption provider through a 
                                // a SQL-like syntax, such as dojox.sql("SET ENCRYPTION BLOWFISH"),
                                // and modify the dojox.crypto.Blowfish code to be able to work using
                                // a Google Gears Worker Pool
                        
                                // do the actual encryption now, asychronously on a Gears worker thread
                                dojox._sql._crypto.encrypt(sqlParam, password, dojo.hitch(this, function(results){
                                        // set the new encrypted value
                                        this._finalArgs[paramIndex] = results;
                                        this._finishedCrypto++;
                                        // are we done with all encryption?
                                        if(this._finishedCrypto >= this._totalCrypto
                                                && this._finishedSpawningCrypto){
                                                callback(this._finalArgs);
                                        }
                                }));
                        }
                }
        
                this._finishedSpawningCrypto = true;
        },

        _decrypt: function(resultSet, needsDecrypt, password, callback){
                //console.debug("decrypt, resultSet="+resultSet+", needsDecrypt="+needsDecrypt+", password="+password);
                
                this._totalCrypto = 0;
                this._finishedCrypto = 0;
                this._finishedSpawningCrypto = false;
                this._finalResultSet = resultSet;
        
                for(var i = 0; i < resultSet.length; i++){
                        var row = resultSet[i];
                
                        // go through each of the column names in row,
                        // seeing if they need decryption
                        for(var columnName in row){
                                if(needsDecrypt == "*" || needsDecrypt[columnName]){
                                        this._totalCrypto++;
                                        var columnValue = row[columnName];
                                
                                        // forming a closure here can cause issues, with values not cleanly
                                        // saved on Firefox/Mac OS X for some of the values above that
                                        // are needed in the callback below; call a subroutine that will form 
                                        // a closure inside of itself instead
                                        this._decryptSingleColumn(columnName, columnValue, password, i,
                                                                                                function(finalResultSet){
                                                callback(finalResultSet);
                                        });
                                }
                        }
                }
        
                this._finishedSpawningCrypto = true;
        },

        _stripCryptoSQL: function(sql){
                // replace all DECRYPT(*) occurrences with a *
                sql = sql.replace(/DECRYPT\(\*\)/ig, "*");
        
                // match any ENCRYPT(?, ?, ?, etc) occurrences,
                // then replace with just the question marks in the
                // middle
                var matches = sql.match(/ENCRYPT\([^\)]*\)/ig);
                if(matches != null){
                        for(var i = 0; i < matches.length; i++){
                                var encryptStatement = matches[i];
                                var encryptValue = encryptStatement.match(/ENCRYPT\(([^\)]*)\)/i)[1];
                                sql = sql.replace(encryptStatement, encryptValue);
                        }
                }
        
                // match any DECRYPT(COL1, COL2, etc) occurrences,
                // then replace with just the column names
                // in the middle
                matches = sql.match(/DECRYPT\([^\)]*\)/ig);
                if(matches != null){
                        for(var i = 0; i < matches.length; i++){
                                var decryptStatement = matches[i];
                                var decryptValue = decryptStatement.match(/DECRYPT\(([^\)]*)\)/i)[1];
                                sql = sql.replace(decryptStatement, decryptValue);
                        }
                }
        
                return sql;
        },

        _flagEncryptedArgs: function(sql, args){
                // capture literal strings that have question marks in them,
                // and also capture question marks that stand alone
                var tester = new RegExp(/([\"][^\"]*\?[^\"]*[\"])|([\'][^\']*\?[^\']*[\'])|(\?)/ig);
                var matches;
                var currentParam = 0;
                var results = [];
                while((matches = tester.exec(sql)) != null){
                        var currentMatch = RegExp.lastMatch+"";

                        // are we a literal string? then ignore it
                        if(/^[\"\']/.test(currentMatch)){
                                continue;
                        }

                        // do we have an encrypt keyword to our left?
                        var needsEncrypt = false;
                        if(/ENCRYPT\([^\)]*$/i.test(RegExp.leftContext)){
                                needsEncrypt = true;
                        }

                        // set the encrypted flag
                        results[currentParam] = needsEncrypt;

                        currentParam++;
                }
        
                return results;
        },

        _determineDecryptedColumns: function(sql){
                var results = {};

                if(/DECRYPT\(\*\)/i.test(sql)){
                        results = "*";
                }else{
                        var tester = /DECRYPT\((?:\s*\w*\s*\,?)*\)/ig;
                        var matches;
                        while(matches = tester.exec(sql)){
                                var lastMatch = new String(RegExp.lastMatch);
                                var columnNames = lastMatch.replace(/DECRYPT\(/i, "");
                                columnNames = columnNames.replace(/\)/, "");
                                columnNames = columnNames.split(/\s*,\s*/);
                                dojo.forEach(columnNames, function(column){
                                        if(/\s*\w* AS (\w*)/i.test(column)){
                                                column = column.match(/\s*\w* AS (\w*)/i)[1];
                                        }
                                        results[column] = true;
                                });
                        }
                }

                return results;
        },

        _decryptSingleColumn: function(columnName, columnValue, password, currentRowIndex,
                                                                                        callback){
                //console.debug("decryptSingleColumn, columnName="+columnName+", columnValue="+columnValue+", currentRowIndex="+currentRowIndex)
                dojox._sql._crypto.decrypt(columnValue, password, dojo.hitch(this, function(results){
                        // set the new decrypted value
                        this._finalResultSet[currentRowIndex][columnName] = results;
                        this._finishedCrypto++;
                        
                        // are we done with all encryption?
                        if(this._finishedCrypto >= this._totalCrypto
                                && this._finishedSpawningCrypto){
                                //console.debug("done with all decrypts");
                                callback(this._finalResultSet);
                        }
                }));
        }
});

}

if(!dojo._hasResource["dojox.sql"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code.
dojo._hasResource["dojox.sql"] = true;

dojo.provide("dojox.sql");

}

if(!dojo._hasResource["dojox.storage.GearsStorageProvider"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code.
dojo._hasResource["dojox.storage.GearsStorageProvider"] = true;
dojo.provide("dojox.storage.GearsStorageProvider");




if(dojo.isGears){
        
        (function(){
                // make sure we don't define the gears provider if we're not gears
                // enabled
                
                dojo.declare("dojox.storage.GearsStorageProvider", dojox.storage.Provider, {
                        // summary:
                        //              Storage provider that uses the features of Google Gears
                        //              to store data (it is saved into the local SQL database
                        //              provided by Gears, using dojox.sql)
                        // description: 
                        //              
                        //
                        //              You can disable this storage provider with the following djConfig
                        //              variable:
                        //              var djConfig = { disableGearsStorage: true };
                        //              
                        //              Authors of this storage provider-       
                        //                      Brad Neuberg, bkn3@columbia.edu 
                        constructor: function(){
                        },
                        // instance methods and properties
                        TABLE_NAME: "__DOJO_STORAGE",
                        initialized: false,
                        
                        _available: null,
                        
                        initialize: function(){
                                //console.debug("dojox.storage.GearsStorageProvider.initialize");
                                if(djConfig["disableGearsStorage"] == true){
                                        return;
                                }
                                
                                // partition our storage data so that multiple apps
                                // on the same host won't collide
                                this.TABLE_NAME = "__DOJO_STORAGE";
                                
                                // create the table that holds our data
                                try{
                                        dojox.sql("CREATE TABLE IF NOT EXISTS " + this.TABLE_NAME + "( "
                                                                + " namespace TEXT, "
                                                                + " key TEXT, "
                                                                + " value TEXT "
                                                                + ")"
                                                        );
                                        dojox.sql("CREATE UNIQUE INDEX IF NOT EXISTS namespace_key_index" 
                                                                + " ON " + this.TABLE_NAME
                                                                + " (namespace, key)");
                                }catch(e){
                                        console.debug("dojox.storage.GearsStorageProvider.initialize:", e);
                                        
                                        this.initialized = false; // we were unable to initialize
                                        dojox.storage.manager.loaded();
                                        return;
                                }
                                
                                // indicate that this storage provider is now loaded
                                this.initialized = true;
                                dojox.storage.manager.loaded(); 
                        },
                        
                        isAvailable: function(){
                                // is Google Gears available and defined?
                                return this._available = dojo.isGears;
                        },

                        put: function(key, value, resultsHandler, namespace){
                                if(this.isValidKey(key) == false){
                                        throw new Error("Invalid key given: " + key);
                                }
                                namespace = namespace||this.DEFAULT_NAMESPACE;
                                
                                // serialize the value;
                                // handle strings differently so they have better performance
                                if(dojo.isString(value)){
                                        value = "string:" + value;
                                }else{
                                        value = dojo.toJson(value);
                                }
                                
                                // try to store the value       
                                try{
                                        dojox.sql("DELETE FROM " + this.TABLE_NAME
                                                                + " WHERE namespace = ? AND key = ?",
                                                                namespace, key);
                                        dojox.sql("INSERT INTO " + this.TABLE_NAME
                                                                + " VALUES (?, ?, ?)",
                                                                namespace, key, value);
                                }catch(e){
                                        // indicate we failed
                                        console.debug("dojox.storage.GearsStorageProvider.put:", e);
                                        resultsHandler(this.FAILED, key, e.toString());
                                        return;
                                }
                                
                                if(resultsHandler){
                                        resultsHandler(dojox.storage.SUCCESS, key, null);
                                }
                        },

                        get: function(key, namespace){
                                if(this.isValidKey(key) == false){
                                        throw new Error("Invalid key given: " + key);
                                }
                                namespace = namespace||this.DEFAULT_NAMESPACE;
                                
                                // try to find this key in the database
                                var results = dojox.sql("SELECT * FROM " + this.TABLE_NAME
                                                                                        + " WHERE namespace = ? AND "
                                                                                        + " key = ?",
                                                                                        namespace, key);
                                if(!results.length){
                                        return null;
                                }else{
                                        results = results[0].value;
                                }
                                
                                // destringify the content back into a 
                                // real JavaScript object;
                                // handle strings differently so they have better performance
                                if(dojo.isString(results) && (/^string:/.test(results))){
                                        results = results.substring("string:".length);
                                }else{
                                        results = dojo.fromJson(results);
                                }
                                
                                return results;
                        },
                        
                        getNamespaces: function(){
                                var results = [ dojox.storage.DEFAULT_NAMESPACE ];
                                
                                var rs = dojox.sql("SELECT namespace FROM " + this.TABLE_NAME
                                                                        + " DESC GROUP BY namespace");
                                for(var i = 0; i < rs.length; i++){
                                        if(rs[i].namespace != dojox.storage.DEFAULT_NAMESPACE){
                                                results.push(rs[i].namespace);
                                        }
                                }
                                
                                return results;
                        },

                        getKeys: function(namespace){
                                namespace = namespace||this.DEFAULT_NAMESPACE;
                                if(this.isValidKey(namespace) == false){
                                        throw new Error("Invalid namespace given: " + namespace);
                                }
                                
                                var rs = dojox.sql("SELECT key FROM " + this.TABLE_NAME
                                                                        + " WHERE namespace = ?",
                                                                        namespace);
                                
                                var results = [];
                                for(var i = 0; i < rs.length; i++){
                                        results.push(rs[i].key);
                                }
                                
                                return results;
                        },

                        clear: function(namespace){
                                if(this.isValidKey(namespace) == false){
                                        throw new Error("Invalid namespace given: " + namespace);
                                }
                                namespace = namespace||this.DEFAULT_NAMESPACE;
                                
                                dojox.sql("DELETE FROM " + this.TABLE_NAME 
                                                        + " WHERE namespace = ?",
                                                        namespace);
                        },
                        
                        remove: function(key, namespace){
                                namespace = namespace||this.DEFAULT_NAMESPACE;
                                
                                dojox.sql("DELETE FROM " + this.TABLE_NAME 
                                                        + " WHERE namespace = ? AND"
                                                        + " key = ?",
                                                        namespace,
                                                        key);
                        },
                        
                        putMultiple: function(keys, values, resultsHandler, namespace) {
                                if(this.isValidKeyArray(keys) === false 
                                                || ! values instanceof Array 
                                                || keys.length != values.length){
                                        throw new Error("Invalid arguments: keys = [" 
                                                                        + keys + "], values = [" + values + "]");
                                }
                                
                                if(namespace == null || typeof namespace == "undefined"){
                                        namespace = dojox.storage.DEFAULT_NAMESPACE;            
                                }
        
                                if(this.isValidKey(namespace) == false){
                                        throw new Error("Invalid namespace given: " + namespace);
                                }
        
                                this._statusHandler = resultsHandler;

                                // try to store the value       
                                try{
                                        dojox.sql.open();
                                        dojox.sql.db.execute("BEGIN TRANSACTION");
                                        var _stmt = "REPLACE INTO " + this.TABLE_NAME + " VALUES (?, ?, ?)";
                                        for(var i=0;i<keys.length;i++) {
                                                // serialize the value;
                                                // handle strings differently so they have better performance
                                                var value = values[i];
                                                if(dojo.isString(value)){
                                                        value = "string:" + value;
                                                }else{
                                                        value = dojo.toJson(value);
                                                }
                                
                                                dojox.sql.db.execute( _stmt,
                                                        [namespace, keys[i], value]);
                                        }
                                        dojox.sql.db.execute("COMMIT TRANSACTION");
                                        dojox.sql.close();
                                }catch(e){
                                        // indicate we failed
                                        console.debug("dojox.storage.GearsStorageProvider.putMultiple:", e);
                                        if(resultsHandler){
                                                resultsHandler(this.FAILED, keys, e.toString());
                                        }
                                        return;
                                }
                                
                                if(resultsHandler){
                                        resultsHandler(dojox.storage.SUCCESS, key, null);
                                }
                        },

                        getMultiple: function(keys, namespace){
                                //      TODO: Maybe use SELECT IN instead

                                if(this.isValidKeyArray(keys) === false){
                                        throw new ("Invalid key array given: " + keys);
                                }
                                
                                if(namespace == null || typeof namespace == "undefined"){
                                        namespace = dojox.storage.DEFAULT_NAMESPACE;            
                                }
                                
                                if(this.isValidKey(namespace) == false){
                                        throw new Error("Invalid namespace given: " + namespace);
                                }
                
                                var _stmt = "SELECT * FROM " + this.TABLE_NAME  + 
                                        " WHERE namespace = ? AND "     + " key = ?";
                                
                                var results = [];
                                for(var i=0;i<keys.length;i++){
                                        var result = dojox.sql( _stmt, namespace, keys[i]);
                                                
                                        if( ! result.length){
                                                results[i] = null;
                                        }else{
                                                result = result[0].value;
                                                
                                                // destringify the content back into a 
                                                // real JavaScript object;
                                                // handle strings differently so they have better performance
                                                if(dojo.isString(result) && (/^string:/.test(result))){
                                                        results[i] = result.substring("string:".length);
                                                }else{
                                                        results[i] = dojo.fromJson(result);
                                                }
                                        }
                                }
                                
                                return results;
                        },
                        
                        removeMultiple: function(keys, namespace){
                                namespace = namespace||this.DEFAULT_NAMESPACE;
                                
                                dojox.sql.open();
                                dojox.sql.db.execute("BEGIN TRANSACTION");
                                var _stmt = "DELETE FROM " + this.TABLE_NAME + " WHERE namespace = ? AND key = ?";

                                for(var i=0;i<keys.length;i++){
                                        dojox.sql.db.execute( _stmt,
                                                [namespace, keys[i]]);
                                }
                                dojox.sql.db.execute("COMMIT TRANSACTION");
                                dojox.sql.close();
                        },                              
                        
                        isPermanent: function(){ return true; },

                        getMaximumSize: function(){ return this.SIZE_NO_LIMIT; },

                        hasSettingsUI: function(){ return false; },
                        
                        showSettingsUI: function(){
                                throw new Error(this.declaredClass 
                                                                        + " does not support a storage settings user-interface");
                        },
                        
                        hideSettingsUI: function(){
                                throw new Error(this.declaredClass 
                                                                        + " does not support a storage settings user-interface");
                        }
                });

                // register the existence of our storage providers
                dojox.storage.manager.register("dojox.storage.GearsStorageProvider",
                                                                                new dojox.storage.GearsStorageProvider());
        
                dojox.storage.manager.initialize();
        })();
}

}

if(!dojo._hasResource["dojox.storage._common"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code.
dojo._hasResource["dojox.storage._common"] = true;
dojo.provide("dojox.storage._common");





// FIXME: Find way to set isGears from offline.profile.js file; it didn't
// work for me
//dojo.requireIf(!dojo.isGears, "dojox.storage.FlashStorageProvider");
//dojo.requireIf(!dojo.isGears, "dojox.storage.WhatWGStorageProvider");

// now that we are loaded and registered tell the storage manager to
// initialize itself
dojox.storage.manager.initialize();

}

if(!dojo._hasResource["dojox.storage"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code.
dojo._hasResource["dojox.storage"] = true;
dojo.provide("dojox.storage");


}

if(!dojo._hasResource["dojox.off.files"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code.
dojo._hasResource["dojox.off.files"] = true;
dojo.provide("dojox.off.files");

// Author: Brad Neuberg, bkn3@columbia.edu, http://codinginparadise.org

// summary:
//      Helps maintain resources that should be
//      available offline, such as CSS files.
// description:
//      dojox.off.files makes it easy to indicate
//      what resources should be available offline,
//      such as CSS files, JavaScript, HTML, etc.
dojox.off.files = {
        // versionURL: String
        //      An optional file, that if present, records the version
        //      of our bundle of files to make available offline. If this
        //      file is present, and we are not currently debugging,
        //      then we only refresh our offline files if the version has
        //      changed. 
        versionURL: "version.js",
        
        // listOfURLs: Array
        //      For advanced usage; most developers can ignore this.
        //      Our list of URLs that will be cached and made available
        //      offline.
        listOfURLs: [],
        
        // refreshing: boolean
        //      For advanced usage; most developers can ignore this.
        //      Whether we are currently in the middle
        //      of refreshing our list of offline files.
        refreshing: false,

        _cancelID: null,
        
        _error: false,
        _errorMessages: [],
        _currentFileIndex: 0,
        _store: null,
        _doSlurp: false,
        
        slurp: function(){
                // summary:
                //      Autoscans the page to find all resources to
                //      cache. This includes scripts, images, CSS, and hyperlinks
                //      to pages that are in the same scheme/port/host as this
                //      page. We also scan the embedded CSS of any stylesheets
                //      to find @import statements and url()'s.
                //  You should call this method from the top-level, outside of
                //      any functions and before the page loads:
                //
                //      <script>
                //              
                //              
                //              
                //              
                //
                //              // configure how we should work offline
                //
                //              // set our application name
                //              dojox.off.ui.appName = "Moxie";
                //
                //              // automatically "slurp" the page and
                //              // capture the resources we need offline
                //              dojox.off.files.slurp();
                //
                //              // tell Dojo Offline we are ready for it to initialize itself now
                //              // that we have finished configuring it for our application
                //              dojox.off.initialize();
                //      </script>
                //
                //      Note that inline styles on elements are not handled (i.e.
                //      if you somehow have an inline style that uses a URL);
                //      object and embed tags are not scanned since their format
                //      differs based on type; and elements created by JavaScript
                //      after page load are not found. For these you must manually
                //      add them with a dojox.off.files.cache() method call.
                
                // just schedule the slurp once the page is loaded and
                // Dojo Offline is ready to slurp; dojox.off will call
                // our _slurp() method before indicating it is finished
                // loading
                this._doSlurp = true;
        },
        
        cache: function(urlOrList){ /* void */
                // summary:
                //              Caches a file or list of files to be available offline. This
                //              can either be a full URL, such as http://foobar.com/index.html,
                //              or a relative URL, such as ../index.html. This URL is not
                //              actually cached until dojox.off.sync.synchronize() is called.
                // urlOrList: String or Array[]
                //              A URL of a file to cache or an Array of Strings of files to
                //              cache
                
                //console.debug("dojox.off.files.cache, urlOrList="+urlOrList);
                
                if(dojo.isString(urlOrList)){
                        var url = this._trimAnchor(urlOrList+"");
                        if(!this.isAvailable(url)){ 
                                this.listOfURLs.push(url); 
                        }
                }else if(urlOrList instanceof dojo._Url){
                        var url = this._trimAnchor(urlOrList.uri);
                        if(!this.isAvailable(url)){ 
                                this.listOfURLs.push(url); 
                        }
                }else{
                        dojo.forEach(urlOrList, function(url){
                                url = this._trimAnchor(url);
                                if(!this.isAvailable(url)){ 
                                        this.listOfURLs.push(url); 
                                }
                        }, this);
                }
        },
        
        printURLs: function(){
                // summary:
                //      A helper function that will dump and print out
                //      all of the URLs that are cached for offline
                //      availability. This can help with debugging if you
                //      are trying to make sure that all of your URLs are
                //      available offline
                console.debug("The following URLs are cached for offline use:");
                dojo.forEach(this.listOfURLs, function(i){
                        console.debug(i);
                });     
        },
        
        remove: function(url){ /* void */
                // summary:
                //              Removes a URL from the list of files to cache.
                // description:
                //              Removes a URL from the list of URLs to cache. Note that this
                //              does not actually remove the file from the offline cache;
                //              instead, it just prevents us from refreshing this file at a
                //              later time, so that it will naturally time out and be removed
                //              from the offline cache
                // url: String
                //              The URL to remove
                for(var i = 0; i < this.listOfURLs.length; i++){
                        if(this.listOfURLs[i] == url){
                                this.listOfURLs = this.listOfURLs.splice(i, 1);
                                break;
                        }
                }
        },
        
        isAvailable: function(url){ /* boolean */
                // summary:
                //              Determines whether the given resource is available offline.
                // url: String
                //      The URL to check
                for(var i = 0; i < this.listOfURLs.length; i++){
                        if(this.listOfURLs[i] == url){
                                return true;
                        }
                }
                
                return false;
        },
        
        refresh: function(callback){ /* void */
                //console.debug("dojox.off.files.refresh");
                // summary:
                //      For advanced usage; most developers can ignore this.
                //      Refreshes our list of offline resources,
                //      making them available offline.
                // callback: Function
                //      A callback that receives two arguments: whether an error
                //      occurred, which is a boolean; and an array of error message strings
                //      with details on errors encountered. If no error occured then message is
                //      empty array with length 0.
                try{
                        if(djConfig.isDebug){
                                this.printURLs();
                        }
                        
                        this.refreshing = true;
                        
                        if(this.versionURL){
                                this._getVersionInfo(function(oldVersion, newVersion, justDebugged){
                                        //console.warn("getVersionInfo, oldVersion="+oldVersion+", newVersion="+newVersion
                                        //                              + ", justDebugged="+justDebugged+", isDebug="+djConfig.isDebug);
                                        if(djConfig.isDebug || !newVersion || justDebugged 
                                                        || !oldVersion || oldVersion != newVersion){
                                                console.warn("Refreshing offline file list");
                                                this._doRefresh(callback, newVersion);
                                        }else{
                                                console.warn("No need to refresh offline file list");
                                                callback(false, []);
                                        }
                                });
                        }else{
                                console.warn("Refreshing offline file list");
                                this._doRefresh(callback);
                        }
                }catch(e){
                        this.refreshing = false;
                       
                        // can't refresh files -- core operation --
                        // fail fast
                        dojox.off.coreOpFailed = true;
                        dojox.off.enabled = false;
                        dojox.off.onFrameworkEvent("coreOperationFailed");
                }
        },
        
        abortRefresh: function(){
                // summary:
                //      For advanced usage; most developers can ignore this.
                //      Aborts and cancels a refresh.
                if(!this.refreshing){
                        return;
                }
                
                this._store.abortCapture(this._cancelID);
                this.refreshing = false;
        },
        
        _slurp: function(){
                if(!this._doSlurp){
                        return;
                }
                
                var handleUrl = dojo.hitch(this, function(url){
                        if(this._sameLocation(url)){
                                this.cache(url);
                        }
                });
                
                handleUrl(window.location.href);
                
                dojo.query("script").forEach(function(i){
                        try{
                                handleUrl(i.getAttribute("src"));
                        }catch(exp){
                                //console.debug("dojox.off.files.slurp 'script' error: " 
                                //                              + exp.message||exp);
                        }
                });
                
                dojo.query("link").forEach(function(i){
                        try{
                                if(!i.getAttribute("rel")
                                        || i.getAttribute("rel").toLowerCase() != "stylesheet"){
                                        return;
                                }
                        
                                handleUrl(i.getAttribute("href"));
                        }catch(exp){
                                //console.debug("dojox.off.files.slurp 'link' error: " 
                                //                              + exp.message||exp);
                        }
                });
                
                dojo.query("img").forEach(function(i){
                        try{
                                handleUrl(i.getAttribute("src"));
                        }catch(exp){
                                //console.debug("dojox.off.files.slurp 'img' error: " 
                                //                              + exp.message||exp);
                        }
                });
                
                dojo.query("a").forEach(function(i){
                        try{
                                handleUrl(i.getAttribute("href"));
                        }catch(exp){
                                //console.debug("dojox.off.files.slurp 'a' error: " 
                                //                              + exp.message||exp);
                        }
                });
                
                // FIXME: handle 'object' and 'embed' tag
                
                // parse our style sheets for inline URLs and imports
                dojo.forEach(document.styleSheets, function(sheet){
                        try{
                                if(sheet.cssRules){ // Firefox
                                        dojo.forEach(sheet.cssRules, function(rule){
                                                var text = rule.cssText;
                                                if(text){
                                                        var matches = text.match(/url\(\s*([^\) ]*)\s*\)/i);
                                                        if(!matches){
                                                                return;
                                                        }
                                                        
                                                        for(var i = 1; i < matches.length; i++){
                                                                handleUrl(matches[i])
                                                        }
                                                }
                                        });
                                }else if(sheet.cssText){ // IE
                                        var matches;
                                        var text = sheet.cssText.toString();
                                        // unfortunately, using RegExp.exec seems to be flakey
                                        // for looping across multiple lines on IE using the
                                        // global flag, so we have to simulate it
                                        var lines = text.split(/\f|\r|\n/);
                                        for(var i = 0; i < lines.length; i++){
                                                matches = lines[i].match(/url\(\s*([^\) ]*)\s*\)/i);
                                                if(matches && matches.length){
                                                        handleUrl(matches[1]);
                                                }
                                        }
                                }
                        }catch(exp){
                                //console.debug("dojox.off.files.slurp stylesheet parse error: " 
                                //                              + exp.message||exp);
                        }
                });
                
                //this.printURLs();
        },
        
        _sameLocation: function(url){
                if(!url){ return false; }
                
                // filter out anchors
                if(url.length && url.charAt(0) == "#"){
                        return false;
                }
                
                // FIXME: dojo._Url should be made public;
                // it's functionality is very useful for
                // parsing URLs correctly, which is hard to
                // do right
                url = new dojo._Url(url);
                
                // totally relative -- ../../someFile.html
                if(!url.scheme && !url.port && !url.host){ 
                        return true;
                }
                
                // scheme relative with port specified -- brad.com:8080
                if(!url.scheme && url.host && url.port
                                && window.location.hostname == url.host
                                && window.location.port == url.port){
                        return true;
                }
                
                // scheme relative with no-port specified -- brad.com
                if(!url.scheme && url.host && !url.port
                        && window.location.hostname == url.host
                        && window.location.port == 80){
                        return true;
                }
                
                // else we have everything
                return  window.location.protocol == (url.scheme + ":")
                                && window.location.hostname == url.host
                                && (window.location.port == url.port || !window.location.port && !url.port);
        },
        
        _trimAnchor: function(url){
                return url.replace(/\#.*$/, "");
        },
        
        _doRefresh: function(callback, newVersion){
                // get our local server
                var localServer;
                try{
                        localServer = google.gears.factory.create("beta.localserver", "1.0");
                }catch(exp){
                        dojo.setObject("google.gears.denied", true);
                        dojox.off.onFrameworkEvent("coreOperationFailed");
                        throw "Google Gears must be allowed to run";
                }
                
                var storeName = "dot_store_" 
                                                        + window.location.href.replace(/[^0-9A-Za-z_]/g, "_");
                        
                // refresh everything by simply removing
                // any older stores
                localServer.removeStore(storeName);
                
                // open/create the resource store
                localServer.openStore(storeName);
                var store = localServer.createStore(storeName);
                this._store = store;

                // add our list of files to capture
                var self = this;
                this._currentFileIndex = 0;
                this._cancelID = store.capture(this.listOfURLs, function(url, success, captureId){
                        //console.debug("store.capture, url="+url+", success="+success);
                        if(!success && self.refreshing){
                                self._cancelID = null;
                                self.refreshing = false;
                                var errorMsgs = [];
                                errorMsgs.push("Unable to capture: " + url);
                                callback(true, errorMsgs);
                                return;
                        }else if(success){
                                self._currentFileIndex++;
                        }
                        
                        if(success && self._currentFileIndex >= self.listOfURLs.length){
                                self._cancelID = null;
                                self.refreshing = false;
                                if(newVersion){
                                        dojox.storage.put("oldVersion", newVersion, null,
                                                                        dojox.off.STORAGE_NAMESPACE);
                                }
                                dojox.storage.put("justDebugged", djConfig.isDebug, null,
                                                                        dojox.off.STORAGE_NAMESPACE);
                                callback(false, []);
                        }
                });
        },
        
        _getVersionInfo: function(callback){
                var justDebugged = dojox.storage.get("justDebugged", 
                                                                        dojox.off.STORAGE_NAMESPACE);
                var oldVersion = dojox.storage.get("oldVersion",
                                                                        dojox.off.STORAGE_NAMESPACE);
                var newVersion = null;
                
                callback = dojo.hitch(this, callback);
                
                dojo.xhrGet({
                                url: this.versionURL + "?browserbust=" + new Date().getTime(),
                                timeout: 5 * 1000,
                                handleAs: "javascript",
                                error: function(err){
                                        //console.warn("dojox.off.files._getVersionInfo, err=",err);
                                        dojox.storage.remove("oldVersion", dojox.off.STORAGE_NAMESPACE);
                                        dojox.storage.remove("justDebugged", dojox.off.STORAGE_NAMESPACE);
                                        callback(oldVersion, newVersion, justDebugged);
                                },
                                load: function(data){
                                        //console.warn("dojox.off.files._getVersionInfo, load=",data);
                                        
                                        // some servers incorrectly return 404's
                                        // as a real page
                                        if(data){
                                                newVersion = data;
                                        }
                                        
                                        callback(oldVersion, newVersion, justDebugged);
                                }
                });
        }
}

}

if(!dojo._hasResource["dojox.off.sync"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code.
dojo._hasResource["dojox.off.sync"] = true;
dojo.provide("dojox.off.sync");





// Author: Brad Neuberg, bkn3@columbia.edu, http://codinginparadise.org

// summary:
//              Exposes syncing functionality to offline applications
dojo.mixin(dojox.off.sync, {
        // isSyncing: boolean
        //              Whether we are in the middle of a syncing session.
        isSyncing: false,
        
        // cancelled: boolean
        //              Whether we were cancelled during our last sync request or not. If
        //              we are cancelled, then successful will be false.
        cancelled: false,
        
        // successful: boolean
        //              Whether the last sync was successful or not.  If false, an error
        //              occurred.
        successful: true,
        
        // details: String[]
        //              Details on the sync. If the sync was successful, this will carry
        //              any conflict or merging messages that might be available; if the
        //              sync was unsuccessful, this will have an error message.  For both
        //              of these, this should be an array of Strings, where each string
        //              carries details on the sync. 
        //      Example: 
        //              dojox.off.sync.details = ["The document 'foobar' had conflicts - yours one",
        //                                              "The document 'hello world' was automatically merged"];
        details: [],
        
        // error: boolean
        //              Whether an error occurred during the syncing process.
        error: false,
        
        // actions: dojox.off.sync.ActionLog
        //              Our ActionLog that we store offline actions into for later
        //              replaying when we go online
        actions: null,
        
        // autoSync: boolean
        //              For advanced usage; most developers can ignore this.
        //              Whether we do automatically sync on page load or when we go online.
        //              If true we do, if false syncing must be manually initiated.
        //              Defaults to true.
        autoSync: true,
        
        // summary:
        //      An event handler that is called during the syncing process with
        //      the state of syncing. It is important that you connect to this
        //      method and respond to certain sync events, especially the 
        //      "download" event.
        // description:
        //      This event handler is called during the syncing process. You can
        //      do a dojo.connect to receive sync feedback:
        //
        //              dojo.connect(dojox.off.sync, "onSync", someFunc);
        //
        //      You will receive one argument, which is the type of the event
        //      and which can have the following values.
        //
        //      The most common two types that you need to care about are "download"
        //      and "finished", especially if you are using the default
        //      Dojo Offline UI widget that does the hard work of informing
        //      the user through the UI about what is occuring during syncing.
        //
        //      If you receive the "download" event, you should make a network call
        //      to retrieve and store your data somehow for offline access. The
        //      "finished" event indicates that syncing is done. An example:
        //      
        //              dojo.connect(dojox.off.sync, "onSync", function(type){
        //                      if(type == "download"){
        //                              // make a network call to download some data
        //                              // for use offline
        //                              dojo.xhrGet({
        //                                      url:            "downloadData.php",
        //                                      handleAs:       "javascript",
        //                                      error:          function(err){
        //                                              dojox.off.sync.finishedDownloading(false, "Can't download data");
        //                                      },
        //                                      load:           function(data){
        //                                              // store our data
        //                                              dojox.storage.put("myData", data);
        //
        //                                              // indicate we are finished downloading
        //                                              dojox.off.sync.finishedDownloading(true);
        //                                      }
        //                              });
        //                      }else if(type == "finished"){
        //                              // update UI somehow to indicate we are finished,
        //                              // such as using the download data to change the 
        //                              // available data
        //                      }
        //              })
        //
        //      Here is the full list of event types if you want to do deep
        //      customization, such as updating your UI to display the progress
        //      of syncing (note that the default Dojo Offline UI widget does
        //      this for you if you choose to pull that in). Most of these
        //      are only appropriate for advanced usage and can be safely
        //      ignored:
        //
        //              * "start"
        //                              syncing has started
        //              * "refreshFiles"
        //                              syncing will begin refreshing
        //                              our offline file cache
        //              * "upload"
        //                              syncing will begin uploading
        //                              any local data changes we have on the client.
        //                              This event is fired before we fire
        //                              the dojox.off.sync.actions.onReplay event for
        //                              each action to replay; use it to completely
        //                              over-ride the replaying behavior and prevent
        //                              it entirely, perhaps rolling your own sync
        //                              protocol if needed.
        //              * "download"
        //                              syncing will begin downloading any new data that is
        //                              needed into persistent storage. Applications are required to
        //                              implement this themselves, storing the required data into
        //                              persistent local storage using Dojo Storage.
        //              * "finished"
        //                              syncing is finished; this
        //                              will be called whether an error ocurred or not; check
        //                              dojox.off.sync.successful and dojox.off.sync.error for sync details
        //              * "cancel"
        //                              Fired when canceling has been initiated; canceling will be
        //                              attempted, followed by the sync event "finished".
        onSync: function(/* String */ type){},
        
        synchronize: function(){ /* void */
                // summary: Starts synchronizing

                //dojo.debug("synchronize");
                if(this.isSyncing || dojox.off.goingOnline || (!dojox.off.isOnline)){
                        return;
                }
        
                this.isSyncing = true;
                this.successful = false;
                this.details = [];
                this.cancelled = false;
                
                this.start();
        },
        
        cancel: function(){ /* void */
                // summary:
                //      Attempts to cancel this sync session
                
                if(!this.isSyncing){ return; }
                
                this.cancelled = true;
                if(dojox.off.files.refreshing){
                        dojox.off.files.abortRefresh();
                }
                
                this.onSync("cancel");
        },
        
        finishedDownloading: function(successful /* boolean? */, 
                                                                        errorMessage /* String? */){
                // summary:
                //              Applications call this method from their
                //              after getting a "download" event in
                //              dojox.off.sync.onSync to signal that
                //              they are finished downloading any data 
                //              that should be available offline
                // successful: boolean?
                //              Whether our downloading was successful or not.
                //              If not present, defaults to true.
                // errorMessage: String?
                //              If unsuccessful, a message explaining why
                if(typeof successful == "undefined"){
                        successful = true;
                }
                
                if(!successful){
                        this.successful = false;
                        this.details.push(errorMessage);
                        this.error = true;
                }
                
                this.finished();
        },
        
        start: function(){ /* void */
                // summary:
                //      For advanced usage; most developers can ignore this.
                //      Called at the start of the syncing process. Advanced
                //      developers can over-ride this method to use their
                //      own sync mechanism to start syncing.
                
                if(this.cancelled){
                        this.finished();
                        return;
                }
                this.onSync("start");
                this.refreshFiles();
        },
        
        refreshFiles: function(){ /* void */
                // summary:
                //      For advanced usage; most developers can ignore this.
                //      Called when we are going to refresh our list
                //      of offline files during syncing. Advanced developers 
                //      can over-ride this method to do some advanced magic related to
                //      refreshing files.
                
                //dojo.debug("refreshFiles");
                if(this.cancelled){
                        this.finished();
                        return;
                }
                
                this.onSync("refreshFiles");
                
                dojox.off.files.refresh(dojo.hitch(this, function(error, errorMessages){
                        if(error){
                                this.error = true;
                                this.successful = false;
                                for(var i = 0; i < errorMessages.length; i++){
                                        this.details.push(errorMessages[i]);
                                }
                                
                                // even if we get an error while syncing files,
                                // keep syncing so we can upload and download
                                // data
                        }
                        
                        this.upload();
                }));
        },
        
        upload: function(){ /* void */
                // summary:
                //      For advanced usage; most developers can ignore this.
                //      Called when syncing wants to upload data. Advanced
                //      developers can over-ride this method to completely
                //      throw away the Action Log and replaying system
                //      and roll their own advanced sync mechanism if needed.
                
                if(this.cancelled){
                        this.finished();
                        return;
                }
                
                this.onSync("upload");
                
                // when we are done uploading start downloading
                dojo.connect(this.actions, "onReplayFinished", this, this.download);
                
                // replay the actions log
                this.actions.replay();
        },
        
        download: function(){ /* void */
                // summary:
                //      For advanced usage; most developers can ignore this.
                //      Called when syncing wants to download data. Advanced
                //      developers can over-ride this method to use their
                //      own sync mechanism.
                
                if(this.cancelled){
                        this.finished();
                        return;
                }
                
                // apps should respond to the "download"
                // event to download their data; when done
                // they must call dojox.off.sync.finishedDownloading()
                this.onSync("download");
        },
        
        finished: function(){ /* void */
                // summary:
                //      For advanced usage; most developers can ignore this.
                //      Called when syncing is finished. Advanced
                //      developers can over-ride this method to clean
                //      up after finishing their own sync
                //      mechanism they might have rolled.
                this.isSyncing = false;
                
                this.successful = (!this.cancelled && !this.error);
                
                this.onSync("finished");
        },
        
        _save: function(callback){
                this.actions._save(function(){
                        callback();
                });
        },
        
        _load: function(callback){
                this.actions._load(function(){
                        callback();
                });
        }
});


// summary:
//              A class that records actions taken by a user when they are offline,
//              suitable for replaying when the network reappears. 
// description:
//              The basic idea behind this method is to record user actions that would
//              normally have to contact a server into an action log when we are
//              offline, so that later when we are online we can simply replay this log
//              in the order user actions happened so that they can be executed against
//              the server, causing synchronization to happen. 
//              
//              When we replay, for each of the actions that were added, we call a 
//              method named onReplay that applications should connect to and 
//              which will be called over and over for each of our actions -- 
//              applications should take the offline action
//              information and use it to talk to a server to have this action
//              actually happen online, 'syncing' themselves with the server. 
//
//              For example, if the action was "update" with the item that was updated, we
//              might call some RESTian server API that exists for updating an item in
//              our application.  The server could either then do sophisticated merging
//              and conflict resolution on the server side, for example, allowing you
//              to pop up a custom merge UI, or could do automatic merging or nothing
//              of the sort. When you are finished with this particular action, your
//              application is then required to call continueReplay() on the actionLog object
//              passed to onReplay() to continue replaying the action log, or haltReplay()
//              with the reason for halting to completely stop the syncing/replaying
//              process.
//
//              For example, imagine that we have a web application that allows us to add
//              contacts. If we are offline, and we update a contact, we would add an action;
//              imagine that the user has to click an Update button after changing the values
//              for a given contact:
//      
//              dojox.off.whenOffline(dojo.byId("updateButton"), "onclick", function(evt){
//                      // get the updated customer values
//                      var customer = getCustomerValues();
//                      
//                      // we are offline -- just record this action
//                      var action = {name: "update", customer: customer};
//                      dojox.off.sync.actions.add(action)
//                      
//                      // persist this customer data into local storage as well
//                      dojox.storage.put(customer.name, customer);
//              })
//
//              Then, when we go back online, the dojox.off.sync.actions.onReplay event
//              will fire over and over, once for each action that was recorded while offline:
//
//              dojo.connect(dojox.off.sync.actions, "onReplay", function(action, actionLog){
//                      // called once for each action we added while offline, in the order
//                      // they were added
//                      if(action.name == "update"){
//                              var customer = action.customer;
//                              
//                              // call some network service to update this customer
//                              dojo.xhrPost({
//                                      url: "updateCustomer.php",
//                                      content: {customer: dojo.toJson(customer)},
//                                      error: function(err){
//                                              actionLog.haltReplay(err);
//                                      },
//                                      load: function(data){
//                                              actionLog.continueReplay();
//                                      }
//                              })
//                      }
//              })
//
//              Note that the actions log is always automatically persisted locally while using it, so
//              that if the user closes the browser or it crashes the actions will safely be stored
//              for later replaying.
dojo.declare("dojox.off.sync.ActionLog", null, {
                // entries: Array
                //              An array of our action entries, where each one is simply a custom
                //              object literal that were passed to add() when this action entry
                //              was added.
                entries: [],
                
                // reasonHalted: String
                //              If we halted, the reason why
                reasonHalted: null,
                
                // isReplaying: boolean
                //              If true, we are in the middle of replaying a command log; if false,
                //              then we are not
                isReplaying: false,
                
                // autoSave: boolean
                //              Whether we automatically save the action log after each call to
                //              add(); defaults to true. For applications that are rapidly adding
                //              many action log entries in a short period of time, it can be
                //              useful to set this to false and simply call save() yourself when
                //              you are ready to persist your command log -- otherwise performance
                //              could be slow as the default action is to attempt to persist the
                //              actions log constantly with calls to add().
                autoSave: true,
                
                add: function(action /* Object */){ /* void */
                        // summary:
                        //      Adds an action to our action log
                        // description:
                        //      This method will add an action to our
                        //      action log, later to be replayed when we
                        //      go from offline to online. 'action'
                        //      will be available when this action is
                        //      replayed and will be passed to onReplay.
                        //
                        //      Example usage:
                        //      
                        //      dojox.off.sync.log.add({actionName: "create", itemType: "document",
                        //                                        {title: "Message", content: "Hello World"}});
                        // 
                        //      The object literal is simply a custom object appropriate
                        //      for our application -- it can be anything that preserves the state
                        //      of a user action that will be executed when we go back online
                        //      and replay this log. In the above example,
                        //      "create" is the name of this action; "documents" is the 
                        //      type of item this command is operating on, such as documents, contacts,
                        //      tasks, etc.; and the final argument is the document that was created. 
                        
                        if(this.isReplaying){
                                throw "Programming error: you can not call "
                                                + "dojox.off.sync.actions.add() while "
                                                + "we are replaying an action log";
                        }
                        
                        this.entries.push(action);
                        
                        // save our updated state into persistent
                        // storage
                        if(this.autoSave){
                                this._save();
                        }
                },
                
                onReplay: function(action /* Object */, 
                                                        actionLog /* dojox.off.sync.ActionLog */){ /* void */
                        // summary:
                        //      Called when we replay our log, for each of our action
                        //      entries.
                        // action: Object
                        //      A custom object literal representing an action for this
                        //      application, such as 
                        //      {actionName: "create", item: {title: "message", content: "hello world"}}
                        // actionLog: dojox.off.sync.ActionLog
                        //      A reference to the dojox.off.sync.actions log so that developers
                        //      can easily call actionLog.continueReplay() or actionLog.haltReplay().
                        // description:
                        //      This callback should be connected to by applications so that
                        //      they can sync themselves when we go back online:
                        //
                        //              dojo.connect(dojox.off.sync.actions, "onReplay", function(action, actionLog){
                        //                              // do something
                        //              })
                        //
                        //      When we replay our action log, this callback is called for each
                        //      of our action entries in the order they were added. The 
                        //      'action' entry that was passed to add() for this action will 
                        //      also be passed in to onReplay, so that applications can use this information
                        //      to do their syncing, such as contacting a server web-service
                        //      to create a new item, for example. 
                        // 
                        //      Inside the method you connected to onReplay, you should either call
                        //      actionLog.haltReplay(reason) if an error occurred and you would like to halt
                        //      action replaying or actionLog.continueReplay() to have the action log
                        //      continue replaying its log and proceed to the next action; 
                        //      the reason you must call these is the action you execute inside of 
                        //      onAction will probably be asynchronous, since it will be talking on 
                        //      the network, and you should call one of these two methods based on 
                        //      the result of your network call.
                },
                
                length: function(){ /* Number */
                        // summary:
                        //      Returns the length of this 
                        //      action log
                        return this.entries.length;
                },
                
                haltReplay: function(reason /* String */){ /* void */
                        // summary: Halts replaying this command log.
                        // reason: String
                        //              The reason we halted.
                        // description:
                        //              This method is called as we are replaying an action log; it
                        //              can be called from dojox.off.sync.actions.onReplay, for
                        //              example, for an application to indicate an error occurred
                        //              while replaying this action, halting further processing of
                        //              the action log. Note that any action log entries that
                        //              were processed before have their effects retained (i.e.
                        //              they are not rolled back), while the action entry that was
                        //              halted stays in our list of actions to later be replayed.       
                        if(!this.isReplaying){
                                return;
                        }
                        
                        if(reason){
                                this.reasonHalted = reason.toString();          
                        }
                        
                        // save the state of our action log, then
                        // tell anyone who is interested that we are
                        // done when we are finished saving
                        if(this.autoSave){
                                var self = this;
                                this._save(function(){
                                        self.isReplaying = false;
                                        self.onReplayFinished();
                                });
                        }else{
                                this.isReplaying = false;
                                this.onReplayFinished();
                        }
                },
                
                continueReplay: function(){ /* void */
                        // summary:
                        //              Indicates that we should continue processing out list of
                        //              actions.
                        // description:
                        //              This method is called by applications that have overridden
                        //              dojox.off.sync.actions.onReplay() to continue replaying our 
                        //              action log after the application has finished handling the 
                        //              current action.
                        if(!this.isReplaying){
                                return;
                        }
                        
                        // shift off the old action we just ran
                        this.entries.shift();
                        
                        // are we done?
                        if(!this.entries.length){
                                // save the state of our action log, then
                                // tell anyone who is interested that we are
                                // done when we are finished saving
                                if(this.autoSave){
                                        var self = this;
                                        this._save(function(){
                                                self.isReplaying = false;
                                                self.onReplayFinished();
                                        });
                                        return;
                                }else{
                                        this.isReplaying = false;
                                        this.onReplayFinished();
                                        return;
                                }
                        }
                        
                        // get the next action
                        var nextAction = this.entries[0];
                        this.onReplay(nextAction, this);
                },
                
                clear: function(){ /* void */
                        // summary:
                        //      Completely clears this action log of its entries
                        
                        if(this.isReplaying){
                                return;
                        }
                        
                        this.entries = [];
                        
                        // save our updated state into persistent
                        // storage
                        if(this.autoSave){
                                this._save();
                        }
                },
                
                replay: function(){ /* void */
                        // summary:
                        //      For advanced usage; most developers can ignore this.
                        //      Replays all of the commands that have been
                        //      cached in this command log when we go back online;
                        //      onCommand will be called for each command we have
                        
                        if(this.isReplaying){
                                return;
                        }
                        
                        this.reasonHalted = null;
                        
                        if(!this.entries.length){
                                this.onReplayFinished();
                                return;
                        }
                        
                        this.isReplaying = true;
                        
                        var nextAction = this.entries[0];
                        this.onReplay(nextAction, this);
                },
                
                // onReplayFinished: Function
                //      For advanced usage; most developers can ignore this.
                //      Called when we are finished replaying our commands;
                //      called if we have successfully exhausted all of our
                //      commands, or if an error occurred during replaying.
                //      The default implementation simply continues the
                //      synchronization process. Connect to this to register
                //      for the event:
                //
                //              dojo.connect(dojox.off.sync.actions, "onReplayFinished", 
                //                                      someFunc)
                onReplayFinished: function(){
                },

                toString: function(){
                        var results = "";
                        results += "[";
                        
                        for(var i = 0; i < this.entries.length; i++){
                                results += "{";
                                for(var j in this.entries[i]){
                                        results += j + ": \"" + this.entries[i][j] + "\"";
                                        results += ", ";
                                }
                                results += "}, ";
                        }
                        
                        results += "]";
                        
                        return results;
                },
                
                _save: function(callback){
                        if(!callback){
                                callback = function(){};
                        }
                        
                        try{
                                var self = this;
                                var resultsHandler = function(status, key, message){
                                        //console.debug("resultsHandler, status="+status+", key="+key+", message="+message);
                                        if(status == dojox.storage.FAILED){
                                                dojox.off.onFrameworkEvent("save", 
                                                                                        {status: dojox.storage.FAILED,
                                                                                        isCoreSave: true,
                                                                                        key: key,
                                                                                        value: message,
                                                                                        namespace: dojox.off.STORAGE_NAMESPACE});
                                                callback();
                                        }else if(status == dojox.storage.SUCCESS){
                                                callback();
                                        }
                                };
                                
                                dojox.storage.put("actionlog", this.entries, resultsHandler,
                                                                        dojox.off.STORAGE_NAMESPACE);
                        }catch(exp){
                                console.debug("dojox.off.sync._save: " + exp.message||exp);
                                dojox.off.onFrameworkEvent("save",
                                                        {status: dojox.storage.FAILED,
                                                        isCoreSave: true,
                                                        key: "actionlog",
                                                        value: this.entries,
                                                        namespace: dojox.off.STORAGE_NAMESPACE});
                                callback();
                        }
                },
                
                _load: function(callback){
                        var entries = dojox.storage.get("actionlog", dojox.off.STORAGE_NAMESPACE);
                        
                        if(!entries){
                                entries = [];
                        }
                        
                        this.entries = entries;
                        
                        callback();
                }
        }
);

dojox.off.sync.actions = new dojox.off.sync.ActionLog();

}

if(!dojo._hasResource["dojox.off._common"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code.
dojo._hasResource["dojox.off._common"] = true;
dojo.provide("dojox.off._common");





// Author: Brad Neuberg, bkn3@columbia.edu, http://codinginparadise.org

// summary:
//              dojox.off is the main object for offline applications.
dojo.mixin(dojox.off, {
        // isOnline: boolean
        //      true if we are online, false if not
        isOnline: false,
        
        // NET_CHECK: int
        //              For advanced usage; most developers can ignore this.
        //              Time in seconds on how often we should check the status of the
        //              network with an automatic background timer. The current default
        //              is 5 seconds.
        NET_CHECK: 5,
        
        // STORAGE_NAMESPACE: String
        //              For advanced usage; most developers can ignore this.
        //              The namespace we use to save core data into Dojo Storage.
        STORAGE_NAMESPACE: "_dot",
        
        // enabled: boolean
        //              For advanced usage; most developers can ignore this.
        //              Whether offline ability is enabled or not. Defaults to true.
        enabled: true,
        
        // availabilityURL: String
        //              For advanced usage; most developers can ignore this.
        //              The URL to check for site availability.  We do a GET request on
        //              this URL to check for site availability.  By default we check for a
        //              simple text file in src/off/network_check.txt that has one value
        //              it, the value '1'.
        availabilityURL: dojo.moduleUrl("dojox", "off/network_check.txt"),
        
        // goingOnline: boolean
        //              For advanced usage; most developers can ignore this.
        //              True if we are attempting to go online, false otherwise
        goingOnline: false,
        
        // coreOpFailed: boolean
        //              For advanced usage; most developers can ignore this.
        //              A flag set by the Dojo Offline framework that indicates that the
        //              user denied some operation that required the offline cache or an
        //              operation failed in some critical way that was unrecoverable. For
        //              example, if the offline cache is Google Gears and we try to get a
        //              Gears database, a popup window appears asking the user whether they
        //              will approve or deny this request. If the user denies the request,
        //              and we are doing some operation that is core to Dojo Offline, then
        //              we set this flag to 'true'.  This flag causes a 'fail fast'
        //              condition, turning off offline ability.
        coreOpFailed: false,
        
        // doNetChecking: boolean
        //              For advanced usage; most developers can ignore this.
        //              Whether to have a timing interval in the background doing automatic
        //              network checks at regular intervals; the length of time between
        //              checks is controlled by dojox.off.NET_CHECK. Defaults to true.
        doNetChecking: true,
        
        // hasOfflineCache: boolean
        //              For advanced usage; most developers can ignore this.
        //      Determines if an offline cache is available or installed; an
        //      offline cache is a facility that can truely cache offline
        //      resources, such as JavaScript, HTML, etc. in such a way that they
        //      won't be removed from the cache inappropriately like a browser
        //      cache would. If this is false then an offline cache will be
        //      installed. Only Google Gears is currently supported as an offline
        //      cache. Future possible offline caches include Firefox 3.
        hasOfflineCache: null,
        
        // browserRestart: boolean
        //              For advanced usage; most developers can ignore this.
        //              If true, the browser must be restarted to register the existence of
        //              a new host added offline (from a call to addHostOffline); if false,
        //              then nothing is needed.
        browserRestart: false,
        
        _STORAGE_APP_NAME: window.location.href.replace(/[^0-9A-Za-z_]/g, "_"),
        
        _initializeCalled: false,
        _storageLoaded: false,
        _pageLoaded: false,
        
        onLoad: function(){
                // summary:
                //      Called when Dojo Offline can be used.
                // description:
                //      Do a dojo.connect to this to know when you can
                //      start using Dojo Offline:
                //              dojo.connect(dojox.off, "onLoad", myFunc);
        },
        
        onNetwork: function(type){
                // summary:
                //      Called when our on- or offline- status changes.
                // description:
                //      If we move online, then this method is called with the
                //      value "online". If we move offline, then this method is
                //      called with the value "offline". You can connect to this
                //      method to do add your own behavior:
                //
                //              dojo.connect(dojox.off, "onNetwork", someFunc)
                //
                //      Note that if you are using the default Dojo Offline UI
                //      widget that most of the on- and off-line notification
                //      and syncing is automatically handled and provided to the
                //      user.
                // type: String
                //      Either "online" or "offline".
        },
        
        initialize: function(){ /* void */
                // summary:
                //              Called when a Dojo Offline-enabled application is finished
                //              configuring Dojo Offline, and is ready for Dojo Offline to
                //              initialize itself.
                // description:
                //              When an application has finished filling out the variables Dojo
                //              Offline needs to work, such as dojox.off.ui.appName, it must
                //              this method to tell Dojo Offline to initialize itself.
                
                //              Note:
                //              This method is needed for a rare edge case. In some conditions,
                //              especially if we are dealing with a compressed Dojo build, the
                //              entire Dojo Offline subsystem might initialize itself and be
                //              running even before the JavaScript for an application has had a
                //              chance to run and configure Dojo Offline, causing Dojo Offline
                //              to have incorrect initialization parameters for a given app,
                //              such as no value for dojox.off.ui.appName. This method is
                //              provided to prevent this scenario, to slightly 'slow down' Dojo
                //              Offline so it can be configured before running off and doing
                //              its thing.      

                //console.debug("dojox.off.initialize");
                this._initializeCalled = true;
                
                if(this._storageLoaded && this._pageLoaded){
                        this._onLoad();
                }
        },
        
        goOffline: function(){ /* void */
                // summary:
                //              For advanced usage; most developers can ignore this.
                //              Manually goes offline, away from the network.
                if((dojox.off.sync.isSyncing)||(this.goingOnline)){ return; }
                
                this.goingOnline = false;
                this.isOnline = false;
        },
        
        goOnline: function(callback){ /* void */
                // summary: 
                //              For advanced usage; most developers can ignore this.
                //              Attempts to go online.
                // description:
                //              Attempts to go online, making sure this web application's web
                //              site is available. 'callback' is called asychronously with the
                //              result of whether we were able to go online or not.
                // callback: Function
                //              An optional callback function that will receive one argument:
                //              whether the site is available or not and is boolean. If this
                //              function is not present we call dojo.xoff.onOnline instead if
                //              we are able to go online.
                
                //console.debug("goOnline");
                
                if(dojox.off.sync.isSyncing || dojox.off.goingOnline){
                        return;
                }
                
                this.goingOnline = true;
                this.isOnline = false;
                
                // see if can reach our web application's web site
                this._isSiteAvailable(callback);
        },
        
        onFrameworkEvent: function(type /* String */, saveData /* Object? */){
                //      summary:
                //              For advanced usage; most developers can ignore this.
                //              A standard event handler that can be attached to to find out
                //              about low-level framework events. Most developers will not need to
                //              attach to this method; it is meant for low-level information
                //              that can be useful for updating offline user-interfaces in
                //              exceptional circumstances. The default Dojo Offline UI
                //              widget takes care of most of these situations.
                //      type: String
                //              The type of the event:
                //
                //              * "offlineCacheInstalled"
                //                      An event that is fired when a user
                //                      has installed an offline cache after the page has been loaded.
                //                      If a user didn't have an offline cache when the page loaded, a
                //                      UI of some kind might have prompted them to download one. This
                //                      method is called if they have downloaded and installed an
                //                      offline cache so a UI can reinitialize itself to begin using
                //                      this offline cache.
                //              * "coreOperationFailed"
                //                      Fired when a core operation during interaction with the
                //                      offline cache is denied by the user. Some offline caches, such
                //                      as Google Gears, prompts the user to approve or deny caching
                //                      files, using the database, and more. If the user denies a
                //                      request that is core to Dojo Offline's operation, we set
                //                      dojox.off.coreOpFailed to true and call this method for
                //                      listeners that would like to respond some how to Dojo Offline
                //                      'failing fast'.
                //              * "save"
                //                      Called whenever the framework saves data into persistent
                //                      storage. This could be useful for providing save feedback
                //                      or providing appropriate error feedback if saving fails 
                //                      due to a user not allowing the save to occur
                //      saveData: Object?
                //              If the type was 'save', then a saveData object is provided with
                //              further save information. This object has the following properties:     
                //
                //              * status - dojox.storage.SUCCESS, dojox.storage.PENDING, dojox.storage.FAILED
                //              Whether the save succeeded, whether it is pending based on a UI
                //              dialog asking the user for permission, or whether it failed.    
                //
                //              * isCoreSave - boolean
                //              If true, then this save was for a core piece of data necessary
                //              for the functioning of Dojo Offline. If false, then it is a
                //              piece of normal data being saved for offline access. Dojo
                //              Offline will 'fail fast' if some core piece of data could not
                //              be saved, automatically setting dojox.off.coreOpFailed to
                //              'true' and dojox.off.enabled to 'false'.
                //
                //              * key - String
                //              The key that we are attempting to persist
                //
                //              * value - Object
                //              The object we are trying to persist
                //
                //              * namespace - String
                //              The Dojo Storage namespace we are saving this key/value pair
                //              into, such as "default", "Documents", "Contacts", etc.
                //              Optional.
                if(type == "save"){
                        if(saveData.isCoreSave && (saveData.status == dojox.storage.FAILED)){
                                dojox.off.coreOpFailed = true;
                                dojox.off.enabled = false;
                        
                                // FIXME: Stop the background network thread
                                dojox.off.onFrameworkEvent("coreOperationFailed");
                        }
                }else if(type == "coreOperationFailed"){
                        dojox.off.coreOpFailed = true;
                        dojox.off.enabled = false;
                        // FIXME: Stop the background network thread
                }
        },
        
        _checkOfflineCacheAvailable: function(callback){
                // is a true, offline cache running on this machine?
                this.hasOfflineCache = dojo.isGears;
                
                callback();
        },
        
        _onLoad: function(){
                //console.debug("dojox.off._onLoad");
                
                // both local storage and the page are finished loading
                
                // cache the Dojo JavaScript -- just use the default dojo.js
                // name for the most common scenario
                // FIXME: TEST: Make sure syncing doesn't break if dojo.js
                // can't be found, or report an error to developer
                dojox.off.files.cache(dojo.moduleUrl("dojo", "dojo.js"));
                
                // pull in the files needed by Dojo
                this._cacheDojoResources();
                
                // FIXME: need to pull in the firebug lite files here!
                // workaround or else we will get an error on page load
                // from Dojo that it can't find 'console.debug' for optimized builds
                // dojox.off.files.cache(djConfig.baseRelativePath + "src/debug.js");
                
                // make sure that resources needed by all of our underlying
                // Dojo Storage storage providers will be available
                // offline
                dojox.off.files.cache(dojox.storage.manager.getResourceList());
                
                // slurp the page if the end-developer wants that
                dojox.off.files._slurp();
                
                // see if we have an offline cache; when done, move
                // on to the rest of our startup tasks
                this._checkOfflineCacheAvailable(dojo.hitch(this, "_onOfflineCacheChecked"));
        },
        
        _onOfflineCacheChecked: function(){
                // this method is part of our _onLoad series of startup tasks
                
                // if we have an offline cache, see if we have been added to the 
                // list of available offline web apps yet
                if(this.hasOfflineCache && this.enabled){
                        // load framework data; when we are finished, continue
                        // initializing ourselves
                        this._load(dojo.hitch(this, "_finishStartingUp"));
                }else if(this.hasOfflineCache && !this.enabled){
                        // we have an offline cache, but it is disabled for some reason
                        // perhaps due to the user denying a core operation
                        this._finishStartingUp();
                }else{
                        this._keepCheckingUntilInstalled();
                }
        },
        
        _keepCheckingUntilInstalled: function(){
                // this method is part of our _onLoad series of startup tasks
                
                // kick off a background interval that keeps
                // checking to see if an offline cache has been
                // installed since this page loaded
                        
                // FIXME: Gears: See if we are installed somehow after the
                // page has been loaded
                
                // now continue starting up
                this._finishStartingUp();
        },
        
        _finishStartingUp: function(){
                //console.debug("dojox.off._finishStartingUp");
                
                // this method is part of our _onLoad series of startup tasks
                
                if(!this.hasOfflineCache){
                        this.onLoad();
                }else if(this.enabled){
                        // kick off a thread to check network status on
                        // a regular basis
                        this._startNetworkThread();

                        // try to go online
                        this.goOnline(dojo.hitch(this, function(){
                                //console.debug("Finished trying to go online");
                                // indicate we are ready to be used
                                dojox.off.onLoad();
                        }));
                }else{ // we are disabled or a core operation failed
                        if(this.coreOpFailed){
                                this.onFrameworkEvent("coreOperationFailed");
                        }else{
                                this.onLoad();
                        }
                }
        },
        
        _onPageLoad: function(){
                //console.debug("dojox.off._onPageLoad");
                this._pageLoaded = true;
                
                if(this._storageLoaded && this._initializeCalled){
                        this._onLoad();
                }
        },
        
        _onStorageLoad: function(){
                //console.debug("dojox.off._onStorageLoad");
                this._storageLoaded = true;
                
                // were we able to initialize storage? if
                // not, then this is a core operation, and
                // let's indicate we will need to fail fast
                if(!dojox.storage.manager.isAvailable()
                        && dojox.storage.manager.isInitialized()){
                        this.coreOpFailed = true;
                        this.enabled = false;
                }
                
                if(this._pageLoaded && this._initializeCalled){
                        this._onLoad();         
                }
        },
        
        _isSiteAvailable: function(callback){
                // summary:
                //              Determines if our web application's website is available.
                // description:
                //              This method will asychronously determine if our web
                //              application's web site is available, which is a good proxy for
                //              network availability. The URL dojox.off.availabilityURL is
                //              used, which defaults to this site's domain name (ex:
                //              foobar.com). We check for dojox.off.AVAILABILITY_TIMEOUT (in
                //              seconds) and abort after that
                // callback: Function
                //              An optional callback function that will receive one argument:
                //              whether the site is available or not and is boolean. If this
                //              function is not present we call dojox.off.onNetwork instead if we
                //              are able to go online.
                dojo.xhrGet({
                        url:            this._getAvailabilityURL(),
                        handleAs:       "text",
                        timeout:        this.NET_CHECK * 1000, 
                        error:          dojo.hitch(this, function(err){
                                //console.debug("dojox.off._isSiteAvailable.error: " + err);
                                this.goingOnline = false;
                                this.isOnline = false;
                                if(callback){ callback(false); }
                        }),
                        load:           dojo.hitch(this, function(data){
                                //console.debug("dojox.off._isSiteAvailable.load, data="+data);
                                this.goingOnline = false;
                                this.isOnline = true;
                                
                                if(callback){ callback(true);
                                }else{ this.onNetwork("online"); }
                        })
                });
        },
        
        _startNetworkThread: function(){
                //console.debug("startNetworkThread");
                
                // kick off a thread that does periodic
                // checks on the status of the network
                if(!this.doNetChecking){
                        return;
                }
                
                window.setInterval(dojo.hitch(this, function(){ 
                        var d = dojo.xhrGet({
                                url:            this._getAvailabilityURL(),
                                handleAs:       "text",
                                timeout:        this.NET_CHECK * 1000,
                                error:          dojo.hitch(this, 
                                                                function(err){
                                                                        if(this.isOnline){
                                                                                this.isOnline = false;
                                                                                
                                                                                // FIXME: xhrGet() is not
                                                                                // correctly calling abort
                                                                                // on the XHR object when
                                                                                // it times out; fix inside
                                                                                // there instead of externally
                                                                                // here
                                                                                try{
                                                                                        if(typeof d.ioArgs.xhr.abort == "function"){
                                                                                                d.ioArgs.xhr.abort();
                                                                                        }
                                                                                }catch(e){}
                                        
                                                                                // if things fell in the middle of syncing, 
                                                                                // stop syncing
                                                                                dojox.off.sync.isSyncing = false;
                                        
                                                                                this.onNetwork("offline");
                                                                        }
                                                                }
                                                        ),
                                load:           dojo.hitch(this, 
                                                                function(data){
                                                                        if(!this.isOnline){
                                                                                this.isOnline = true;
                                                                                this.onNetwork("online");
                                                                        }
                                                                }
                                                        )
                        });

                }), this.NET_CHECK * 1000);
        },
        
        _getAvailabilityURL: function(){
                var url = this.availabilityURL.toString();
                
                // bust the browser's cache to make sure we are really talking to
                // the server
                if(url.indexOf("?") == -1){
                        url += "?";
                }else{
                        url += "&";
                }
                url += "browserbust=" + new Date().getTime();
                
                return url;
        },
        
        _onOfflineCacheInstalled: function(){
                this.onFrameworkEvent("offlineCacheInstalled");
        },
        
        _cacheDojoResources: function(){
                // if we are a non-optimized build, then the core Dojo bootstrap
                // system was loaded as separate JavaScript files;
                // add these to our offline cache list. these are
                // loaded before the dojo.require() system exists
                
                // FIXME: create a better mechanism in the Dojo core to
                // expose whether you are dealing with an optimized build;
                // right now we just scan the SCRIPT tags attached to this
                // page and see if there is one for _base/_loader/bootstrap.js
                var isOptimizedBuild = true;
                dojo.forEach(dojo.query("script"), function(i){
                        var src = i.getAttribute("src");
                        if(!src){ return; }
                        
                        if(src.indexOf("_base/_loader/bootstrap.js") != -1){
                                isOptimizedBuild = false;
                        }
                });
                
                if(!isOptimizedBuild){
                        dojox.off.files.cache(dojo.moduleUrl("dojo", "_base.js").uri);
                        dojox.off.files.cache(dojo.moduleUrl("dojo", "_base/_loader/loader.js").uri);
                        dojox.off.files.cache(dojo.moduleUrl("dojo", "_base/_loader/bootstrap.js").uri);
                        
                        // FIXME: pull in the host environment file in a more generic way
                        // for other host environments
                        dojox.off.files.cache(dojo.moduleUrl("dojo", "_base/_loader/hostenv_browser.js").uri);
                }
                
                // add anything that was brought in with a 
                // dojo.require() that resulted in a JavaScript
                // URL being fetched
                
                // FIXME: modify dojo/_base/_loader/loader.js to
                // expose a public API to get this information
        
                for(var i = 0; i < dojo._loadedUrls.length; i++){
                        dojox.off.files.cache(dojo._loadedUrls[i]);
                }
                
                // FIXME: add the standard Dojo CSS file
        },
        
        _save: function(){
                // summary:
                //              Causes the Dojo Offline framework to save its configuration
                //              data into local storage.        
        },
        
        _load: function(callback){
                // summary:
                //              Causes the Dojo Offline framework to load its configuration
                //              data from local storage
                dojox.off.sync._load(callback);
        }
});


// wait until the storage system is finished loading
dojox.storage.manager.addOnLoad(dojo.hitch(dojox.off, "_onStorageLoad"));

// wait until the page is finished loading
dojo.addOnLoad(dojox.off, "_onPageLoad");

}

if(!dojo._hasResource["dojox.off"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code.
dojo._hasResource["dojox.off"] = true;
dojo.provide("dojox.off");


}

if(!dojo._hasResource["dojox.off.ui"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code.
dojo._hasResource["dojox.off.ui"] = true;
dojo.provide("dojox.off.ui");





// Author: Brad Neuberg, bkn3@columbia.edu, http://codinginparadise.org

// summary:
//      dojox.off.ui provides a standard,
//      default user-interface for a 
//      Dojo Offline Widget that can easily
//      be dropped into applications that would
//      like to work offline.
dojo.mixin(dojox.off.ui, {
        // appName: String
        //      This application's name, such as "Foobar". Note that
        //      this is a string, not HTML, so embedded markup will
        //      not work, including entities. Only the following
        //      characters are allowed: numbers, letters, and spaces.
        //      You must set this property.
        appName: "setme",
        
        // autoEmbed: boolean
        //      For advanced usage; most developers can ignore this.
        //      Whether to automatically auto-embed the default Dojo Offline
        //      widget into this page; default is true. 
        autoEmbed: true,
        
        // autoEmbedID: String
        //      For advanced usage; most developers can ignore this.
        //      The ID of the DOM element that will contain our
        //      Dojo Offline widget; defaults to the ID 'dot-widget'.
        autoEmbedID: "dot-widget",
        
        // runLink: String
        //      For advanced usage; most developers can ignore this.
        //      The URL that should be navigated to to run this 
        //      application offline; this will be placed inside of a
        //      link that the user can drag to their desktop and double
        //      click. Note that this URL must exactly match the URL
        //      of the main page of our resource that is offline for
        //      it to be retrieved from the offline cache correctly.
        //      For example, if you have cached your main page as
        //      http://foobar.com/index.html, and you set this to
        //      http://www.foobar.com/index.html, the run link will
        //      not work. By default this value is automatically set to 
        //      the URL of this page, so it does not need to be set
        //      manually unless you have unusual needs.
        runLink: window.location.href,
        
        // runLinkTitle: String
        //      For advanced usage; most developers can ignore this.
        //      The text that will be inside of the link that a user
        //      can drag to their desktop to run this application offline.
        //      By default this is automatically set to "Run " plus your
        //      application's name.
        runLinkTitle: "Run Application",
        
        // learnHowPath: String
        //      For advanced usage; most developers can ignore this.
        //      The path to a web page that has information on 
        //      how to use this web app offline; defaults to
        //      src/off/ui-template/learnhow.html, relative to
        //      your Dojo installation. Make sure to set
        //      dojo.to.ui.customLearnHowPath to true if you want
        //      a custom Learn How page.
        learnHowPath: dojo.moduleUrl("dojox", "off/resources/learnhow.html"),
        
        // customLearnHowPath: boolean
        //      For advanced usage; most developers can ignore this.
        //      Whether the developer is using their own custom page
        //      for the Learn How instructional page; defaults to false.
        //      Use in conjunction with dojox.off.ui.learnHowPath.
        customLearnHowPath: false,
        
        htmlTemplatePath: dojo.moduleUrl("dojox", "off/resources/offline-widget.html").uri,
        cssTemplatePath: dojo.moduleUrl("dojox", "off/resources/offline-widget.css").uri,
        onlineImagePath: dojo.moduleUrl("dojox", "off/resources/greenball.png").uri,
        offlineImagePath: dojo.moduleUrl("dojox", "off/resources/redball.png").uri,
        rollerImagePath: dojo.moduleUrl("dojox", "off/resources/roller.gif").uri,
        checkmarkImagePath: dojo.moduleUrl("dojox", "off/resources/checkmark.png").uri,
        learnHowJSPath: dojo.moduleUrl("dojox", "off/resources/learnhow.js").uri,
        
        _initialized: false,
        
        onLoad: function(){
                // summary:
                //      A function that should be connected to allow your
                //      application to know when Dojo Offline, the page, and
                //      the Offline Widget are all initialized and ready to be
                //      used:
                //
                //              dojo.connect(dojox.off.ui, "onLoad", someFunc)
        },

        _initialize: function(){
                //console.debug("dojox.off.ui._initialize");
                
                // make sure our app name is correct
                if(this._validateAppName(this.appName) == false){
                        alert("You must set dojox.off.ui.appName; it can only contain "
                                        + "letters, numbers, and spaces; right now it "
                                        + "is incorrectly set to '" + dojox.off.ui.appName + "'");
                        dojox.off.enabled = false;
                        return;
                }
                
                // set our run link text to its default
                this.runLinkText = "Run " + this.appName;
                
                // setup our event listeners for Dojo Offline events
                // to update our UI
                dojo.connect(dojox.off, "onNetwork", this, "_onNetwork");
                dojo.connect(dojox.off.sync, "onSync", this, "_onSync");
                
                // cache our default UI resources
                dojox.off.files.cache([
                                                        this.htmlTemplatePath,
                                                        this.cssTemplatePath,
                                                        this.onlineImagePath,
                                                        this.offlineImagePath,
                                                        this.rollerImagePath,
                                                        this.checkmarkImagePath
                                                        ]);
                
                // embed the offline widget UI
                if(this.autoEmbed){
                        this._doAutoEmbed();
                }
        },
        
        _doAutoEmbed: function(){
                // fetch our HTML for the offline widget

                // dispatch the request
                dojo.xhrGet({
                        url:     this.htmlTemplatePath,
                        handleAs:       "text",
                        error:          function(err){
                                dojox.off.enabled = false;
                                err = err.message||err;
                                alert("Error loading the Dojo Offline Widget from "
                                                + this.htmlTemplatePath + ": " + err);
                        },
                        load:           dojo.hitch(this, this._templateLoaded)   
                });
        },
        
        _templateLoaded: function(data){
                //console.debug("dojox.off.ui._templateLoaded");
                // inline our HTML
                var container = dojo.byId(this.autoEmbedID);
                if(container){ container.innerHTML = data; }
                
                // fill out our image paths
                this._initImages();
                
                // update our network indicator status ball
                this._updateNetIndicator();
                
                // update our 'Learn How' text
                this._initLearnHow();
                
                this._initialized = true;
                
                // check offline cache settings
                if(!dojox.off.hasOfflineCache){
                        this._showNeedsOfflineCache();
                        return;
                }
                
                // check to see if we need a browser restart
                // to be able to use this web app offline
                if(dojox.off.hasOfflineCache && dojox.off.browserRestart){
                        this._needsBrowserRestart();
                        return;
                }else{
                        var browserRestart = dojo.byId("dot-widget-browser-restart");
                        if(browserRestart){ browserRestart.style.display = "none"; }
                }
                
                // update our sync UI
                this._updateSyncUI();
                
                // register our event listeners for our main buttons
                this._initMainEvtHandlers();
                
                // if offline functionality is disabled, disable everything
                this._setOfflineEnabled(dojox.off.enabled);
                
                // update our UI based on the state of the network
                this._onNetwork(dojox.off.isOnline ? "online" : "offline");
                
                // try to go online
                this._testNet();
        },
        
        _testNet: function(){
                dojox.off.goOnline(dojo.hitch(this, function(isOnline){
                        //console.debug("testNet callback, isOnline="+isOnline);
                        
                        // display our online/offline results
                        this._onNetwork(isOnline ? "online" : "offline");
                        
                        // indicate that our default UI 
                        // and Dojo Offline are now ready to
                        // be used
                        this.onLoad();
                }));
        },
        
        _updateNetIndicator: function(){
                var onlineImg = dojo.byId("dot-widget-network-indicator-online");
                var offlineImg = dojo.byId("dot-widget-network-indicator-offline");
                var titleText = dojo.byId("dot-widget-title-text");
                
                if(onlineImg && offlineImg){
                        if(dojox.off.isOnline == true){
                                onlineImg.style.display = "inline";
                                offlineImg.style.display = "none";
                        }else{
                                onlineImg.style.display = "none";
                                offlineImg.style.display = "inline";
                        }
                }
                
                if(titleText){
                        if(dojox.off.isOnline){
                                titleText.innerHTML = "Online";
                        }else{
                                titleText.innerHTML = "Offline";
                        }
                }
        },
        
        _initLearnHow: function(){
                var learnHow = dojo.byId("dot-widget-learn-how-link");
                
                if(!learnHow){ return; }
                
                if(!this.customLearnHowPath){
                        // add parameters to URL so the Learn How page
                        // can customize itself and display itself
                        // correctly based on framework settings
                        var dojoPath = djConfig.baseRelativePath;
                        this.learnHowPath += "?appName=" + encodeURIComponent(this.appName)
                                                                        + "&hasOfflineCache=" + dojox.off.hasOfflineCache
                                                                        + "&runLink=" + encodeURIComponent(this.runLink)
                                                                        + "&runLinkText=" + encodeURIComponent(this.runLinkText)
                                                                        + "&baseRelativePath=" + encodeURIComponent(dojoPath);
                        
                        // cache our Learn How JavaScript page and
                        // the HTML version with full query parameters
                        // so it is available offline without a cache miss                                      
                        dojox.off.files.cache(this.learnHowJSPath);
                        dojox.off.files.cache(this.learnHowPath);
                }
                
                learnHow.setAttribute("href", this.learnHowPath);
                
                var appName = dojo.byId("dot-widget-learn-how-app-name");
                
                if(!appName){ return; }
                
                appName.innerHTML = "";
                appName.appendChild(document.createTextNode(this.appName));
        },
        
        _validateAppName: function(appName){
                if(!appName){ return false; }
                
                return (/^[a-z0-9 ]*$/i.test(appName));
        },
        
        _updateSyncUI: function(){
                var roller = dojo.byId("dot-roller");
                var checkmark = dojo.byId("dot-success-checkmark");
                var syncMessages = dojo.byId("dot-sync-messages");
                var details = dojo.byId("dot-sync-details");
                var cancel = dojo.byId("dot-sync-cancel");
                
                if(dojox.off.sync.isSyncing){
                        this._clearSyncMessage();
                        
                        if(roller){ roller.style.display = "inline"; }
                        
                        if(checkmark){ checkmark.style.display = "none"; }
                        
                        if(syncMessages){
                                dojo.removeClass(syncMessages, "dot-sync-error");
                        }
                        
                        if(details){ details.style.display = "none"; }
                        
                        if(cancel){ cancel.style.display = "inline"; }
                }else{  
                        if(roller){ roller.style.display = "none"; }
                        
                        if(cancel){ cancel.style.display = "none"; }
                        
                        if(syncMessages){
                                dojo.removeClass(syncMessages, "dot-sync-error");
                        }
                }
        },
        
        _setSyncMessage: function(message){
                var syncMessage = dojo.byId("dot-sync-messages");
                if(syncMessage){
                        // when used with Google Gears pre-release in Firefox/Mac OS X,
                        // the browser would crash when testing in Moxie
                        // if we set the message this way for some reason.
                        // Brad Neuberg, bkn3@columbia.edu
                        //syncMessage.innerHTML = message;
                        
                        while(syncMessage.firstChild){
                                syncMessage.removeChild(syncMessage.firstChild);
                        }
                        syncMessage.appendChild(document.createTextNode(message));
                }
        },
        
        _clearSyncMessage: function(){
                this._setSyncMessage("");
        },
        
        _initImages: function(){        
                var onlineImg = dojo.byId("dot-widget-network-indicator-online");
                if(onlineImg){
                        onlineImg.setAttribute("src", this.onlineImagePath);
                }
                
                var offlineImg = dojo.byId("dot-widget-network-indicator-offline");
                if(offlineImg){
                        offlineImg.setAttribute("src", this.offlineImagePath);
                }
                
                var roller = dojo.byId("dot-roller");
                if(roller){
                        roller.setAttribute("src", this.rollerImagePath);
                }
                
                var checkmark = dojo.byId("dot-success-checkmark");
                if(checkmark){
                        checkmark.setAttribute("src", this.checkmarkImagePath);
                }
        },
        
        _showDetails: function(evt){
                // cancel the button's default behavior
                evt.preventDefault();
                evt.stopPropagation();
                
                if(!dojox.off.sync.details.length){
                        return;
                }
                
                // determine our HTML message to display
                var html = "";
                html += "<html><head><title>Sync Details</title><head><body>";
                html += "<h1>Sync Details</h1>\n";
                html += "<ul>\n";
                for(var i = 0; i < dojox.off.sync.details.length; i++){
                        html += "<li>";
                        html += dojox.off.sync.details[i];
                        html += "</li>";        
                }
                html += "</ul>\n";
                html += "<a href='javascript:window.close()' "
                                 + "style='text-align: right; padding-right: 2em;'>"
                                 + "Close Window"
                                 + "</a>\n";
                html += "</body></html>";
                
                // open a popup window with this message
                var windowParams = "height=400,width=600,resizable=true,"
                                                        + "scrollbars=true,toolbar=no,menubar=no,"
                                                        + "location=no,directories=no,dependent=yes";

                var popup = window.open("", "SyncDetails", windowParams);
                
                if(!popup){ // aggressive popup blocker
                        alert("Please allow popup windows for this domain; can't display sync details window");
                        return;
                }
                
                popup.document.open();
                popup.document.write(html);
                popup.document.close();
                
                // put the focus on the popup window
                if(popup.focus){
                        popup.focus();
                }
        },
        
        _cancel: function(evt){
                // cancel the button's default behavior
                evt.preventDefault();
                evt.stopPropagation();
                
                dojox.off.sync.cancel();
        },
        
        _needsBrowserRestart: function(){
                var browserRestart = dojo.byId("dot-widget-browser-restart");
                if(browserRestart){
                        dojo.addClass(browserRestart, "dot-needs-browser-restart");
                }
                
                var appName = dojo.byId("dot-widget-browser-restart-app-name");
                if(appName){
                        appName.innerHTML = "";
                        appName.appendChild(document.createTextNode(this.appName));
                }
                
                var status = dojo.byId("dot-sync-status");
                if(status){
                        status.style.display = "none";
                }
        },
        
        _showNeedsOfflineCache: function(){
                var widgetContainer = dojo.byId("dot-widget-container");
                if(widgetContainer){
                        dojo.addClass(widgetContainer, "dot-needs-offline-cache");
                }
        },
        
        _hideNeedsOfflineCache: function(){
                var widgetContainer = dojo.byId("dot-widget-container");
                if(widgetContainer){
                        dojo.removeClass(widgetContainer, "dot-needs-offline-cache");
                }
        },
        
        _initMainEvtHandlers: function(){
                var detailsButton = dojo.byId("dot-sync-details-button");
                if(detailsButton){
                        dojo.connect(detailsButton, "onclick", this, this._showDetails);
                }
                var cancelButton = dojo.byId("dot-sync-cancel-button");
                if(cancelButton){
                        dojo.connect(cancelButton, "onclick", this, this._cancel);
                }
        },
        
        _setOfflineEnabled: function(enabled){
                var elems = [];
                elems.push(dojo.byId("dot-sync-status"));
                
                for(var i = 0; i < elems.length; i++){
                        if(elems[i]){
                                elems[i].style.visibility = 
                                                        (enabled ? "visible" : "hidden");
                        }
                }
        },
        
        _syncFinished: function(){
                this._updateSyncUI();
                
                var checkmark = dojo.byId("dot-success-checkmark");
                var details = dojo.byId("dot-sync-details");
                
                if(dojox.off.sync.successful == true){
                        this._setSyncMessage("Sync Successful");
                        if(checkmark){ checkmark.style.display = "inline"; }
                }else if(dojox.off.sync.cancelled == true){
                        this._setSyncMessage("Sync Cancelled");
                        
                        if(checkmark){ checkmark.style.display = "none"; }
                }else{
                        this._setSyncMessage("Sync Error");
                        
                        var messages = dojo.byId("dot-sync-messages");
                        if(messages){
                                dojo.addClass(messages, "dot-sync-error");
                        }
                        
                        if(checkmark){ checkmark.style.display = "none"; }
                }
                
                if(dojox.off.sync.details.length && details){
                        details.style.display = "inline";
                }
        },
        
        _onFrameworkEvent: function(type, saveData){
                if(type == "save"){
                        if(saveData.status == dojox.storage.FAILED && !saveData.isCoreSave){
                                alert("Please increase the amount of local storage available "
                                                + "to this application");
                                if(dojox.storage.hasSettingsUI()){
                                        dojox.storage.showSettingsUI();
                                }               
                        
                                // FIXME: Be able to know if storage size has changed
                                // due to user configuration
                        }
                }else if(type == "coreOperationFailed"){
                        console.log("Application does not have permission to use Dojo Offline");
                
                        if(!this._userInformed){
                                alert("This application will not work if Google Gears is not allowed to run");
                                this._userInformed = true;
                        }
                }else if(type == "offlineCacheInstalled"){
                        // clear out the 'needs offline cache' info
                        this._hideNeedsOfflineCache();
                
                        // check to see if we need a browser restart
                        // to be able to use this web app offline
                        if(dojox.off.hasOfflineCache == true
                                && dojox.off.browserRestart == true){
                                this._needsBrowserRestart();
                                return;
                        }else{
                                var browserRestart = dojo.byId("dot-widget-browser-restart");
                                if(browserRestart){
                                        browserRestart.style.display = "none";
                                }
                        }
                
                        // update our sync UI
                        this._updateSyncUI();
                
                        // register our event listeners for our main buttons
                        this._initMainEvtHandlers();
                
                        // if offline is disabled, disable everything
                        this._setOfflineEnabled(dojox.off.enabled);
                
                        // try to go online
                        this._testNet();
                }
        },
        
        _onSync: function(type){
                //console.debug("ui, onSync="+type);
                switch(type){
                        case "start": 
                                this._updateSyncUI();
                                break;
                                
                        case "refreshFiles":
                                this._setSyncMessage("Downloading UI...");
                                break;
                                
                        case "upload":
                                this._setSyncMessage("Uploading new data...");
                                break;
                                
                        case "download":
                                this._setSyncMessage("Downloading new data...");
                                break;
                                
                        case "finished":
                                this._syncFinished();
                                break;
                                
                        case "cancel":
                                this._setSyncMessage("Canceling Sync...");
                                break;
                                
                        default:
                                dojo.warn("Programming error: "
                                                        + "Unknown sync type in dojox.off.ui: " + type);
                                break;
                }
        },
        
        _onNetwork: function(type){
                // summary:
                //      Called when we go on- or off-line
                // description:
                //      When we go online or offline, this method is called to update
                //      our UI. Default behavior is to update the Offline
                //      Widget UI and to attempt a synchronization.
                // type: String
                //      "online" if we just moved online, and "offline" if we just
                //      moved offline.
                
                if(!this._initialized){ return; }
                
                // update UI
                this._updateNetIndicator();
                
                if(type == "offline"){
                        this._setSyncMessage("You are working offline");
                
                        // clear old details
                        var details = dojo.byId("dot-sync-details");
                        if(details){ details.style.display = "none"; }
                        
                        // if we fell offline during a sync, hide
                        // the sync info
                        this._updateSyncUI();
                }else{ // online
                        // synchronize, but pause for a few seconds
                        // so that the user can orient themselves
                        if(dojox.off.sync.autoSync){
                                window.setTimeout("dojox.off.sync.synchronize()", 1000);
                        }
                }
        }
});

// register ourselves for low-level framework events
dojo.connect(dojox.off, "onFrameworkEvent", dojox.off.ui, "_onFrameworkEvent");

// start our magic when the Dojo Offline framework is ready to go
dojo.connect(dojox.off, "onLoad", dojox.off.ui, dojox.off.ui._initialize);

}

if(!dojo._hasResource["dojox.off.offline"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code.
dojo._hasResource["dojox.off.offline"] = true;
dojo.provide("dojox.off.offline");







}