skipper-gcstorage
Version:
A skipper adapter used for uploading files to Google Cloud Storage
149 lines (138 loc) • 7.11 kB
JavaScript
const Writable = require("stream").Writable;
const _ = require("lodash");
const mime = require("mime");
const sharp = require('sharp');
const utils = require('./utils');
/**
* skipper-gcs
*
* @param {Dictionary} globalOpts
* @property {String?} projectId
* @property {String?} keyFilename
* @property {String} bucket
* @property {Object?} bucketMetadata used to create non-existing bucket
* @property {Number?} maxBytes
* @property {Object?} metadata The metadata of gcs file
* @property {Boolean?} public Whether to make the file public
* @property {Boolean|String} keepName Whether to keep original filename or generate new UUID name
* [keepName=false, default]:
* The `upload` implements a unique filename by combining:
* • a generated UUID (like "4d5f444-38b4-4dc3-b9c3-74cb7fbbc932")
* • the uploaded file's original extension (like ".jpg")
*
* [keepName=true]:
* The `upload` will keep the original filename and extension.
*
* [keepName=specified string]:
* The `upload` will rename the file as the value of `keepName`.
* @property {Object?} resize // refer to https://sharp.pixelplumbing.com/api-resize#resize
* @property {Number?} width
* @property {Number?} height
*
* @returns {{receive: (function(Object): *), read: ((function(*): (*))|*), ls: ls, rm: rm}}
* @property {Function} ls
* @property {Function} read
* @property {Function} rm
* @property {Function} receive
*/
module.exports = function SkipperGCS(globalOpts) {
globalOpts = globalOpts || {};
_.defaults(globalOpts, {
bucket: "",
resize: {},
keepName: false
});
return {
ls: function (dirname, done) {
const bucket = utils.getBucket(globalOpts);
bucket.getFiles({prefix: dirname,}, function (err, files) {
if (err) {
done(err);
} else {
files = _.map(files, "name");
done(undefined, files);
}
});
},
read: function (fd) {
if (arguments[1]) {
return arguments[1](new Error('For performance reasons, skipper-gcstorage does not support passing in a callback to `.read()`'));
}
const bucket = utils.getBucket(globalOpts);
return bucket.file(fd).createReadStream();
},
rm: function (filename, done) {
const bucket = utils.getBucket(globalOpts);
bucket.file(filename).delete(done);
},
/**
* A simple receiver for Skipper that writes Upstreams to Google Cloud Storage
*
* @param {Object} options
* @return {Stream.Writable}
*/
receive: function GCSReceiver(options) {
options = options || {};
_.defaults(options, globalOpts);
// if maxBytes is configured in "MB" ended string
// convert it into bytes
if (options.maxBytes) {
const _maxBytesRegResult = (options.maxBytes + '').match(/(\d+)m/i);
if (!_.isNull(_maxBytesRegResult)) {
options.maxBytes = _maxBytesRegResult[1] * 1024 * 1024;
}
}
// Build an instance of a writable stream in object mode.
const receiver__ = Writable({objectMode: true,});
receiver__.once('error', (unusedErr) => {
// console.log('ERROR ON receiver ::', unusedErr);
});//œ
// This `_write` method is invoked each time a new file is pumped in
// from the upstream. `incomingFileStream` is a readable binary stream.
receiver__._write = (incomingFileStream, encoding, proceed) => {
utils.getOrCreateBucket(options, bucket => {
// `skipperFd` is the file descriptor-- the unique identifier.
// Often represents the location where file should be written.
//
// But note that we formerly used `fd`, but now Node attaches an `fd` property
// to Readable streams that come from the filesystem. So this kinda messed
// us up. And we had to do this instead:
const incomingFd = incomingFileStream.skipperFd || (_.isString(incomingFileStream.fd) ? incomingFileStream.fd : undefined);
if (!_.isString(incomingFd)) {
return proceed(new Error('In skipper-gcstorage adapter, write() method called with a stream that has an invalid `skipperFd`: ' + incomingFd));
}
const originalName = incomingFileStream.filename || incomingFd;
let saveAs = utils.getFilename(originalName, incomingFd, options.keepName);
incomingFileStream.once('error', (unusedErr) => {
// console.log('ERROR ON incoming readable file stream in Skipper Google Cloud Storage adapter (%s) ::', incomingFileStream.filename, unusedErr);
});//œ
options.metadata = options.metadata || {};
options.metadata.contentType = mime.getType(incomingFd);
options.metadata.filename = originalName;
let file = bucket.file(saveAs);
file.exists(function (err, exists) {
if (exists) {
saveAs = utils.incrementalRename(saveAs);
file = bucket.file(saveAs);
}
});
const isImage = options.metadata.contentType && options.metadata.contentType.startsWith('image');
const resize = {...options.resize, fit: 'inside'};
const transformer = sharp().rotate().resize(resize);
const stream = isImage && (resize.width || resize.height)
? incomingFileStream.pipe(transformer)
: incomingFileStream;
stream.pipe(file.createWriteStream(options))
.on('error', (err) => receiver__.emit("error", err))
.on('finish', function () {
incomingFileStream.extra = file.metadata;
// Indicate that a file was persisted.
receiver__.emit('writefile', incomingFileStream);
proceed();
});
});
};
return receiver__;
},
};
};