¶ ResourceManagerCopyright(c) 2015 Stefano Balietti MIT Licensed Handles loading, caching of static resources and templates from file system |
"use strict";
module.exports = ResourceManager;
var fs = require('fs');
var jade = require('jade');
var games = {};
|
TODO: support different template engines. |
|
¶ ResourceManager constructorConstructs a new instance of ResourceManager Params
servernode
ServerNode
Instance of servernode
|
function ResourceManager(servernode) {
this.servernode = servernode;
|
Testing. |
this.games = games;
|
Adding servernode resources caching. |
this.addGame('/', servernode.rootDir + '/');
}
|
¶ ResourceManager methods |
|
¶ ResourceManager.addGameSetup an empty data structure for holding game info Params
gameName
string
The name of the game as it appears in http requests
rootDir
string
The path of the root directory of the game
|
ResourceManager.prototype.addGame = function(gameName, rootDir) {
if ('string' !== typeof gameName) {
throw new TypeError('ResourceManager.addGame: ' +
'gameName must be string.');
}
if (rootDir && 'string' !== typeof rootDir) {
throw new Error('ResourceManager.addGame: ' +
'rootDir must be string or undefined.');
}
if (!rootDir) {
rootDir = this.servernode.resolveGameDir(gameName);
if (!rootDir) {
throw new Error('ResourceManager.addGame: could not find ' +
'rootDir of game: ' + gameName + '.');
}
}
games[gameName] = {
rootDir: rootDir,
cachePublic: {},
cacheTemplate: {},
cacheContext: {},
contextCallbacks: {}
};
};
|
¶ ResourceManager.createAliasCreates an alias for a cached game. Params
alias
string
The name of the alias
gameName
string
The name of the game
|
ResourceManager.prototype.createAlias = function(alias, gameName) {
if ('string' !== typeof alias) {
throw new TypeError('ResourceManager.createAlias: alias ' +
'must be string.');
}
if ('string' !== typeof gameName) {
throw new TypeError('ResourceManager.createAlias: ' +
'gameName must be string.');
}
if (!games[gameName]) {
throw new Error('ResourceManager.createAlias: ' +
'game not found: ' + gameName + '.');
}
games[alias] = games[gameName];
};
|
¶ ResourceManager.getFromPublicReturns the content of a file from a game's public directory If the requested resource was previously cached, it executes the callback immediately, otherwise it tries to load it from file system and cache it, if the caching option is enabled. Trailing slash is always removed. Params
alias
string
The name of the alias
gameName
string
The name of the game
cb
function
Callback to execute with the content of
the requested file, or
null if file is not found.
|
ResourceManager.prototype.getFromPublic = function(gameName, file, cb) {
var cachedFile, filePath;
var that;
if (file.lastIndexOf('\/') === (file.length - 1)) {
|
Removing the trailing slash because it creates: Error: ENOTDIR in fetching the file. |
file = file.substring(0, file.length - 1);
}
cachedFile = games[gameName].cachePublic[file];
|
File was previously found and cached. |
if (cachedFile) {
cb(cachedFile);
return;
}
|
File was previously looked up and not found. |
else if (cachedFile === false) {
cb(null);
return;
}
|
Build filePath to file in public directory. |
filePath = games[gameName].rootDir + 'public/' + file;
that = this;
|
Checks if exists in 'public/' or as view. |
fs.exists(filePath, function(exists) {
|
Exists in public, cache it, serve it. |
if (exists) {
fs.readFile(filePath, 'utf8', function(err, data) {
|
Cache it. |
if (that.cacheEnabled) that.inPublic(gameName, file, data);
cb(data);
});
}
else {
if (that.cacheEnabled) pager.inPublic(gameName, file, false);
cb(null);
}
});
};
|
¶ ResourceManager.getFromViewsReturns the content of a file from a game's public directory If the requested resource was previously cached, it executes the callback immediately, otherwise it tries to load it from file system and cache it, if the caching option is enabled. Trailing slash is always removed. Params
alias
string
The name of the alias
gameName
string
The name of the game
|
|
TODO: Fix. |
ResourceManager.prototype.getFromViews = function(gameName, file, cb,
gameSettings, headers) {
var basename, gameDir;
var templatePath, contextPath;
var cachedTemplate, cachedContextCb;
var view, context;
var that;
if (file.lastIndexOf('\/') === (file.length - 1)) {
|
Removing the trailing slash because it creates: Error: ENOTDIR in fetching the file. |
file = file.substring(0, file.length - 1);
}
|
Check if it a template. |
basename = file.substr(0, file.lastIndexOf('.'));
gameDir = games[gameName].rootDir;
|
Instantiate templates, if available.
|
|
Matches: xx/xx/xx/xx/xx/ |
if (basename.match(/^[^\/]*\/.*$/)) {
templatePath = gameDir + 'views/templates/' +
basename.split('/')[1] + '.jade';
}
else {
templatePath = gameDir + 'views/templates/' + basename + '.jade';
}
contextPath = gameDir + 'views/contexts/' + basename + '.js';
|
Info about previous requests to the template file. templateFound = pager.inTemplates(gameName, templatePath); |
|
Get from cache. |
cachedTemplate = games[gameName].cacheTemplate[templatePath];
cachedContextCb = games[gameName].contextCallbacks[contextPath];
|
Template not existing. |
if (cachedTemplate === false) {
cb(null);
return;
}
|
cachedTemplate and cachedContextCb are loaded together so we can check either to know whether they have been loaded once. |
|
Template existing (cached), and context callback existing or not. |
if (cachedTemplate) {
if (cachedContextCb) context = contextCb(gameSettings, headers);
view = jade.render(cachedTemplate, context);
cb(view);
return;
}
that = this;
fs.exists(templatePath, function(exists) {
if (!exists) {
if (that.cacheEnabled) {
that.inTemplates(gameName, templatePath, false);
}
cb(null);
}
else {
fs.readFile(templatePath, 'utf8', function(err, data) {
|
Cache it. |
if (that.cacheEnabled) {
that.inTemplates(gameName, templatePath, data);
}
fs.exists(contextPath, function(exists) {
var logger;
if (!exists) {
if (that.cacheEnabled) {
games[gameName]
.contextCallbacks[contextPath] = false;
}
}
else {
|
Function or FALSE (on error). Errors caught by the function. |
logger = that.servernode.logger;
cachedContextCb = loadContextCallback(contextPath,
logger);
if (that.cacheEnabled) {
games[gameName].contextCallbacks[contextPath] =
cachedContextCb;
}
}
if (cachedContextCb) {
context = cachedContextCb(gameSettings, headers);
}
|
TODO: If there is no callback we could cache the output of the rendered template. |
view = jade.render(data, context, function(a) {
console.log(a)
|
Context callback might be existing or not. |
cb(a);
});
});
});
}
});
};
|
¶ ResourceManager.inPublicSets/Gets whether a file was previously found in If No type-checking for increasing speed. Params
gameName
string
The name of the game
path
string
The path to the file in
/public/
found
boolean
undefined
The state
Returns
boolean
undefined
TRUE, if previously found, FALSE, if not found,
undefined if no previous record was set
|
ResourceManager.prototype.inPublic = function(gameName, path, found) {
if ('undefined' === typeof found) return games[gameName].cachePublic[path];
games[gameName].cachePublic[path] = found;
return found;
};
|
¶ ResourceManager.inTemplatesSets/Gets whether a file was previously found in If No type-checking for increasing speed. Params
gameName
string
The name of the game
path
string
The path to the file in
/public/
found
boolean
undefined
The state
Returns
boolean
undefined
TRUE, if previously found, FALSE, if not found,
undefined if no previous record was set
|
ResourceManager.prototype.inTemplates = function(gameName, path, found) {
if ('undefined' === typeof found) {
return games[gameName].cacheTemplate[path];
}
games[gameName].cacheTemplate[path] = found;
return found;
};
|
¶ ResourceManager.cacheContextStores in memory a copy of a fully instantiated context object No type-checking for increasing speed. Params
gameName
string
The name of the game
path
string
The path to the file in
/views/templates/
context
object
The context object
See
ResourceManager.getContext
|
ResourceManager.prototype.cacheContext = function(gameName, path, context) {
games[gameName].cacheContext[path] = context;
};
|
¶ ResourceManager.cacheContextCallbackStores in memory a copy of a context callback No type-checking for increasing speed. Params
gameName
string
The name of the game
path
string
The path to the file in
/views/contexts/
cb
function
The context callback
|
ResourceManager.prototype.cacheContextCallback = function(gameName, path, cb) {
games[gameName].contextCallbacks[path] = cb;
};
|
¶ ResourceManager.getContextReturns a context object instantiated from a context callback No type-checking for increasing speed. Params
gameName
string
The name of the game
contextPath
string
The path to the file in
/views/contexts/
gameSettings
object
Game settings to pass to the context callback
headers
object
Headers of the connection to pass to the
context callback
Returns
object
context The fully instantiated context object
|
ResourceManager.prototype.getContext = function(gameName, contextPath,
gameSettings, headers) {
var context, cb;
if (!games[gameName]) return null;
cb = games[gameName].contextCallbacks[contextPath];
if (!cb) return null;
context = cb(gameSettings, headers);
return context;
};
|
¶ ResourceManager.getSandBoxReturns a object containing only safe methods Useful when you need to give game developer access to the resource manager, and you need to maintain game separation. No type-checking for increasing speed. Params
gameName
string
The name of the game
gamePath
string
The path to game directory
Returns
object
A sandboxed version of the resource manager
|
ResourceManager.prototype.getSandBox = function(gameName, gamePath) {
var sb = {};
sb.modifyContext = function(contextPath, cb) {
var context, cb, fullPath;
if ('string' !== typeof contextPath) {
throw new TypeError('ResourceManager.modifyContext: contextPath ' +
'must be string.');
}
if ('function' !== typeof cb) {
throw new TypeError('ResourceManager.modifyContext: cb must be ' +
'function.');
}
fullPath = gamePath + 'views/contexts/' + contextPath;
games[gameName].contextCallbacks[fullPath] = cb;
};
return sb;
};
|
¶ Helper methods |
|
¶ loadContextCallbackLoads the context callback from file system and catches errors Params
contextPath
string
The path to load
logger
Logger
The ServerNode logger
Returns
function
boolean
The context callback or FALSE in case of error
|
function loadContextCallback(contextPath, logger) {
var cb;
try {
cb = require(contextPath);
if ('function' !== typeof cb) {
throw new TypeError('loadContextCallback: context callback ' +
'must be function.');
}
}
catch(e) {
logger.error('loadContextCallback: error loading ' +
'context file: ' + contextPath + ' ' + e.stack);
return false;
}
return cb;
}
|