280 lines
8.9 KiB
JavaScript
280 lines
8.9 KiB
JavaScript
/*********************************
|
|
* QCore *
|
|
*********************************/
|
|
var http = require('http');
|
|
var fs = require('fs');
|
|
var m_path = require('path');
|
|
var m_url = require('url');
|
|
var formidable = require('formidable');
|
|
|
|
var QCore = function() {
|
|
var acts = {};
|
|
this.acts = acts;
|
|
self = this;
|
|
this.mimetypes = {};
|
|
this.index = null;
|
|
this.index_parts = [];
|
|
this.last_part = { start: 0, end: 0 };
|
|
|
|
this.etags = {}; // etags cache, obj: uri { etag: '...', timestamp: }
|
|
|
|
this.res_cache = {}; // resource cache ('/view.png' => '03xc345');
|
|
this.rules = {};
|
|
|
|
// TODO: make file calls explicitly refer to this.cwd
|
|
this.cwd = process.cwd();
|
|
|
|
this.http = http.createServer(function(req, res) {
|
|
// parse the URL into proper segments and attach these to the request
|
|
var purl = m_url.parse(req.url, true);
|
|
req.hash = purl.hash; req.pathname = purl.pathname;
|
|
req.path = purl.path; req.query = purl.query;
|
|
// set up req.area from pathname (pathname - act)
|
|
var last = req.pathname.lastIndexOf("/");
|
|
var act = req.pathname.substr(last+1);
|
|
if (acts[act]) {
|
|
req.area = unescape(req.pathname.substr(0, last)); // area is pathname without the act
|
|
} else {
|
|
act = "";
|
|
req.area = unescape(req.pathname);
|
|
}
|
|
req.act = act; // attach act to req
|
|
req.fields = {}; // fields from POST
|
|
req.files = {}; // files from POST
|
|
if (req.method == 'POST') { // POST handler
|
|
var form = new formidable.IncomingForm();
|
|
form.multiples = true;
|
|
form.uploadDir = self.cwd+'/tmp';
|
|
form.parse(req, function(err, fields, files) {
|
|
// TODO: handle err
|
|
req.fields = fields;
|
|
req.files = files;
|
|
for (var i = 0, keys = Object.keys(acts[act]); i < keys.length; i++) {
|
|
acts[act][i](req, res);
|
|
}
|
|
});
|
|
return;
|
|
} else { // GET and otherwise
|
|
// if QRULE query is passed, we attempt to read only the requested rule
|
|
if ('QRULE' in req.query) {
|
|
if (typeof self.rules[act] !== 'undefined' && self.rules[act][req.query['QRULE']] !== 'undefined') {
|
|
self.rules[act][req.query['QRULE']](req, res, null, function() { res.end()});
|
|
}
|
|
} else {
|
|
for (var i = 0, keys = Object.keys(acts[act]); i < keys.length; i++) {
|
|
acts[act][i](req, res);
|
|
}
|
|
}
|
|
}
|
|
});
|
|
|
|
this.act = function(act, callback) {
|
|
if (typeof acts[act] === 'undefined') {
|
|
acts[act] = [];
|
|
}
|
|
acts[act].push(callback);
|
|
};
|
|
this.rule = function(zone, replace, callback) {
|
|
if (typeof this.rules[zone] === 'undefined') {
|
|
this.rules[zone] = {};
|
|
}
|
|
this.rules[zone][replace] = callback;
|
|
};
|
|
var fs_timeout; // timer to bypass multiple fs.watch triggering due to unstable API
|
|
this.loadIndex = function(event, filename) {
|
|
if (fs_timeout) return;
|
|
fs_timeout = setTimeout(function() { fs_timeout = null; }, 100);
|
|
var reg = /@@[a-z]+@@/ig;
|
|
fs.readFile('index.html', function(err, data) {
|
|
if (err) throw err;
|
|
self.index = data;
|
|
var search;
|
|
self.index_parts = [];
|
|
while ((search = reg.exec(data)) !== null) {
|
|
self.index_parts.push({ replace: search[0], start: search.index, end: reg.lastIndex });
|
|
}
|
|
});
|
|
};
|
|
// TODO: loadActs should allow for dynamic updates by using fs.watch and clearing the require cache
|
|
this.loadActs = function(cb) {
|
|
var self = this;
|
|
fs.readdir('acts', function(err, files) {
|
|
if (err) throw err;
|
|
for (var i = 0; i < files.length; i++) {
|
|
var ext = self.getExt(files[i]);
|
|
if (ext == 'js') {
|
|
console.log('Loading act: ' + files[i]);
|
|
require(self.cwd+'/acts/'+files[i])(self);
|
|
}
|
|
}
|
|
(typeof cb !== 'undefined' ? cb() : '');
|
|
});
|
|
};
|
|
fs.watch('index.html', this.loadIndex);
|
|
};
|
|
QCore.prototype.defaults = {};
|
|
QCore.prototype.listen = function(port, cb) {
|
|
this.loadActs();
|
|
this.loadIndex();
|
|
if (typeof cb !== 'undefined') cb();
|
|
try {
|
|
this.http.listen(port);
|
|
} catch(err) {
|
|
console.log('uhoh, could not listen on port '+port);
|
|
console.log(err);
|
|
}
|
|
};
|
|
QCore.prototype.setDefault = function(key, value) {
|
|
this.defaults[key] = value;
|
|
};
|
|
QCore.prototype.getDefault = function(key) {
|
|
if (key in this.defaults) {
|
|
return this.defaults[key];
|
|
}
|
|
return '';
|
|
};
|
|
QCore.prototype.addMIMEtype = function(ext, mimetype) {
|
|
if (typeof this.mimetypes[ext] === 'undefined') this.mimetypes[ext] = [];
|
|
this.mimetypes[ext].push(mimetype);
|
|
};
|
|
QCore.prototype.getMIMEtype = function(ext) {
|
|
if (this.mimetypes[ext]) return this.mimetypes[ext][0];
|
|
return '';
|
|
};
|
|
QCore.prototype.parsePage = function(zone, fallback_zone, req, res) {
|
|
var qparse = new QCore.prototype.QParse();
|
|
qparse.indices = this.index_parts.slice(); // clone it TODO: just make an indices (number-only) array
|
|
|
|
var context = this;
|
|
var cb = function(qparse, callback) {
|
|
res.write(context.index.slice(qparse.offset, qparse.current.start));
|
|
if (typeof context.rules[zone] === 'undefined') { zone = fallback_zone; }
|
|
if (typeof context.rules[zone] === 'undefined') { zone = ''; }
|
|
if (typeof context.rules[zone] !== 'undefined') {
|
|
if (typeof context.rules[zone][qparse.current.replace] !== 'undefined') {
|
|
context.rules[zone][qparse.current.replace](req, res, qparse, callback);
|
|
} else {
|
|
// fallback action
|
|
if (typeof context.rules[fallback_zone][qparse.current.replace] !== 'undefined') {
|
|
context.rules[fallback_zone][qparse.current.replace](req, res, qparse, callback);
|
|
// default action
|
|
} else if (typeof context.rules[''][qparse.current.replace] !== 'undefined') {
|
|
context.rules[''][qparse.current.replace](req, res, qparse, callback);
|
|
} else {
|
|
callback();
|
|
}
|
|
}
|
|
}
|
|
};
|
|
|
|
var series = function(qparse) {
|
|
if (qparse.current) {
|
|
cb(qparse, function(next) {
|
|
if (qparse.next) {
|
|
res.write(context.index.slice(qparse.current.end, qparse.next.start));
|
|
qparse.offset = qparse.next.end;
|
|
qparse.current = qparse.indices.shift();
|
|
qparse.next = qparse.indices[0];
|
|
series(qparse);
|
|
} else {
|
|
res.write(context.index.slice(qparse.current.end));
|
|
qparse.current = qparse.indices.shift();
|
|
qparse.next = qparse.indices[0];
|
|
series(qparse);
|
|
}
|
|
});
|
|
} else {
|
|
res.end();
|
|
}
|
|
};
|
|
qparse.current = qparse.indices.shift();
|
|
qparse.next = qparse.indices[0];
|
|
series(qparse);
|
|
};
|
|
|
|
QCore.prototype.readFile = function(stream, path, cb) {
|
|
fs.exists(path, function(exists) {
|
|
if (exists) {
|
|
var rs = fs.createReadStream(path);
|
|
rs.on('data', function(chunk) {
|
|
stream.write(chunk);
|
|
});
|
|
rs.on('end', function() {
|
|
cb('EOF');
|
|
});
|
|
rs.on('error', function(err) {
|
|
cb('ERR', err);
|
|
});
|
|
} else {
|
|
cb('FNF'); // file not found
|
|
}
|
|
});
|
|
};
|
|
QCore.prototype.readFiles = function(dir, callback) {
|
|
fs.readdir(dir, function(err, files) {
|
|
files.forEach(function(file) {
|
|
var stat_file = dir+'/'+file
|
|
fs.stat(stat_file, function(err, stats) {
|
|
if (stats.isDirectory()) {
|
|
QCore.prototype.readFiles(stat_file, callback);
|
|
callback(err, stat_file, true);
|
|
} else if (stats.isFile()) {
|
|
callback(err, stat_file, false);
|
|
}
|
|
});
|
|
});
|
|
});
|
|
};
|
|
QCore.prototype.readDirectory = function(dir, callback) {
|
|
var all_files = [];
|
|
fs.readdir(dir, function(err, files) {
|
|
if (err) {
|
|
callback(err, null);
|
|
return;
|
|
}
|
|
var len = files.length;
|
|
var total = 0;
|
|
if (len == 0) callback(err, all_files);
|
|
files.forEach(function(file, index) {
|
|
var stat_file = dir + '/' + file;
|
|
fs.stat(stat_file, function(err, stats) {
|
|
if (stats.isDirectory()) {
|
|
all_files.push({'is_dir': true, 'name': file});
|
|
} else if (stats.isFile()) {
|
|
all_files.push({'is_dir': false, 'name': file});
|
|
}
|
|
if (++total == len) {
|
|
callback(err, all_files);
|
|
}
|
|
});
|
|
});
|
|
});
|
|
};
|
|
QCore.prototype.getExt = function(file) {
|
|
if (typeof file === 'undefined') return '';
|
|
var last = file.lastIndexOf(".");
|
|
if (last == -1) return '';
|
|
var ext = file.substr(last+1);
|
|
return ext;
|
|
};
|
|
QCore.prototype.r_mkdir = function(dir, mode, cb) {
|
|
console.log('creating '+dir);
|
|
fs.mkdir(dir, mode, function(err) {
|
|
if (err && err.code == 'ENOENT') {
|
|
QCore.r_mkdir(m_path.dirname(dir), mode, QCore.r_mkdir.bind(this, dir, mode, cb));
|
|
} else if (typeof cb !== 'undefined') {
|
|
cb(err);
|
|
}
|
|
});
|
|
};
|
|
/*********************************
|
|
* QParse *
|
|
*********************************/
|
|
QCore.prototype.QParse = function() {};
|
|
QCore.prototype.QParse.prototype.indices = {};
|
|
QCore.prototype.QParse.prototype.offset = 0;
|
|
QCore.prototype.QParse.prototype.current = null;
|
|
QCore.prototype.QParse.prototype.next = null;
|
|
|
|
module.exports = QCore;
|