File:CacheManager.js
/**
* @module Core
* @namespace springroll
*/
(function(undefined)
{
// Classes to import
var Debug;
/**
* Used for managing the browser cache of loading external elements
* can easily load version manifest and apply it to the media loader
* supports cache busting all media load requests
* uses the query string to bust browser versions.
*
* @class CacheManager
* @constructor
* @param {springroll.Application} app Reference to application
*/
var CacheManager = function(app)
{
if (DEBUG && !Debug)
{
Debug = include('springroll.Debug', false);
}
/**
* The current application
* @protected
* @property {springroll.Application} _app
*/
this._app = app;
/**
* The collection of version numbers
* @protected
* @property {Dictionary} _versions
*/
this._versions = {};
/**
* The list of URL filtering functions.
* @protected
* @property {Array} _filters
*/
this._filters = [];
/**
* A global version or cache busting string to apply to every url.
* @property {String} _globalVersion
*/
this._globalVersion = null;
// Function bindings
this._applySpecificVersion = this._applySpecificVersion.bind(this);
this._applyGlobalVersion = this._applyGlobalVersion.bind(this);
// Initial set
this.cacheBust = false;
};
/* Easy access to the prototype */
var p = extend(CacheManager);
/**
* If we are suppose to cache bust every file
* @property {Boolean} cacheBust
* @public
* @default false
*/
Object.defineProperty(p, "cacheBust",
{
get: function()
{
return !!(this._globalVersion && this._globalVersion.indexOf("cb=") === 0);
},
set: function(value)
{
if (value)
{
this._globalVersion = "cb=" + Date.now();
this.unregisterURLFilter(this._applySpecificVersion);
this.registerURLFilter(this._applyGlobalVersion);
}
else
{
var version = this._app.options.version;
this._globalVersion = version ? "v=" + version : null;
if (this._globalVersion)
{
this.unregisterURLFilter(this._applySpecificVersion);
this.registerURLFilter(this._applyGlobalVersion);
}
else
{
this.unregisterURLFilter(this._applyGlobalVersion);
this.registerURLFilter(this._applySpecificVersion);
}
}
}
});
/**
* Destroy the cache manager, don't use after this.
* @public
* @method destroy
*/
p.destroy = function()
{
this._app = null;
this._versions = null;
this._filters = null;
this._applySpecificVersion = null;
this._applyGlobalVersion = null;
};
/**
* Adds a versions text file containing versions for different assets.
* @public
* @method addVersionsFile
* @param {String} url The url of the versions file.
* @param {Function} callback Callback when the versions file has been loaded.
* @param {String} baseUrl A base url to prepend all lines of the file.
*/
p.addVersionsFile = function(url, callback, baseUrl)
{
if (DEBUG && Debug) Debug.assert(/^.*\.txt$/.test(url), "The versions file must be a *.txt file");
// If we already cache busting, we can ignore this
if (this.cacheBust)
{
if (callback) callback();
return;
}
// Add a random version number to never cache the text file
this.addVersion(url, Date.now().toString());
//ensure that that cache busting version is applied
url = this._applySpecificVersion(url);
var cm = this;
// Load the version
this._app.load(url, function(versions)
{
// check for a valid result content
if (versions)
{
// Remove carrage returns and split on newlines
var lines = versions.replace(/\r/g, '').split("\n");
var i, parts;
// Go line by line
for (i = 0, len = lines.length; i < len; i++)
{
// Check for a valid line
if (!lines[i]) continue;
// Split lines
parts = lines[i].split(' ');
// Add the parts
if (parts.length != 2) continue;
// Add the versioning
cm.addVersion((baseUrl || "") + parts[0], parts[1]);
}
}
if (callback) callback();
});
};
/**
* Add a version number for a file
* @method addVersion
* @public
* @param {String} url The url of the object
* @param {String} version Version number or has of file
*/
p.addVersion = function(url, version)
{
if (!this._versions[url])
this._versions[url] = version;
};
/**
* Adds a function for running all urls through, to modify them if needed.
* Functions used should accept one string parameter (the url), and return the
* modified url.
* @method registerURLFilter
* @public
* @param {Function} filter The function that will handle urls.
*/
p.registerURLFilter = function(filter)
{
if (this._filters.indexOf(filter) == -1)
this._filters.push(filter);
};
/**
* Removes a function from the list of filtering functions.
* @method unregisterURLFilter
* @public
* @param {Function} filter The function to remove.
*/
p.unregisterURLFilter = function(filter)
{
var index = this._filters.indexOf(filter);
if (index > -1)
this._filters.splice(index, 1);
};
/**
* Applies a url specific version to a url from the versions file.
* @method _applySpecificVersion
* @private
* @param {String} url The url to apply versioning to.
* @return {String} The modified url.
*/
p._applySpecificVersion = function(url)
{
//don't apply versioning if the asset is retrieved from a php service
var basePath = this._app.options.basePath;
if (basePath && basePath.indexOf("?") > 0) return url;
var ver = this._versions[url];
//if a version exists for this url, and the url doesn't already have 'v=' in it
//then apply the url specific version.
if (ver && /(\?|\&)v\=[0-9]*/.test(url) === false)
{
url = url + (url.indexOf("?") < 0 ? "?" : "&") + "v=" + ver.version;
}
return url;
};
/**
* Applies cache busting or a global version to a url.
* @method _applyGlobalVersion
* @private
* @param {String} url The url to apply versioning to.
* @return {String} The modified url.
*/
p._applyGlobalVersion = function(url)
{
if (!this._globalVersion) return url;
//don't apply versioning if the asset is retrieved from a php service
var basePath = this._app.options.basePath;
if (basePath && basePath.indexOf("?") > 0) return url;
//apply the versioning if it isn't already on the url
var test = this._globalVersion.indexOf("cb=") === 0 ?
(/(\?|\&)cb\=[0-9]*/) : (/(\?|\&)v\=/);
if (test.test(url) === false)
{
url = url + (url.indexOf("?") < 0 ? "?" : "&") + this._globalVersion;
}
return url;
};
/**
* Applies a base path to a relative url. This is not used in the filtering
* system because PreloadJS has its own method of prepending the base path
* that we use. Instead, it is used with an extra parameter to prepare().
* @method _applyBasePath
* @private
* @param {String} url The url to prepend the base path to.
* @return {String} The modified url.
*/
p._applyBasePath = function(url)
{
var basePath = this._app.options.basePath;
if (basePath && /^http(s)?\:/.test(url) === false && url.search(basePath) == -1)
{
url = basePath + url;
}
return url;
};
/**
* Prepare a URL with the necessary cache busting and/or versioning
* as well as the base directory.
* @public
* @method prepare
* @param {String} url The url to prepare
* @param {Boolean} [applyBasePath=false] If the global base path should be applied to the url.
* This defaults to false because it can potentially interfere with later regular
* expression checks, particularly with PreloadJS
* @return {String} The final url with version/cache and basePath added
*/
p.prepare = function(url, applyBasePath)
{
//apply first in case the base path is strange and makes the rest of the path a query string
if (applyBasePath)
{
url = this._applyBasePath(url);
}
for (var i = 0, len = this._filters.length; i < len; ++i)
{
url = this._filters[i](url);
}
return url;
};
// Assign to namespace
namespace('springroll').CacheManager = CacheManager;
}());