Attach properties to file object after performing gm manipulations
I'm trying to figure out the best way to attach the resized properties of my images to their FS.File objects, since my current method is unreliable.
In my setup I am using four S3 stores that use four different folders and have their own transformWrite function. The uploads sometimes fail when performing multiple uploads in a production environment (about 1 in 5 will fail). I have isolated the issue to happening whenever my stores use more than one transformWrite function that calls file.update internally.
This is the transform for my mobile store. I resize the image while preserving aspect ratio and then attach the new dimensions onto the image's file object with a metadata key.
TransformWrites.mobile = function(file, readStream, writeStream){
gm(readStream, file.name)
.resize(dimensions.mobileWidth, dimensions.mobileHeight)
.gravity('Center')
.stream(Meteor.bindEnvironment(function(err, stdout, stderr){
if (! file.metadata.mobileDimensions) {
gm(stdout).size(Meteor.bindEnvironment(function(err, value){
if (! value) { return }
file.update({$set: {metadata: _.extend(file.metadata, {
mobileDimensions: {
width: value.width,
height: value.height,
},
})}})
}));
}
stderr.pipe(process.stderr);
stdout.pipe(writeStream);
}));
};
Is there a more reliable pattern I could use to keep uploads from failing? The error I receive is always some form of Error: 5627bc1c0dd266fc02a820d4 does not exist, which shows in my logs after a message such as GRIDFS { _id: 5627bc1c0dd266fc02a820d4, root: 'cfs_gridfs._tempstore' }.
Hi leonidez. I know it's really late, but took me a day or so to solve the same problem that you had. It really sucks and I don't like it, but it works.
function transformWrite(fileObj, readStream, writeStream, maxWidth, maxHeight) {
// Some clever guy would say why I'm making this instead of
// `gm().resize(maxWidth, maxHeight, '>')`. The reason is that the cfs_gridfs package is broken
// and raises an error saying that the file doesn't exist if the image is smaller.
// It doesn't happen however if it's bigger. This is why we need to always force a write
// on the stream. Yeah, sucks.
gm(readStream, fileObj.name()).size(
{bufferStream: true},
function(err, size) {
if(! size) {
return false;
}
const calcSize = getResizeDimensions(size.width, size.height, maxWidth, maxHeight);
// Resize the image
this.resize(calcSize[0], calcSize[1], '>');
this.stream((err, stdout, stderr) => {
stdout.pipe(writeStream);
});
}
);
// This is not ideal, but the only way to use fileObj.update is inside the
// FS.Utility.safeCallback, so we cannot use it in the function above
gm(readStream, fileObj.name()).size({bufferStream: true}, FS.Utility.safeCallback(
(err, size) => {
if (! size) {
return false;
}
const calcSize = getResizeDimensions(size.width, size.height, maxWidth, maxHeight);
fileObj.update({$set: {'metadata.width': calcSize[0], 'metadata.height': calcSize[1]}});
}
));
}
function getResizeDimensions(width, height, maxWidth, maxHeight) {
if (maxWidth >= width && maxHeight >= height) {
return [width, height];
}
if (width > maxWidth && height <= maxHeight) {
const calcHeight = Math.round(height * maxWidth / width);
return [maxWidth, calcHeight];
}
if (height > maxHeight && width <= maxWidth) {
const calcWidth = Math.round(width * maxHeight / height);
return [calcWidth, maxHeight];
}
if (maxWidth < width && maxHeight < height) {
if ((width / maxWidth) > (height / maxHeight)) {
const calcHeight = Math.round(height * maxWidth / width);
return [maxWidth, calcHeight];
} else if ((width / maxWidth) < (height / maxHeight)) {
const calcWidth = Math.round(width * maxHeight / height);
return [calcWidth, maxHeight];
} else {
return [maxWidth, maxHeight];
}
}
}
Stores.images = new FS.Store.GridFS('images', {
transformWrite: (fileObj, readStream, writeStream) => {
transformWrite(fileObj, readStream, writeStream, 1280, 960)
}
});