/* Deletes the original images and thumbnails of archived posts, * leaving just the 'vint' thumbnail. */ var imgConfig = require('../imager/config'), crypto = require('crypto'), db = require('../db'), etc = require('../etc'), fs = require('fs'), path = require('path'), imager = require('../imager'), tail = require('../tail'), winston = require('winston'); function Recycler() { this.tag = 'archive'; this.y = new db.Yakusoku(this.tag, db.UPKEEP_IDENT); } var R = Recycler.prototype; R.recycle_post = function (post, cb) { if (!post.image || !post.image.src || post.hideimg) return cb(null); var r = this.y.connect(); var image = post.image var src = imager.media_path('src', image.src); var toDelete = []; if (image.thumb) { toDelete.push(src); src = imager.media_path('thumb', image.thumb); } if (image.realthumb) { toDelete.push(imager.media_path('thumb', image.realthumb)); } if (image.mid) { toDelete.push(imager.media_path('mid', image.mid)); } MD5_file(src, function (err, MD5) { if (err) { if (err.code == 'ENOENT') winston.warn(src + " doesn't exist."); else winston.error(err); return cb(null); } var dest = MD5 + path.extname(src); var dest_path = imager.media_path('vint', dest); etc.movex(src, dest_path, function (err) { if (err) return cb(err); var m = r.multi(); var key = post.op ? 'post:' + post.num : 'thread:' + post.num; m.hdel(key, 'src'); m.hdel(key, 'thumb'); m.hdel(key, 'mid'); m.hset(key, 'vint', dest); m.exec(function (err) { if (err) { // move it back etc.movex(dest_path, src, function (e) { if (e) winston.error(e); return cb(err); }); return; } toDelete.forEach(function (victim) { fs.unlink(victim, function (err) { if (err) winston.error(err); }); }); if (toDelete.length) { var info = post.num + ': del ' + toDelete.length; winston.info(info); } cb(null); }); }); }); }; R.recycle_thread = function (op, cb) { op = parseInt(op, 10); var reader = new db.Reader(this.y); reader.get_thread(this.tag, op, {}); var do_post = this.recycle_post.bind(this); reader.on('thread', function (thread) { if (thread.immortal) return cb(null); // grrr, ought to stream var posts = [thread]; reader.on('post', function (post) { posts.push(post); }); reader.on('endthread', function () { tail.forEach(posts, do_post, cb); }); reader.on('error', cb); }); }; R.recycle_archive = function (cb) { var key = 'tag:' + this.tag.length + ':' + this.tag; var r = this.y.connect(); var do_thread = this.recycle_thread.bind(this); r.zrange(key + ':threads', 0, -1, function (err, threads) { if (err) return cb(err); tail.forEach(threads, do_thread, cb); }); }; function MD5_file(path, callback) { var stream = fs.createReadStream(path); var hash = crypto.createHash('md5'); stream.once('error', function (err) { stream.destroy(); callback(err); }); stream.on('data', function (buf) { hash.update(buf); }); stream.on('end', function () { stream.destroy(); /* grr stupid digest() won't give us a Buffer */ hash = new Buffer(hash.digest('binary'), 'binary'); callback(null, imager.squish_MD5(hash)); }); } if (require.main === module) process.nextTick(function () { var recycler = new Recycler; recycler.recycle_archive(function (err) { if (err) throw err; recycler.y.disconnect(); process.exit(0); }); });