Subversion Repositories Applications.papyrus

Rev

Blame | Last modification | View Log | RSS feed

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>
                //              dojo.require("dojox.sql");
                //              dojo.require("dojox.off");
                //              dojo.require("dojox.off.ui");
                //              dojo.require("dojox.off.sync");
                //
                //              // 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);
                                }
                });
        }
}

}