You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
doushio/archive/dump.js

251 lines
5.4 KiB

/* Dumps the given thread to JSON.
* Not production-ready.
*/
var _ = require('../lib/underscore'),
async = require('async'),
caps = require('../server/caps'),
db = require('../db'),
etc = require('../etc'),
fs = require('fs'),
joinPath = require('path').join,
render = require('../server/render'),
util = require('util');
var DUMP_DIR = 'www/archive';
var AUTH_DUMP_DIR = 'authdump';
var DUMP_IDENT = {ip: '127.0.0.1', auth: 'dump'};
require('../server/amusement'); // load dice hook
function Dumper(reader, out) {
this.out = out;
this.reader = reader;
_.bindAll(this);
reader.on('thread', this.on_thread);
reader.on('post', this.on_post);
reader.on('endthread', this.on_endthread);
}
var D = Dumper.prototype;
D.on_thread = function (op_post) {
if (this.needComma) {
this.out.write(',\n');
this.needComma = false;
}
this.op = op_post.num;
this.out.write('[\n' + JSON.stringify(tweak_post(op_post)));
};
D.on_post = function (post) {
this.out.write(',\n' + JSON.stringify(tweak_post(post, this.op)));
};
D.on_endthread = function () {
this.out.write('\n]');
this.needComma = true;
this.op = null;
};
D.destroy = function () {
this.reader.removeListener('thread', this.on_thread);
this.reader.removeListener('post', this.on_post);
this.reader.removeListener('endthread', this.on_endthread);
this.reader = null;
this.out = null;
};
function AuthDumper(reader, out) {
Dumper.call(this, reader, out);
}
util.inherits(AuthDumper, Dumper);
var AD = AuthDumper.prototype;
AD.on_thread = function (post) {
this.out.write('{"ips":{');
if (post.num && post.ip) {
this.out.write('"'+post.num+'":'+JSON.stringify(post.ip));
this.needComma = true;
}
};
AD.on_post = function (post) {
if (post.num && post.ip) {
if (this.needComma)
this.out.write(',');
else
this.needComma = true;
this.out.write('"'+post.num+'":'+JSON.stringify(post.ip));
}
};
AD.on_endthread = function () {
this.out.write('}}');
this.needComma = false;
};
function tweak_post(post, known_op) {
post = _.clone(post);
/* thread-only */
if (typeof post.tags == 'string')
post.tags = db.parse_tags(post.tags);
if (typeof post.origTags == 'string')
post.origTags = db.parse_tags(post.origTags);
if (typeof post.hctr == 'string')
post.hctr = parseInt(post.hctr, 10);
if (typeof post.imgctr == 'string')
post.imgctr = parseInt(post.imgctr, 10);
/* post-only */
if (known_op == post.op)
delete post.op;
if (post.hideimg) {
delete post.image;
delete post.hideimg;
}
if (post.body == '')
delete post.body;
/* blacklisting is bad... */
delete post.ip;
return post;
}
function dump_thread(op, board, ident, outputs, cb) {
if (!caps.can_access_board(ident, board))
return cb(404);
if (!caps.can_access_thread(ident, op))
return cb(404);
var yaku = new db.Yakusoku(board, ident);
var reader = new db.Reader(yaku);
reader.get_thread(board, op, {});
reader.once('nomatch', function () {
cb(404);
yaku.disconnect();
});
reader.once('redirect', function (op) {
cb('redirect', op);
yaku.disconnect();
});
reader.once('begin', function (preThread) {
var dumper = new Dumper(reader, outputs.json);
var authDumper = new AuthDumper(reader, outputs.auth);
var out = outputs.html;
render.write_thread_head(out, '', board, op, {
subject: preThread.subject,
});
var fakeReq = {ident: ident, headers: {}};
var opts = {fullPosts: true, board: board};
render.write_thread_html(reader, fakeReq, out, opts);
reader.once('end', function () {
outputs.json.write('\n');
outputs.auth.write('\n');
render.write_page_end(out, ident, true);
yaku.disconnect();
cb(null);
});
});
function on_err(err) {
yaku.disconnect();
cb(err);
}
reader.once('error', on_err);
yaku.once('error', on_err);
}
function close_stream(stream, cb) {
if (!stream.writable)
return cb(null);
if (stream.write(''))
close();
else
stream.once('drain', close);
function close() {
// deal with process.stdout not being closable
try {
stream.destroySoon(function (err) {
if (cb)
cb(err);
cb = null;
});
}
catch (e) {
if (cb)
cb(null);
cb = null;
}
}
}
function load_state(cb) {
async.series([
etc.checked_mkdir.bind(null, DUMP_DIR),
etc.checked_mkdir.bind(null, AUTH_DUMP_DIR),
require('../server/state').reload_hot_resources,
db.track_OPs,
], cb);
}
if (require.main === module) (function () {
var op = parseInt(process.argv[2], 10), board = process.argv[3];
if (!op) {
console.error('Usage: node upkeep/dump.js <thread>');
process.exit(-1);
}
console.log('Loading state...');
load_state(function (err) {
if (err)
throw err;
if (!board)
board = db.first_tag_of(op);
if (!board) {
console.error(op + ' has no tags.');
process.exit(-1);
}
console.log('Dumping thread...');
var base = joinPath(DUMP_DIR, op.toString());
var authBase = joinPath(AUTH_DUMP_DIR, op.toString());
var outputs = {
auth: fs.createWriteStream(authBase + '.json'),
json: fs.createWriteStream(base + '.json'),
html: fs.createWriteStream(base + '.html'),
};
dump_thread(op, board, DUMP_IDENT, outputs, function (err) {
if (err)
throw err;
var streams = [];
for (var k in outputs)
streams.push(outputs[k]);
async.each(streams, close_stream, quit);
});
});
function quit() {
// crappy flush for stdout (can't close it)
if (process.stdout.write(''))
process.exit(0);
else
process.stdout.on('drain', function () {
process.exit(0);
});
}
})();