backend/core/image/index.js
'use strict';
// error code:
// 1 : datastore error
// 2 : image manipulation error
var defaultAvatarSize = 256;
var logger = require('..').logger;
var PassThrough = require('stream').PassThrough;
var gm = require('gm').subClass({ imageMagick: true });
var filestore = require('../filestore');
var async = require('async');
var extend = require('extend');
var ObjectId = require('mongoose').Types.ObjectId;
var avatarGenerationModule = require('./generate-avatar');
function getDuplicates(readable) {
var p1 = new PassThrough();
var p2 = new PassThrough();
readable.pipe(p1);
readable.pipe(p2);
return [p1, p2];
}
function getSize(readable, callback) {
gm(readable)
.size({bufferStream: true}, function(err, size) {
if (err) {
logger.error('Failed to get image size');
logger.debug(err);
err.code = 2;
return callback(err);
}
return callback(null, size, this);
});
}
function checkImageSquare(readable, callback) {
getSize(readable, function(err, size, gmInstance) {
if (err) {
return callback(err);
}
var sizeWidthMin = size.height - 2;
var sizeWidthMax = size.height + 2;
if (size.width < sizeWidthMin || size.width > sizeWidthMax) {
logger.debug('image is not a square');
var error = new Error('Image is not a square');
error.code = 2;
return callback(error);
}
return callback(null, size, gmInstance);
});
}
function recordAvatar(id, contentType, opts, readable, callback) {
var streams = getDuplicates(readable);
var fullSizeMetadata = extend(true, {}, opts);
var resizedMetadata = extend(true, {}, opts);
fullSizeMetadata.avatar = {
fullsize: true
};
resizedMetadata.avatar = {
originalId: id,
fullsize: false,
size: defaultAvatarSize
};
var responses = {
datastore: {},
gm: {}
};
async.parallel(
[
function(callback) {
filestore.store(id, contentType, fullSizeMetadata, streams[0], {}, function(err, file) {
if (err) {
logger.debug('failed to record original image');
logger.debug(err);
responses.datastore = {error: err, size: file.length};
responses.datastore.error.code = 1;
} else {
responses.datastore.size = file.length;
}
callback();
});
},
function(callback) {
checkImageSquare(streams[1], function(err, size, gmInstance) {
if (err) {
responses.gm.error = err;
} else {
responses.gm.size = size;
responses.gm.image = gmInstance;
}
callback();
});
}
],
function() {
if (responses.datastore.error) {
return callback(responses.datastore.error);
}
if (responses.gm.error) {
filestore.delete(id, function() {});
return callback(responses.gm.error);
}
responses.gm.image.resize(defaultAvatarSize, defaultAvatarSize);
responses.gm.image.stream(function(err, stdout) {
if (err) {
logger.debug('failed to stream gm image after resize');
logger.debug(err.stack);
filestore.delete(id, function() {});
err.code = 2;
return callback(err);
}
filestore.store(new ObjectId(), contentType, resizedMetadata, stdout, {}, function(err) {
if (err) {
logger.debug('failed to record resized image');
logger.debug(err.stack);
filestore.delete(id, function() {});
err.code = 1;
return callback(err);
}
return callback(null, responses.datastore.size);
});
});
}
);
}
function setDefaultAvatarSize(size) {
var s = parseInt(size, 10);
if (isNaN(s)) {
return false;
}
defaultAvatarSize = s;
return true;
}
function getSmallAvatar(id, callback) {
filestore.find({
'metadata.avatar.originalId': id,
'metadata.avatar.fullsize': false,
'metadata.avatar.size': defaultAvatarSize
}, function(err, ids) {
if (err || !ids || !ids.length) {
return callback(err);
}
return filestore.get(ids.pop(), function(err, meta, stream) {
return callback(err, meta, stream);
});
});
}
function getAvatar(id, format, callback) {
if (format && format === 'original') {
return filestore.get(id, callback);
}
return getSmallAvatar(id, function(err, meta, stream) {
if (err || !stream) {
return filestore.get(id, callback);
}
return callback(err, meta, stream);
});
}
module.exports.getSize = getSize;
module.exports.getAvatar = getAvatar;
module.exports.getSmallAvatar = getSmallAvatar;
module.exports.checkImageSquare = checkImageSquare;
module.exports.recordAvatar = recordAvatar;
module.exports.setDefaultAvatarSize = setDefaultAvatarSize;
module.exports.avatarGenerationModule = avatarGenerationModule;
module.exports.CONSTANTS = require('./constants');