/********************************* * 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;