Allow for long-term client side caching of dataFetch responses.

This commit is contained in:
Nathan Gray 2014-07-28 21:17:06 +00:00
parent 8a70df4df8
commit df76846b37

View File

@ -26,6 +26,15 @@ egw.extend("data", egw.MODULE_APP_LOCAL, function (_app, _wnd) {
var lastModification = null; var lastModification = null;
/**
* cacheCallback stores callbacks that determine if data is placed
* into cacheStorage, or simply kept temporarily. It is indexed
* by prefix.
*
* @type Array
*/
var cacheCallback = {};
/** /**
* The uid function generates a session-unique id for the current * The uid function generates a session-unique id for the current
* application by appending the application name to the given uid. * application by appending the application name to the given uid.
@ -40,6 +49,35 @@ egw.extend("data", egw.MODULE_APP_LOCAL, function (_app, _wnd) {
return _prefix + "::" + _uid; return _prefix + "::" + _uid;
} }
/**
* Looks like too much data is cached. Forget some.
*
* Tries to free up localStorage by removing cached data for the given
* prefix, but if none is found it will remove all cached data.
*
* @param {string} _prefix UID / application prefix
* @returns {Number} Number of cached recordsets removed
*/
function _clearCache(_prefix)
{
// Find cached items for the prefix, we prefer to expire just within the app
var indexes = [];
for(var i = 0; i < window.localStorage.length; i++)
{
if(window.localStorage.key(i).indexOf('cache_'+_prefix) == 0)
{
indexes.push(i);
window.localStorage.removeItem(window.localStorage.key(i));
}
}
// Nothing for that prefix? Clear all cached data.
if(_prefix && indexes.length == 0)
{
return _clearCache('');
}
return indexes.length;
}
function parseServerResponse(_result, _callback, _context) function parseServerResponse(_result, _callback, _context)
{ {
// Check whether the result is valid // Check whether the result is valid
@ -61,9 +99,12 @@ egw.extend("data", egw.MODULE_APP_LOCAL, function (_app, _wnd) {
if (_result.order && _result.data) if (_result.order && _result.data)
{ {
// Assemble the correct order uids // Assemble the correct order uids
for (var i = 0; i < _result.order.length; i++) if(!(_result.order.length && _result.order[0] && _result.order[0].indexOf(_context.prefix) == 0))
{ {
_result.order[i] = UID(_result.order[i], _context.prefix); for (var i = 0; i < _result.order.length; i++)
{
_result.order[i] = UID(_result.order[i], _context.prefix);
}
} }
// Load all data entries that have been sent or delete them // Load all data entries that have been sent or delete them
@ -95,6 +136,37 @@ egw.extend("data", egw.MODULE_APP_LOCAL, function (_app, _wnd) {
} }
} }
// Check to see if we need long-term caching of the query and its results
if(window.localStorage && _context.prefix && cacheCallback[_context.prefix])
{
// Ask registered callbacks if we should cache this
for(var i = 0; i < cacheCallback[_context.prefix].length; i++)
{
var cc = cacheCallback[_context.prefix][i];
var cache_key = false
if(cache_key = cc.callback.call(cc.context, _context))
{
cache_key = 'cache_' + _context.prefix + '::' + cache_key;
try
{
window.localStorage.setItem(cache_key,JSON.stringify(_result));
}
catch (e)
{
egw.debug('warning', 'Tried to cache some data', cache_key, e);
// Maybe ran out of space? Free some up...
if(e.name == 'QuotaExceededError' // storage quota is exceeded, remove cached data
|| 'NS_ERROR_DOM_QUOTA_REACHED') // FF-name
{
var count = _clearCache(_context.prefix);
egw.debug('info', 'localStorage full, removed ' + count + ' stored datasets');
}
}
}
}
}
// Call the callback function and pass the calculated "order" array // Call the callback function and pass the calculated "order" array
// as well as the "total" count and the "timestamp" to the listener. // as well as the "total" count and the "timestamp" to the listener.
if (_callback) if (_callback)
@ -204,6 +276,29 @@ egw.extend("data", egw.MODULE_APP_LOCAL, function (_app, _wnd) {
knownUids.slice(typeof _queriedRange.start != "undefined" ? _queriedRange.start:0,KNOWN_UID_LIMIT); knownUids.slice(typeof _queriedRange.start != "undefined" ? _queriedRange.start:0,KNOWN_UID_LIMIT);
} }
// Check to see if we have long-term caching of the query and its results
if(window.localStorage && _context.prefix && cacheCallback[_context.prefix])
{
// Ask registered callbacks if we should cache this
for(var i = 0; i < cacheCallback[_context.prefix].length; i++)
{
var cc = cacheCallback[_context.prefix][i];
var cache_key = false
if(cache_key = cc.callback.call(cc.context, _context))
{
cache_key = 'cache_' + _context.prefix + '::' + cache_key;
var cached = window.localStorage.getItem(cache_key);
if(cached)
{
egw.debug('log', 'Data cached query: ' + cache_key + "\nprocessing...");
// Call right away with cached data. We'll still ask the server
// though.
parseServerResponse(JSON.parse(cached), _callback, _context);
}
}
}
}
var request = egw.json( var request = egw.json(
_app+".etemplate_widget_nextmatch.ajax_get_rows.etemplate", _app+".etemplate_widget_nextmatch.ajax_get_rows.etemplate",
[ [
@ -221,8 +316,59 @@ egw.extend("data", egw.MODULE_APP_LOCAL, function (_app, _wnd) {
true true
); );
request.sendRequest(); request.sendRequest();
} },
/**
* Turn on long-term client side cache of a particular request
* (cache the nextmatch query results) for fast, immediate response
* with old data.
*
* The request is still sent to the server, and the cache is updated
* with fresh data, and any needed callbacks are called again with
* the fresh data.
*
* @param {string} prefix UID / Application prefix should match the
* individual record prefix
* @param {function} callback A function that will analize the provided fetch
* parameters and return a reproducable cache key, or false to not cache
* the request.
* @param {object} context Context for callback function.
*/
dataCacheRegister: function(prefix, callback, context)
{
if(typeof cacheCallback[prefix] == 'undefined')
{
cacheCallback[prefix] = [];
}
cacheCallback[prefix].push({
callback: callback,
context: context
});
},
/**
* Unregister a previously registered cache callback
* @param {string} prefix UID / Application prefix should match the
* individual record prefix
* @param {function} [callback] Callback function to un-register. If
* omitted, all functions for the prefix will be removed.
*/
dataCacheUnregister: function(prefix, callback)
{
if(typeof callback != 'undefined')
{
for(var i = 0; i < cacheCallback[prefix].length; i++)
{
if(cacheCallback[prefix][i].callback == callback)
{
cacheCallback[prefix].splice(i,1);
return;
}
}
}
// Callback not provided or not found, reset by prefix
cacheCallback[prefix] = [];
}
}; };
}); });