-
Notifications
You must be signed in to change notification settings - Fork 3
/
index.js
148 lines (138 loc) · 6.94 KB
/
index.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
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__;
},
};
};