I'm trying to read a GridFS file via gridfs-stream
(https://github.com/aheckmann/gridfs-stream), rotate it 90° with gm
and store it as a new GridFS file.
My result looks very 'unstylish'... So I'm asking for help to optimize this little code snippet...
And the second thing for this code: I need a kind of 'switch'. This code do a rotation manipulation of the image. But I need to pass a parameter to do rotation, resize or something else. How do I integrate this?
import Grid from 'gridfs-stream'
import { MongoInternals } from 'meteor/mongo'
const id = '12345'
const gfs = Grid(
MongoInternals.defaultRemoteCollectionDriver().mongo.db,
MongoInternals.NpmModule
)
const readStream = gfs.createReadStream({ _id: id })
readStream.on('error', function (err) {
console.error('Could not read stream', err)
throw Meteor.Error(err)
})
gm(readStream)
.rotate('#ffffff', 90)
.stream(function (err, stdout, stderr) {
if (err) {
console.error('Could not write stream')
throw Meteor.Error(err)
}
const writeStream = gfs.createWriteStream()
const newFileId = writeStream.id
writeStream.on('finish',
function () {
console.log('New file created with ID ' + newFileId)
}
)
stdout.pipe(writeStream)
})
I don't have a project set up to test this on but it looks right.
Complex streaming tends to get pretty ugly looking. Not a whole lot you can do about it, besides trying not let it get out of hand. But let's see what we can do to beautify along the way while adding the additional functionality.
Since you are creating read stream on top level, I think it's cleaner to put your write stream on top level also. You can group them together in an object.
Fat-arrow functions tend to be cleaner looking, so I've put those in for the anonymous functions. Just be aware that fat arrows don't have a this
binding. So if you need to access the this
of your stream, you need to revert to using function
keyword.
Using rs
and ws
to mean readstream
and writestream
are pretty common conventions. So I think it's a safe abbreviation to use where appropriate.
To add ability to use multiple options, we make a wrapper function that takes our in-stream and options and returns out-stream. A plugin, you could say.
By making our function call destructure an object into arguments, we can assign them by name. Easier to tell what's going on.
We use Object.keys
to get an array of option names. Then use names to step through our options, applying each option by spread
ing the array of arguments into the gm
method.
The gm readme says it will return a stream for convenience if no callback given. Nice. :) We'll return the whole stream chain, ready to pipe to whatever output we want.
import Grid from 'gridfs-stream'
import gm from 'gm'
import { MongoInternals } from 'meteor/mongo'
const id = '12345'
const gfs = Grid(
MongoInternals.defaultRemoteCollectionDriver().mongo.db,
MongoInternals.NpmModule
)
const gfsStreams = {
read: _id =>
gfs.createReadStream({ _id })
.on('error', err => {
console.error('Could not read stream', err)
throw Meteor.Error(err)
}),
write: () => {
const ws = gfs.createWriteStream()
const newFileId = ws.id
ws.on('finish', () =>
console.log('New file created with ID ' + newFileId)
)
.on('error' => {
console.error('Could not write stream')
throw Meteor.Error(err)
})
return ws
}
}
const transformedStream = gmTransform({
filestream: gfsStreams.read(id),
gmOptions: {
magnify: [],
rotate: ['ffffff', 90],
blur: [7, 3]
crop: [300, 300, 150, 130]
},
output: stdout
})
stdout.pipe(transformedStream)
function gmTrasform ({filestream, gmOptions}){
let gmStream = gm(filestream)
.on('error', {
console.error('Could not transform image')
throw Meteor.Error(err)
})
Object.keys(gmOptions)
.forEach(opt => {
gmStream = gmStream[opt](...gmOptions[i])
})
return gmStream.stream()
.pipe(gfsStreams.write())
}