Meteor-CollectionFS icon indicating copy to clipboard operation
Meteor-CollectionFS copied to clipboard

Attach properties to file object after performing gm manipulations

Open leonidez opened this issue 10 years ago • 1 comments

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' }.

leonidez avatar Oct 21 '15 16:10 leonidez

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)
  }
});

rafa-munoz avatar May 13 '16 10:05 rafa-munoz