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/imager/index.js

228 lines
5.4 KiB

var async = require('async'),
config = require('./config'),
child_process = require('child_process'),
db = require('./db'),
etc = require('../etc'),
fs = require('fs'),
hooks = require('../hooks'),
path = require('path'),
winston = require('winston');
exports.Onegai = db.Onegai;
exports.config = config;
var image_attrs = ('src thumb dims size MD5 hash imgnm spoiler realthumb vint'
+ ' apng mid audio video duration').split(' ');
exports.image_attrs = image_attrs;
exports.send_dead_image = function (kind, filename, resp) {
filename = dead_path(kind, filename);
var stream = fs.createReadStream(filename);
stream.once('error', function (err) {
if (err.code == 'ENOENT') {
resp.writeHead(404);
resp.end('Image not found');
}
else {
winston.error(err);
resp.end();
}
});
stream.once('open', function () {
var h = {
'Cache-Control': 'no-cache, no-store',
'Expires': 'Thu, 01 Jan 1970 00:00:00 GMT',
};
try {
h['Content-Type'] = require('mime').lookup(filename);
} catch (e) {}
resp.writeHead(200, h);
stream.pipe(resp);
});
};
hooks.hook_sync('extractPost', function (post) {
if (!is_image(post))
return;
var image = {};
image_attrs.forEach(function (key) {
if (key in post) {
image[key] = post[key];
delete post[key];
}
});
if (image.dims.split)
image.dims = image.dims.split(',').map(parse_number);
image.size = parse_number(image.size);
delete image.hash;
post.image = image;
});
function parse_number(n) {
return parseInt(n, 10);
}
hooks.hook_sync('inlinePost', function (info) {
var post = info.dest, image = info.src.image;
if (!image)
return;
image_attrs.forEach(function (key) {
if (key in image)
post[key] = image[key];
});
});
function publish(alloc, cb) {
var mvs = [];
var haveComp = alloc.tmps.comp && alloc.image.realthumb;
for (var kind in alloc.tmps) {
var src = media_path('tmp', alloc.tmps[kind]);
// both comp and thumb go in thumb/
var destDir = (kind == 'comp') ? 'thumb' : kind;
// hack for stupid thumb/realthumb business
var destKey = kind;
if (haveComp) {
if (kind == 'thumb')
destKey = 'realthumb';
else if (kind == 'comp')
destKey = 'thumb';
}
var dest = media_path(destDir, alloc.image[destKey]);
mvs.push(etc.cpx.bind(etc, src, dest));
}
async.parallel(mvs, cb);
}
function validate_alloc(alloc) {
if (!alloc || !alloc.image || !alloc.tmps)
return;
for (var dir in alloc.tmps) {
var fnm = alloc.tmps[dir];
if (!/^[\w_]+$/.test(fnm)) {
winston.warn("Suspicious filename: "
+ JSON.stringify(fnm));
return;
}
}
return true;
}
hooks.hook("buryImage", function (info, callback) {
if (!info.src)
return callback(null);
/* Just in case */
var m = /^\d+\w*\.\w+$/;
if (!info.src.match(m))
return callback(etc.Muggle('Invalid image.'));
var mvs = [mv.bind(null, 'src', info.src)];
function try_thumb(path, t) {
if (!t)
return;
if (!t.match(m))
return callback(etc.Muggle('Invalid thumbnail.'));
mvs.push(mv.bind(null, path, t));
}
try_thumb('thumb', info.thumb);
try_thumb('thumb', info.realthumb);
try_thumb('mid', info.mid);
async.parallel(mvs, callback);
function mv(p, nm, cb) {
etc.movex(media_path(p, nm), dead_path(p, nm), cb);
}
});
function is_image(image) {
return image && (image.src || image.vint);
}
function media_path(dir, filename) {
return path.join(config.MEDIA_DIRS[dir], filename);
}
exports.media_path = media_path;
function dead_path(dir, filename) {
return path.join(config.MEDIA_DIRS.dead, dir, filename);
}
function make_dir(base, key, cb) {
var dir = base ? path.join(base, key) : config.MEDIA_DIRS[key];
etc.checked_mkdir(dir, cb);
}
exports._make_media_dir = make_dir;
exports.make_media_dirs = function (cb) {
var keys = ['src', 'thumb', 'vint', 'dead'];
if (!is_standalone())
keys.push('tmp');
if (config.EXTRA_MID_THUMBNAILS)
keys.push('mid');
async.forEach(keys, make_dir.bind(null, null), function (err) {
if (err)
return cb(err);
var dead = config.MEDIA_DIRS.dead;
var keys = ['src', 'thumb'];
if (config.EXTRA_MID_THUMBNAILS)
keys.push('mid');
async.forEach(keys, make_dir.bind(null, dead), cb);
});
}
exports.serve_image = function (req, resp) {
var m = /^\/(src|thumb|mid|vint)(\/\d+\.\w+)$/.exec(req.url);
if (!m)
return false;
var root = config.MEDIA_DIRS[m[1]];
if (!root)
return false;
require('send')(req, m[2], {root: root}).pipe(resp);
return true;
};
exports.squish_MD5 = function (hash) {
if (typeof hash == 'string')
hash = new Buffer(hash, 'hex');
return hash.toString('base64').replace(/\//g, '_').replace(/=*$/, '');
};
exports.obtain_image_alloc = function (id, cb) {
var onegai = new db.Onegai;
onegai.obtain_image_alloc(id, function (err, alloc) {
onegai.disconnect();
if (err)
return cb(err);
if (validate_alloc(alloc))
cb(null, alloc);
else
cb("Invalid image alloc");
});
};
exports.commit_image_alloc = function (alloc, cb) {
publish(alloc, function (err) {
if (err)
return cb(err);
var o = new db.Onegai;
o.commit_image_alloc(alloc, function (err) {
o.disconnect();
cb(err);
});
});
};
exports.note_hash = function (hash, num) {
if (!config.DUPLICATE_COOLDOWN)
return;
var key = 'hash:' + hash;
db.connect().setex(key, config.DUPLICATE_COOLDOWN, num, function (err) {
if (err)
winston.warn("note hash: " + err);
});
};
var is_standalone = exports.is_standalone = db.is_standalone;