HTML5应用程序缓存,获取客户端中缓存文件的列表



在我的项目中,我试图使用HTML5应用缓存来缓存CSS和JS等静态资源,以及图像和视频等"用户特定"文件。当我说用户特定的图像/视频时,我试图为每个用户提供单独的文件,我还需要控制文件下载的顺序。

在这种情况下,我的清单文件将为每个用户动态加载。有没有一种方法可以让我获得已经缓存在客户端的资源列表?

如果没有,是否可以读取客户端中的".appcache"文件?

是。您可以使用AJAX请求来获取清单缓存文件,然后读取它

但是,这并不能保证问题中的浏览器具有可用的文件。

以下是的示例代码

  • 哪个检查我们是否缓存了HTML5应用程序或没有

  • 如果我们没有处于缓存状态,那么计算清单中加载的资源,并根据清单缓存条目计数(总数)显示进度条,并对所有URL进行手动AJAX GET请求以预热缓存。浏览器会自己做这件事,但这样我们就可以从过程中获得一些进度信息。

  • 当缓存处于已知良好状态时,向前移动

免责声明:自2010年以来未测试工作

/**
 * HTML5 offline manifest preloader.
 * 
 * Load all manifest cached entries, so that they are immediately available during the web app execution.
 * Display some nice JQuery progress while loading.
 * 
 * @copyright 2010 mFabrik Research Oy
 * 
 * @author Mikko Ohtamaa, http://opensourcehacker.com
 */
/**
 * Preloader class constructor.
 * 
 * Manifest is retrieved via HTTP GET and parsed.
 * All cache entries are loaded using HTTP GET.
 * 
 * Local storage attribute "preloaded" is used to check whether loading needs to be performed,
 * as it is quite taxing operation.
 * 
 * To debug this code and force retrieving of all manifest URLs, add reloaded=true HTTP GET query parameter:
 * 
 * 
 * 
 * @param {Function} endCallback will be called when all offline entries are loaded
 * 
 * @param {Object} progressMonitor ProgressMonitor object for which the status of the loading is reported.
 */
function Preloader(endCallback, progressMonitor, debug) {
    if(!progressMonitor) {
        throw "progressMonitor must be defined";
    }
    this.endCallback = endCallback;
    this.progressMonitor = progressMonitor;
    this.logging = debug; // Flag to control console.log() output   
}
Preloader.prototype = { 
    /**
     * Load HTML5 manifest and parse its data
     * 
     * @param data: String, manifest file data
     * @return Array of cache entries 
     * 
     * @throw: Exception if parsing fails
     */
    parseManifest : function(data) {
        /* Declare some helper string functions 
         * 
         * http://rickyrosario.com/blog/javascript-startswith-and-endswith-implementation-for-strings/
         *
         */
        function startswith(str, prefix) {
            return str.indexOf(prefix) === 0;
        }
        var entries = [];
        var sections = ["NETWORK", "CACHE", "FALLBACK"];
        var currentSection = "CACHE";
        var lines = data.split(/rn|r|n/);
        var i;
        if(lines.length <= 1) {
            throw "Manifest does not contain text lines";
        }
        var firstLine = lines[0];
        if(!(startswith(firstLine, "CACHE MANIFEST"))) {
            throw "Invalid cache manifest header:" + firstLine;
        }
        for(i=1; i<lines.length; i++) {
            var line = lines[i];
            this.debug("Parsing line:" + line);
            // If whitespace trimmed line is empty, skip it
            line = jQuery.trim(line);
            if(line == "") {
                continue;
            }
            if(line[0] == "#") {
                // skip comment;
                continue;
            }
            // Test for a new section
            var s = 0;
            var sectionDetected = false;
            for(s=0; s<sections.length; s++) {
                var section = sections[s];
                if(startswith(line, section + ":")) {
                    currentSection = section;
                    sectionDetected = true;
                }
            }
            if(sectionDetected) {
                continue;
            }
            // Otherwise assume we can check for cached url
            if(currentSection == "CACHE") {
                entries.push(line); 
            }
        }
        return entries;
    },
    /**
     * Manifest is given as an <html> attribute.
     */
    extractManifestURL : function() {
        var url = $("html").attr("manifest");
        if(url === null) {
            alert("Preloader cannot find manifest URL from <html> tag");
            return null;
        }
        return url;
    },
    isPreloaded : function() {
        // May be null or false
        return localStorage.getItem("preloaded") == true;
    },
    setPreloaded : function(status) {
        localStorage.setItem("preloaded", status);
    },
    /**
     * Check whether we need to purge offline cache.
     * 
     */
    isForcedReload : function() {
        // http://www.netlobo.com/url_query_string_javascript.html
        function getQueryParam(name) {
          name = name.replace(/[[]/,"\[").replace(/[]]/,"\]");
          var regexS = "[\?&]"+name+"=([^&#]*)";
          var regex = new RegExp( regexS );
          var results = regex.exec( window.location.href );
          if (results == null) {
            return "";
          } else {
            return results[1];
          }
        }
        if(getQueryParam("reload") == "true") {
            return true;
        }   
        return false;
    },
    /**
     * Do everything necessary to set-up offline application
     */
    load : function() {
        this.debug("Entering preloader");
        if (window.applicationCache) {
            this.debug("ApplicationCache status " + window.applicationCache.status);
            this.debug("Please see http://www.w3.org/TR/html5/offline.html#applicationcache");
        } else {
            this.silentError("The browser does not support HTML5 applicationCache object");
            return; 
        }
        var cold;
        if(this.isPreloaded()) {
            // We have succesfully completed preloading before
            // ...move forward
            forceReload = this.isForcedReload(); 
            if (forceReload == true) {
                applicationCache.update();
            } else {
                this.endCallback();
                return;
            }
            cold = false;
        } else {
            cold = true;
        }
        var url = this.extractManifestURL();
        if(url === null) {
            return;
        }
        this.progressMonitor.startProgress(cold);
        $.get(url, {}, jQuery.proxy(manifestLoadedCallback, this));
        function manifestLoadedCallback(data, textStatus, xhr) { 
            this.debug("Manifest retrieved");
            var text = data;
            manifestEntries = this.parseManifest(text); 
            this.debug("Parsed manifest entries:" + manifestEntries.length);
            this.populateCache(manifestEntries);
        }
    },

    /**
     * Bootstrap async loading of cache entries.
     * 
     * @param {Object} entrires
     */
    populateCache : function(entries) {
        this.manifestEntries = entries;
        this.currentEntry = 0;
        this.maxEntry = entries.length;
        this.loadNextEntry();
    },
    /**
     * Make AJAX request to next entry and update progress bar.
     * 
     */
    loadNextEntry : function() {
        if(this.currentEntry >= this.maxEntry) {
            this.setPreloaded(true);
            this.progressMonitor.endProgress();
            this.endCallback();
        }
        var entryURL = this.manifestEntries[this.currentEntry];
        this.debug("Loading entry: " + entryURL);
        function done() {
            this.currentEntry++;
            this.progressMonitor.updateProgress(this.currentEntry, this.maxEntries);
            this.loadNextEntry();   
        }
        this.debug("Preloader fetching:" + entryURL + " (" + this.currentEntry + " / " + this.maxEntry + ")");
        $.get(entryURL, {}, jQuery.proxy(done, this));
    },
    /**
     * Write to debug console
     * 
     * @param {String} msg
     */
    debug : function(msg) {
        if(this.logging) {
            console.log(msg);
        }
    },
    /**
     * Non-end user visible error message
     *
     * @param {Object} msg
     */
    silentError : function(msg) {
        console.log(msg);
    }
};
function ProgressMonitor() {
}
ProgressMonitor.prototype = {
    /**
     * Start progress bar... initialize as 0 / 0
     */
    startProgress : function(coldVirgin) {
        $("#web-app-loading-progress-monitor").show();
        if(coldVirgin) {
            $("#web-app-loading-progress-monitor .first-time").show();
        }
    },
    endProgress : function() {
    },
    updateProgress : function(currentEntry, maxEntries) {
    }
};

我也一直在研究一个解决方案,用于发现缓存的文件,并提出了以下内容。

.htaccess包装器,用于将文件抓取到appcache的目录。

#.htaccess
<FilesMatch ".(mp4|mpg|MPG|m4a|wav|WAV|jpg|JPG|bmp|BMP|png|PNG|gif|GIF)$">
    SetHandler autho
</FilesMatch>
Action autho /www/restricted_access/auth.php

然后,我的auth.php文件将文件(以块为单位)返回到浏览器,但同时也使用先前声明的APPID登录到服务器(我使用DB表)。

这样,当检测到"progress"事件时,可以进行AJAX调用来检索APPID的最后一个条目,该条目包含文件名和发送的数据量。

使用此方法的优点是,它对访问".htaccess-wapped"文件夹中文件的其他方法透明,在我的情况下还包括身份验证。

当由于任何原因未被授权访问文件时,我会返回"未授权"标题。

最新更新