5

I'm using CollectionFS for managing images. Furthermore I'm using graphicsmagick gm() for manipulating images.

Now I want to crop a already saved image. Therefore on a click event a server-method is called, which does the crop(). But after doing this, in the collection I find an empty image with size=0 updated on the correct date.

I don't see, what I am doing wrong.

shared.js

Images = new FS.Collection("images", {
    stores: [
        new FS.Store.FileSystem("thumbnail", { 
            transformWrite: function(fileObj, readStream, writeStream) {
                gm(readStream, fileObj.name()).autoOrient().resize('96', '96' + '^').gravity('Center').extent('96', '96').stream().pipe(writeStream);
            } 
        }),
        new FS.Store.FileSystem("public"),
    ]
});

server.js

Meteor.methods({
    'crop': function (fileId, selection) {
        var file = Images.findOne({ _id: fileId }),
            read = file.createReadStream('public'),
            write = file.createWriteStream('public');

        gm(read)
            .crop(selection.width, selection.height, selection.left, selection.top)
        .stream()
        .pipe(write);
    }
});

client.js

Template.editor.events({
    'click #crop': function () { 
        var fileId      = '123456789',
            selection   = { height: 100, width: 100, top: 10, left: 10 }; 

        Meteor.call('crop', fileId, selection);
    }
});

Update

As recommended by Christian I'm using a tmp-file for the writeStream, because the writeStream can't be the same like the readStream - which caused the empty result.

But after writing to the tmp-file, the content of it has to be copied back to the public store. How do I do that?

Meteor.methods({
    'crop': function (fileId, selection) {

        var fs   = Meteor.npmRequire('fs'),
            file = Images.findOne({ _id: fileId }),
            read = file.createReadStream('public'),
            filename = '/tmp/gm_' + Date.now(),
            tmp  = fs.createWriteStream(filename);

        gm(read)
            .crop(selection.width, selection.height, selection.left, selection.top)
        .stream()
        .pipe(tmp);

        // After writing to tmp -> copy back to stream and delete tmp-file
    }
});

Update 2 I tried this one:

// Add temp store
new FS.Store.FileSystem("temp")

// Method
Meteor.methods({
    'crop': function (fileId, selection) {
        var file  = Images.findOne({ _id: fileId }),
            read  = file.createReadStream('public'),
            temp  = file.createWriteStream('temp');

        gm(read)
            .crop(selection.width, selection.height, selection.left, selection.top)
        .stream()
        .pipe(tmp)
        .on('end', function () {
            var tmpread  = file.createReadStream('temp'),
                write    = file.createWriteStream('public');

            gm(tmpread).stream().pipe(write);
        });

    }
});
Christian Fritz
  • 15,980
  • 3
  • 38
  • 58
user3142695
  • 11,619
  • 29
  • 119
  • 238

1 Answers1

2

You can't read and write into the same file. This is equivalent to things like

cat test | grep 1 > test

on the shell. You can try it and see that test will be empty afterwards.

You need to create an intermediate, temporary file in your crop method.


Assuming that is indeed the problem, then this is one way of doing this (not tested):

var fs = Meteor.npmRequire('fs');
var file = Images.findOne({ _id: fileId }),
var read = file.createReadStream('public'),
var filename = '/tmp/gm_' + Date.now();
var tmp = fs.createWriteStream(filename);

var gmread = gm(read)
    .crop(selection.width, selection.height, selection.left, selection.top)
    .stream();

gmread.on('end', function() {
    // done streaming through GM, copy the result back:
    var tmpread = fs.createReadStream(filename);
    var write = file.createWriteStream('public');
    tmpread.pipe(write);
});

gmread.pipe(tmp);
Christian Fritz
  • 15,980
  • 3
  • 38
  • 58
  • So what is the difference to this? https://github.com/CollectionFS/Meteor-CollectionFS/wiki/How-to:-Convert-a-file-already-stored – user3142695 Nov 20 '15 at 17:52
  • have you tried that and does it work? I'm not an expert at streams, but the way I understand them it might be luck if it did -- it may work for small files that get buffered as a whole before writing out from the buffer begins, but not for larger ones. But who knows, maybe collectionfs does something smart internally to allow this. One way or another, I don't think I would rely on that. Let me know what you find out when you test this. – Christian Fritz Nov 20 '15 at 18:08
  • Hmm.. could you please give an example how you would use a temporary stream? – user3142695 Nov 20 '15 at 18:13
  • How do I copy `tmp`to `write` after it is done and empty `tmp`? – user3142695 Nov 22 '15 at 02:56
  • really? You are asking for all this extra work and you haven't even up-voted or accepted the answer? I'm sure you'll find the answer to that last question somewhere. It's just copying a file into a stream which you already have ;-) – Christian Fritz Nov 22 '15 at 03:44
  • I'm very sorry for that stupid question. But I tried to get this in the last 24 hours, but I don't get it. Of course I will accept the answer - as I always do -, but I just want to get the thing working. Please don't mind... – user3142695 Nov 22 '15 at 04:04
  • I would like to give you a bounty for helping to get this thing work. It is just for copy and removing the temp stream... – user3142695 Nov 22 '15 at 17:39
  • oh wow, thanks! I guess you are really stuck on this, eh? Well I added some code along those lines. Please let me know if it's working. I haven't tested it yet. – Christian Fritz Nov 22 '15 at 21:29
  • Need to use `var fs = Npm.require('fs');` for Meteor 1.2. P.S.: I can give you the bounty in 15 hours... – user3142695 Nov 23 '15 at 02:13
  • `Npm.require` is for packages, in projects themselves you should use `Meteor.npmRequire` provided by the `meteorhacks:npm` package. – Christian Fritz Nov 23 '15 at 06:14
  • Yes, it is working. I accepted your answer. And I will give you the bounty as soon as possible. – user3142695 Nov 23 '15 at 06:15
  • But I had to use `Meteor.bindEnvironment()`. Maybe you want to add this to your answer. – user3142695 Nov 23 '15 at 06:21
  • oh really? sure, I can add it. For which part was that required? – Christian Fritz Nov 23 '15 at 16:07