Separated QCore into a separate .js file -- should probably go into a lib directory. index.html favicon.png changed to /favicon.png. Added tmp directory creation.

master
kts of kettek 2016-01-27 02:24:12 -08:00
parent fcfe8aac45
commit 2144e6d877
3 changed files with 300 additions and 285 deletions

261
QCore.js 100644
View File

@ -0,0 +1,261 @@
/*********************************
* QCore *
*********************************/
var http = require('http');
var fs = require('fs');
var m_path = require('path');
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 = {};
this.http = http.createServer(function(req, res) {
var last = req.url.lastIndexOf("/");
var act = req.url.substr(last+1);
if (acts[act]) {
req.url = req.url.substr(0, last); // rewrite the url without the act
} else {
act = "";
}
req.fields = {};
req.files = {};
if (req.method == 'POST') {
var form = new formidable.IncomingForm();
form.multiples = true;
form.uploadDir = process.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 {
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('./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;

View File

@ -1,7 +1,7 @@
<!DOCTYPE html>
<html smlns="http://www.w3.org/1999/xhtml" lang="en-US">
<head>
<link rel="icon" type="image/png" href="favicon.png">
<link rel="icon" type="image/png" href="/favicon.png">
<title>qwiki @@PAGE@@</title>
<meta charset="UTF-8" />
<link href='https://fonts.googleapis.com/css?family=Oswald|Roboto+Slab:300,700|Roboto+Mono' rel='stylesheet' type='text/css'>

322
index.js
View File

@ -1,133 +1,47 @@
#!/usr/bin/env node
var http = require('http');
var fs = require('fs');
var m_path = require('path');
var util = require('util');
var formidable = require('formidable');
var QCore = require('./QCore.js');
/* qwiki
Node.js based Implementation of the proposed qwiki document
*/
/*********************************
* QCore *
/*********************************
* QWiki *
*********************************/
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 = {};
this.http = http.createServer(function(req, res) {
var last = req.url.lastIndexOf("/");
var act = req.url.substr(last+1);
if (acts[act]) {
req.url = req.url.substr(0, last); // rewrite the url without the act
var QWiki = function() {
QCore.call(this);
// make cache directory
try {
var stats = fs.statSync('cache');
if (!stats.isDirectory()) {
console.log('error, "cache" is not a directory');
}
} catch (err) {
if (err.code == 'ENOENT') {
fs.mkdirSync('cache');
console.log('Created "cache/" -- this is where processed wiki pages are stored as HTML');
} else {
act = "";
console.log('error while loading cache');
}
req.fields = {};
req.files = {};
if (req.method == 'POST') {
var form = new formidable.IncomingForm();
form.multiples = true;
form.uploadDir = process.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;
}
// make tmp directory
try {
var stats = fs.statSync('tmp');
if (!stats.isDirectory()) {
console.log('error, "tmp" is not a directory');
}
} catch (err) {
if (err.code == 'ENOENT') {
fs.mkdirSync('tmp');
console.log('Created "tmp/" -- this is where uploaded files are temporarily stored');
} else {
for (var i = 0, keys = Object.keys(acts[act]); i < keys.length; i++) {
acts[act][i](req, res);
}
console.log('error while loading tmp directory');
}
});
}
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('./acts/'+files[i])(self);
}
}
(typeof cb !== 'undefined' ? cb() : '');
});
};
fs.watch('index.html', this.loadIndex);
};
QCore.prototype.defaults = {};
QCore.prototype.listen = function(port) {
this.loadActs();
this.loadIndex();
this.loadWikiIndex();
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.loadWikiIndex = function() {
}; util.inherits(QWiki, QCore);
QWiki.prototype.loadWikiIndex = function() {
var self = this;
try {
this.wiki_index = JSON.parse(fs.readFileSync('index.json'));
@ -137,7 +51,7 @@ QCore.prototype.loadWikiIndex = function() {
}
var wiki_index = this.wiki_index;
console.log('loaded indices for ' + Object.keys(this.wiki_index.pages).length + ' pages');
readFiles('wiki', function(err, file, is_dir) {
QCore.prototype.readFiles('wiki', function(err, file, is_dir) {
var wiki_file = file.replace('wiki/', '');
if (is_dir) {
if (wiki_file in wiki_index.pages) {
@ -146,7 +60,7 @@ QCore.prototype.loadWikiIndex = function() {
wiki_index.pages[wiki_file] = { dir: true };
}
} else {
if (self.getExt(file) == 'qwk') {
if (QCore.prototype.getExt(file) == 'qwk') {
if (wiki_file in wiki_index.pages) {
} else {
var name = wiki_file.slice(0, -4);
@ -161,83 +75,6 @@ QCore.prototype.loadWikiIndex = function() {
}
});
}
QCore.prototype.parsePage = function(zone, fallback_zone, req, res) {
var qparse = new 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);
};
/*********************************
* QParse *
*********************************/
var QParse = function() {};
QParse.prototype.indices = {};
QParse.prototype.offset = 0;
QParse.prototype.current = null;
QParse.prototype.next = null;
/*********************************
* QWiki *
*********************************/
var QWiki = function() {
QCore.call(this);
try {
var stats = fs.statSync('cache');
if (!stats.isDirectory()) {
console.log('error, "cache" is not a directory');
}
} catch (err) {
if (err.code == 'ENOENT') {
fs.mkdirSync('cache');
console.log('Created "cache/" -- this is where processed wiki pages are stored as HTML');
} else {
console.log('error while loading cache');
}
}
}; util.inherits(QWiki, QCore);
QWiki.prototype.formats = {
raw: {
name: 'raw',
@ -270,8 +107,8 @@ QWiki.prototype.convertAndSave = function(path, name, source, cb) {
}
console.log('converting to ' + name + ' with path ' + path);
var converted = this.formats[name].convert(source);
r_mkdir(m_path.dirname('cache'+path+'.html'), 0777, function() {
fs.writeFile('cache'+path+'.html', converted, function(err) {
QCore.prototype.r_mkdir(m_path.dirname('cache/'+path+'.html'), 0777, function() {
fs.writeFile('cache/'+path+'.html', converted, function(err) {
if (err) {
console.log('error writing "cache'+ path + '.html"');
} else {
@ -299,7 +136,7 @@ QWiki.prototype.createCache = function(wiki_page, source, cb) {
var path = 'cache/' + wiki_page + '.html';
r_mkdir(m_path.dirname(path), 0777, function() {
QCore.prototype.r_mkdir(m_path.dirname(path), 0777, function() {
fs.writeFile(path, converted, function(err) {
if (err) {
console.log('createCache: error writing "' + path + '"');
@ -315,7 +152,7 @@ QWiki.prototype.deletePage = function(wiki_page, cb) {
};
QWiki.prototype.savePage = function(wiki_page, content, cb) {
var path = 'wiki/' + wiki_page + '.qwk';
r_mkdir(m_path.dirname(path), 0777, function() {
QCore.prototype.r_mkdir(m_path.dirname(path), 0777, function() {
console.log('savePage: writing ' + path);
qwiki.deleteCache(wiki_page, function() {
fs.writeFile(path, content, function(err) {
@ -328,7 +165,6 @@ QWiki.prototype.savePage = function(wiki_page, content, cb) {
QWiki.prototype.deleteFile = function(file, cb) {
fs.unlink('wiki/'+file, cb);
};
/*********************************
* qwiki instance *
*********************************/
@ -354,88 +190,6 @@ qwiki.addFormat('md', 'Markdown', function(source) {
qwiki.setDefault('format', 'md');
var 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.readFile = readFile;
var 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()) {
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;
};
var r_mkdir = function(dir, mode, cb) {
console.log('creating '+dir);
fs.mkdir(dir, mode, function(err) {
if (err && err.code == 'ENOENT') {
r_mkdir(m_path.dirname(dir), mode, r_mkdir.bind(this, dir, mode, cb));
} else if (typeof cb !== 'undefined') {
cb(err);
}
});
};
QCore.prototype.r_mkdir = r_mkdir;
var port = 8080;
process.argv.forEach(function (val, index, array) {
var parts = val.split('=');
@ -454,4 +208,4 @@ process.argv.forEach(function (val, index, array) {
}
});
qwiki.listen(port);
qwiki.listen(port, function() { qwiki.loadWikiIndex(); });