paulogdm
paulogdm

Reputation: 1801

Grunt error with fs.unlinkSync on Sails

I'm using skipper to receive the files, sharp to resize (and save) and fs unlink to remove the old image. But I got a very weird error this time that concerns me a lot:

error: ** Grunt :: An error occurred. ** error: Aborted due to warnings. Running "copy:dev" (copy) task Warning: Unable to read "assets/images/users/c8e303ca-1036-4f52-88c7-fda7e01b6bba.jpg" file (Error code: ENOENT).

error: Looks like a Grunt error occurred-- error: Please fix it, then restart Sails to continue running tasks (e.g. watching for changes in assets) error: Or if you're stuck, check out the troubleshooting tips below.

error: Troubleshooting tips: error: error: *-> Are "grunt" and related grunt task modules installed locally? Run npm install if you're not sure. error: error: *-> You might have a malformed LESS, SASS, CoffeeScript file, etc. error: error: *-> Or maybe you don't have permissions to access the .tmp directory? error: e.g., (edited for privacy)/sails/.tmp ? error: error: If you think this might be the case, try running: error: sudo chown -R 1000 (edited for privacy)/sails/.tmp

Grunt stopped running and to have that in production is a big NoNo... I believe that this is caused because of concurrency with fs.unlinkSync(fname). The error is also intermittent and very hard to reproduce in some machines (IO ops/sec maybe?).

I have the following controller action:

var id = 1; // for example

req.file('avatar').upload({
    dirname: require('path').resolve(sails.config.appPath, 'assets/images')
}, function(err, files){
    var avatar = files.pop();

    //file name operations here. output is defined as the path + id + filetype
   //...

    sharp(avatar.fd)
    .resize(800, 800)
    .toFile(output, (err, info)=>{
        if(err){
            res.badRequest();
        } else {
            fs.unlinkSync(avatar.fd);
            res.ok();
        }
    });
});

Now I've been thinking about a few solutions:

  1. Output the new image directly to .temp
  2. Unlink when files exists on .tmp. Explanation: Grunt already copied the old file so removing it would be safe!

But I don't know if this is some spaghetti code or even if a better solution exists.

EDIT: My solution was, as proposed by arbuthnott, wrap a controller like this:

get : function(req, res){
    var filepath = req.path.slice(1,req.path.length);
    //remove '/' root identifier. path.resolve() could be used

    if(fs.existsSync(path.resolve(filepath))){
        return res.sendfile(filepath);
    } else {
        return res.notFound();  
    }
}

Upvotes: 1

Views: 298

Answers (1)

arbuthnott
arbuthnott

Reputation: 3819

I think you are on the right track about the error. You are making some rapid changes to in the assets folder. If I read your code right:

  1. Add an image with user-generated filename to assets/images (ex cat.jpg)
  2. Copy/resize the file to an id filename in assets/images (ex abc123.jpg)
  3. Delete the original upload (cat.jpg)

(I don't know the details of sharp, there may be more under the hood there)

If sails is running in dev mode, then Grunt will be trying to watch the whole assets/ folder, and copy all the changes to .tmp/public/. It's easy to imagine Grunt may register a change, but when it gets around to copying the added file (assets/images/cat.jpg) it is already gone.


I have two suggestions for the solution:

One:

Like you suggested, upload your original to the .tmp folder (maybe even a custom subfolder of .tmp). Still place your sized copy into /assets/images/, and it will be copied to /.tmp/public/ where it can be accessed as an asset by the running app. But Grunt will ignore the quick add-then-delete in the .tmp folder.

Two:

Do a bit of general thinking about both what you want to include in version control, and what Grunt tasks you want to be running in production. Note that if you use sails lift --prod then Grunt watch is turned off by default, and this error would not even occur. Generally, I don't feel like we want Grunt to do too much in production, it is more of a development shortcut. Specifically, Grunt watch can use a lot of resources on a production server.

The note about version control is just that you probably want some of the contents of assets/images/ to be in version control (images used by the site, etc), but maybe not in the case of user-uploaded avatars. Make sure you have a way to differentiate these contents (subdirectories or whatever). Then they can be easily .git-ignore'd or whatever is appropriate.

Hope this helps, good luck!

Upvotes: 1

Related Questions