@engine9-io/input-tools 1.9.10 → 2.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/ForEachEntry.js +18 -43
- package/ValidatingReadable.js +3 -6
- package/buildSamplePackets.js +11 -16
- package/eslint.config.mjs +15 -11
- package/file/FileUtilities.js +976 -1048
- package/file/GoogleDrive.js +32 -38
- package/file/Parquet.js +112 -124
- package/file/R2.js +27 -32
- package/file/S3.js +259 -293
- package/file/tools.js +334 -326
- package/index.js +63 -78
- package/package.json +2 -1
- package/test/cli.js +3 -4
- package/test/file.js +6 -7
- package/test/processing/bigDataMessage.js +8 -10
- package/test/processing/forEach.js +6 -8
- package/test/processing/forEachResume.js +6 -8
- package/test/processing/message.js +31 -39
- package/test/processing/zip.js +6 -7
- package/test/uuid.js +6 -11
- package/timelineTypes.js +2 -24
package/file/S3.js
CHANGED
|
@@ -1,340 +1,308 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
HeadObjectCommand,
|
|
10
|
-
GetObjectAttributesCommand,
|
|
11
|
-
PutObjectCommand,
|
|
12
|
-
ListObjectsV2Command
|
|
13
|
-
} = require('@aws-sdk/client-s3');
|
|
14
|
-
const { getTempFilename, relativeDate } = require('./tools');
|
|
15
|
-
|
|
1
|
+
import debug$0 from "debug";
|
|
2
|
+
import fs from "node:fs";
|
|
3
|
+
import withDb from "mime-type/with-db";
|
|
4
|
+
import clientS3 from "@aws-sdk/client-s3";
|
|
5
|
+
import { getTempFilename, relativeDate } from "./tools.js";
|
|
6
|
+
const debug = debug$0('@engine9-io/input/S3');
|
|
7
|
+
const { mimeType: mime } = withDb;
|
|
8
|
+
const { S3Client, CopyObjectCommand, DeleteObjectCommand, GetObjectCommand, HeadObjectCommand, GetObjectAttributesCommand, PutObjectCommand, ListObjectsV2Command } = clientS3;
|
|
16
9
|
function Worker() {
|
|
17
|
-
|
|
10
|
+
this.prefix = 's3';
|
|
18
11
|
}
|
|
19
|
-
|
|
20
12
|
function getParts(filename) {
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
13
|
+
if (!filename)
|
|
14
|
+
throw new Error(`Invalid filename: ${filename}`);
|
|
15
|
+
if (!filename.startsWith('r2://') && !filename.startsWith('s3://')) {
|
|
16
|
+
throw new Error(`Invalid filename, must start with r2:// or s3://: ${filename}`);
|
|
17
|
+
}
|
|
18
|
+
const parts = filename.split('/');
|
|
19
|
+
const Bucket = parts[2];
|
|
20
|
+
const Key = parts.slice(3).join('/');
|
|
21
|
+
return { Bucket, Key };
|
|
29
22
|
}
|
|
30
23
|
Worker.prototype.getClient = function () {
|
|
31
|
-
|
|
32
|
-
|
|
24
|
+
if (!this.client)
|
|
25
|
+
this.client = new S3Client({});
|
|
26
|
+
return this.client;
|
|
33
27
|
};
|
|
34
|
-
|
|
35
28
|
Worker.prototype.getMetadata = async function ({ filename }) {
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
})
|
|
45
|
-
);
|
|
46
|
-
|
|
47
|
-
return resp;
|
|
29
|
+
const s3Client = this.getClient();
|
|
30
|
+
const { Bucket, Key } = getParts(filename);
|
|
31
|
+
const resp = await s3Client.send(new GetObjectAttributesCommand({
|
|
32
|
+
Bucket,
|
|
33
|
+
Key,
|
|
34
|
+
ObjectAttributes: ['ETag', 'Checksum', 'ObjectParts', 'StorageClass', 'ObjectSize']
|
|
35
|
+
}));
|
|
36
|
+
return resp;
|
|
48
37
|
};
|
|
49
38
|
Worker.prototype.getMetadata.metadata = {
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
39
|
+
options: {
|
|
40
|
+
filename: {}
|
|
41
|
+
}
|
|
53
42
|
};
|
|
54
|
-
|
|
55
43
|
Worker.prototype.stream = async function ({ filename }) {
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
44
|
+
const s3Client = this.getClient();
|
|
45
|
+
const { Bucket, Key } = getParts(filename);
|
|
46
|
+
const command = new GetObjectCommand({ Bucket, Key });
|
|
47
|
+
try {
|
|
48
|
+
debug(`Streaming file ${Key}`);
|
|
49
|
+
const response = await s3Client.send(command);
|
|
50
|
+
return { stream: response.Body };
|
|
51
|
+
}
|
|
52
|
+
catch (e) {
|
|
53
|
+
debug(`Could not stream filename:${filename}`);
|
|
54
|
+
throw e;
|
|
55
|
+
}
|
|
67
56
|
};
|
|
68
57
|
Worker.prototype.stream.metadata = {
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
58
|
+
options: {
|
|
59
|
+
filename: {}
|
|
60
|
+
}
|
|
72
61
|
};
|
|
73
|
-
|
|
74
62
|
Worker.prototype.copy = async function ({ filename, target }) {
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
return s3Client.send(command);
|
|
63
|
+
if (filename.startsWith('s3://') || filename.startsWith('r2://')) {
|
|
64
|
+
//we're fine
|
|
65
|
+
}
|
|
66
|
+
else {
|
|
67
|
+
throw new Error('Cowardly not copying a file not from s3 -- use put instead');
|
|
68
|
+
}
|
|
69
|
+
const s3Client = this.getClient();
|
|
70
|
+
const { Bucket, Key } = getParts(target);
|
|
71
|
+
debug(`Copying ${filename} to ${JSON.stringify({ Bucket, Key })}}`);
|
|
72
|
+
const command = new CopyObjectCommand({
|
|
73
|
+
CopySource: filename.slice(4), // remove the s3:/
|
|
74
|
+
Bucket,
|
|
75
|
+
Key
|
|
76
|
+
});
|
|
77
|
+
return s3Client.send(command);
|
|
92
78
|
};
|
|
93
|
-
|
|
94
79
|
Worker.prototype.copy.metadata = {
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
80
|
+
options: {
|
|
81
|
+
filename: {},
|
|
82
|
+
target: {}
|
|
83
|
+
}
|
|
99
84
|
};
|
|
100
85
|
Worker.prototype.move = async function ({ filename, target }) {
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
86
|
+
await this.copy({ filename, target });
|
|
87
|
+
await this.remove({ filename });
|
|
88
|
+
return { filename: target };
|
|
104
89
|
};
|
|
105
90
|
Worker.prototype.move.metadata = {
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
91
|
+
options: {
|
|
92
|
+
filename: {},
|
|
93
|
+
target: {}
|
|
94
|
+
}
|
|
110
95
|
};
|
|
111
|
-
|
|
112
96
|
Worker.prototype.remove = async function ({ filename }) {
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
97
|
+
const s3Client = this.getClient();
|
|
98
|
+
const { Bucket, Key } = getParts(filename);
|
|
99
|
+
const command = new DeleteObjectCommand({ Bucket, Key });
|
|
100
|
+
return s3Client.send(command);
|
|
117
101
|
};
|
|
118
102
|
Worker.prototype.remove.metadata = {
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
103
|
+
options: {
|
|
104
|
+
filename: {}
|
|
105
|
+
}
|
|
122
106
|
};
|
|
123
|
-
|
|
124
107
|
Worker.prototype.download = async function ({ filename }) {
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
108
|
+
const file = filename.split('/').pop();
|
|
109
|
+
const localPath = await getTempFilename({ targetFilename: file });
|
|
110
|
+
const s3Client = this.getClient();
|
|
111
|
+
const { Bucket, Key } = getParts(filename);
|
|
112
|
+
const command = new GetObjectCommand({ Bucket, Key });
|
|
113
|
+
debug(`Downloading ${file} to ${localPath}`);
|
|
114
|
+
const response = await s3Client.send(command);
|
|
115
|
+
const fileStream = fs.createWriteStream(localPath);
|
|
116
|
+
response.Body.pipe(fileStream);
|
|
117
|
+
return new Promise((resolve, reject) => {
|
|
118
|
+
fileStream.on('finish', async () => {
|
|
119
|
+
const { size } = await fs.promises.stat(localPath);
|
|
120
|
+
resolve({ size, filename: localPath });
|
|
121
|
+
});
|
|
122
|
+
fileStream.on('error', reject);
|
|
140
123
|
});
|
|
141
|
-
fileStream.on('error', reject);
|
|
142
|
-
});
|
|
143
124
|
};
|
|
144
125
|
Worker.prototype.download.metadata = {
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
126
|
+
options: {
|
|
127
|
+
filename: {}
|
|
128
|
+
}
|
|
148
129
|
};
|
|
149
|
-
|
|
150
130
|
Worker.prototype.put = async function (options) {
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
ContentType
|
|
172
|
-
});
|
|
173
|
-
|
|
174
|
-
return s3Client.send(command);
|
|
131
|
+
const { filename, directory } = options;
|
|
132
|
+
if (!filename)
|
|
133
|
+
throw new Error('Local filename required');
|
|
134
|
+
if (directory?.indexOf('s3://') !== 0 && directory?.indexOf('r2://') !== 0)
|
|
135
|
+
throw new Error(`directory path must start with s3:// or r2://, is ${directory}`);
|
|
136
|
+
const file = options.file || filename.split('/').pop();
|
|
137
|
+
const parts = directory.split('/');
|
|
138
|
+
const Bucket = parts[2];
|
|
139
|
+
const Key = parts.slice(3).filter(Boolean).concat(file).join('/');
|
|
140
|
+
const Body = fs.createReadStream(filename);
|
|
141
|
+
const ContentType = mime.lookup(file);
|
|
142
|
+
debug(`Putting ${filename} to ${JSON.stringify({ Bucket, Key, ContentType })}}`);
|
|
143
|
+
const s3Client = this.getClient();
|
|
144
|
+
const command = new PutObjectCommand({
|
|
145
|
+
Bucket,
|
|
146
|
+
Key,
|
|
147
|
+
Body,
|
|
148
|
+
ContentType
|
|
149
|
+
});
|
|
150
|
+
return s3Client.send(command);
|
|
175
151
|
};
|
|
176
152
|
Worker.prototype.put.metadata = {
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
153
|
+
options: {
|
|
154
|
+
filename: {},
|
|
155
|
+
directory: { description: 'Directory to put file, e.g. s3://foo-bar/dir/xyz' },
|
|
156
|
+
file: { description: 'Name of file, defaults to the filename' }
|
|
157
|
+
}
|
|
182
158
|
};
|
|
183
|
-
|
|
184
159
|
Worker.prototype.write = async function (options) {
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
ContentType
|
|
203
|
-
});
|
|
204
|
-
|
|
205
|
-
return s3Client.send(command);
|
|
160
|
+
const { directory, file, content } = options;
|
|
161
|
+
if (!directory?.indexOf('s3://') === 0)
|
|
162
|
+
throw new Error('directory must start with s3://');
|
|
163
|
+
const parts = directory.split('/');
|
|
164
|
+
const Bucket = parts[2];
|
|
165
|
+
const Key = parts.slice(3).filter(Boolean).concat(file).join('/');
|
|
166
|
+
const Body = content;
|
|
167
|
+
debug(`Writing content of length ${content.length} to ${JSON.stringify({ Bucket, Key })}}`);
|
|
168
|
+
const s3Client = this.getClient();
|
|
169
|
+
const ContentType = mime.lookup(file);
|
|
170
|
+
const command = new PutObjectCommand({
|
|
171
|
+
Bucket,
|
|
172
|
+
Key,
|
|
173
|
+
Body,
|
|
174
|
+
ContentType
|
|
175
|
+
});
|
|
176
|
+
return s3Client.send(command);
|
|
206
177
|
};
|
|
207
178
|
Worker.prototype.write.metadata = {
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
179
|
+
options: {
|
|
180
|
+
directory: { description: 'Directory to put file, e.g. s3://foo-bar/dir/xyz' },
|
|
181
|
+
file: { description: 'Name of file, defaults to the filename' },
|
|
182
|
+
content: { description: 'Contents of file' }
|
|
183
|
+
}
|
|
213
184
|
};
|
|
214
|
-
|
|
215
185
|
Worker.prototype.list = async function ({ directory, start, end, raw }) {
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
186
|
+
if (!directory)
|
|
187
|
+
throw new Error('directory is required');
|
|
188
|
+
let dir = directory;
|
|
189
|
+
while (dir.slice(-1) === '/')
|
|
190
|
+
dir = dir.slice(0, -1);
|
|
191
|
+
const { Bucket, Key: Prefix } = getParts(dir);
|
|
192
|
+
const s3Client = this.getClient();
|
|
193
|
+
const command = new ListObjectsV2Command({
|
|
194
|
+
Bucket,
|
|
195
|
+
Prefix: `${Prefix}/`,
|
|
196
|
+
Delimiter: '/'
|
|
197
|
+
});
|
|
198
|
+
const { Contents: files, CommonPrefixes } = await s3Client.send(command);
|
|
199
|
+
if (raw)
|
|
200
|
+
return files;
|
|
201
|
+
// debug('Prefixes:', { CommonPrefixes });
|
|
202
|
+
const output = []
|
|
203
|
+
.concat((CommonPrefixes || []).map((f) => ({
|
|
233
204
|
name: f.Prefix.slice(Prefix.length + 1, -1),
|
|
234
205
|
type: 'directory'
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
.concat(
|
|
238
|
-
(files || [])
|
|
206
|
+
})))
|
|
207
|
+
.concat((files || [])
|
|
239
208
|
.filter(({ LastModified }) => {
|
|
240
|
-
|
|
209
|
+
if (start && new Date(LastModified) < start) {
|
|
241
210
|
return false;
|
|
242
|
-
|
|
211
|
+
}
|
|
212
|
+
else if (end && new Date(LastModified) > end) {
|
|
243
213
|
return false;
|
|
244
|
-
|
|
214
|
+
}
|
|
215
|
+
else {
|
|
245
216
|
return true;
|
|
246
|
-
|
|
247
|
-
|
|
217
|
+
}
|
|
218
|
+
})
|
|
248
219
|
.map(({ Key, Size, LastModified }) => ({
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
return output;
|
|
220
|
+
name: Key.slice(Prefix.length + 1),
|
|
221
|
+
type: 'file',
|
|
222
|
+
size: Size,
|
|
223
|
+
modifiedAt: new Date(LastModified).toISOString()
|
|
224
|
+
})));
|
|
225
|
+
return output;
|
|
257
226
|
};
|
|
258
227
|
Worker.prototype.list.metadata = {
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
228
|
+
options: {
|
|
229
|
+
directory: { required: true }
|
|
230
|
+
}
|
|
262
231
|
};
|
|
263
232
|
/* List everything with the prefix */
|
|
264
233
|
Worker.prototype.listAll = async function (options) {
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
234
|
+
const { directory } = options;
|
|
235
|
+
if (!directory)
|
|
236
|
+
throw new Error('directory is required');
|
|
237
|
+
let dir = directory;
|
|
238
|
+
const start = options.start && relativeDate(options.start);
|
|
239
|
+
const end = options.end && relativeDate(options.end);
|
|
240
|
+
while (dir.slice(-1) === '/')
|
|
241
|
+
dir = dir.slice(0, -1);
|
|
242
|
+
const { Bucket, Key } = getParts(dir);
|
|
243
|
+
const s3Client = this.getClient();
|
|
244
|
+
const files = [];
|
|
245
|
+
let ContinuationToken = null;
|
|
246
|
+
let Prefix = null;
|
|
247
|
+
if (Key)
|
|
248
|
+
Prefix = `${Key}/`;
|
|
249
|
+
do {
|
|
250
|
+
const command = new ListObjectsV2Command({
|
|
251
|
+
Bucket,
|
|
252
|
+
Prefix,
|
|
253
|
+
ContinuationToken
|
|
254
|
+
// Delimiter: '/',
|
|
255
|
+
});
|
|
256
|
+
debug(`Sending List command with prefix ${Prefix} with ContinuationToken ${ContinuationToken}`);
|
|
257
|
+
const result = await s3Client.send(command);
|
|
258
|
+
const newFiles = result.Contents?.filter(({ LastModified }) => {
|
|
259
|
+
if (start && new Date(LastModified) < start) {
|
|
260
|
+
return false;
|
|
261
|
+
}
|
|
262
|
+
else if (end && new Date(LastModified) > end) {
|
|
263
|
+
return false;
|
|
264
|
+
}
|
|
265
|
+
else {
|
|
266
|
+
return true;
|
|
267
|
+
}
|
|
268
|
+
})?.map((d) => `${this.prefix}://${Bucket}/${d.Key}`) || [];
|
|
269
|
+
debug(`Retrieved ${newFiles.length} new files, total ${files.length},sample ${newFiles.slice(0, 3).join(',')}`);
|
|
270
|
+
files.push(...newFiles);
|
|
271
|
+
ContinuationToken = result.NextContinuationToken;
|
|
272
|
+
} while (ContinuationToken);
|
|
273
|
+
return files;
|
|
302
274
|
};
|
|
303
275
|
Worker.prototype.listAll.metadata = {
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
276
|
+
options: {
|
|
277
|
+
directory: { required: true }
|
|
278
|
+
}
|
|
307
279
|
};
|
|
308
|
-
|
|
309
280
|
Worker.prototype.moveAll = async function ({ directory, targetDirectory }) {
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
return Promise.all(configs.map(({ filename, target }) => limitedMethod(async () => this.move({ filename, target }))));
|
|
281
|
+
if (!directory || !targetDirectory)
|
|
282
|
+
throw new Error('directory and targetDirectory required');
|
|
283
|
+
const files = await this.listAll({ directory });
|
|
284
|
+
const configs = files.map((d) => ({
|
|
285
|
+
filename: d,
|
|
286
|
+
target: d.replace(directory, targetDirectory)
|
|
287
|
+
}));
|
|
288
|
+
const pLimit = await import('p-limit');
|
|
289
|
+
const limitedMethod = pLimit.default(10);
|
|
290
|
+
return Promise.all(configs.map(({ filename, target }) => limitedMethod(async () => this.move({ filename, target }))));
|
|
321
291
|
};
|
|
322
292
|
Worker.prototype.moveAll.metadata = {
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
293
|
+
options: {
|
|
294
|
+
directory: { required: true },
|
|
295
|
+
targetDirectory: { required: true }
|
|
296
|
+
}
|
|
327
297
|
};
|
|
328
|
-
|
|
329
298
|
Worker.prototype.stat = async function ({ filename }) {
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
const {
|
|
299
|
+
if (!filename)
|
|
300
|
+
throw new Error('filename is required');
|
|
301
|
+
const s3Client = this.getClient();
|
|
302
|
+
const { Bucket, Key } = getParts(filename);
|
|
303
|
+
const command = new HeadObjectCommand({ Bucket, Key });
|
|
304
|
+
const response = await s3Client.send(command);
|
|
305
|
+
const {
|
|
338
306
|
// "AcceptRanges": "bytes",
|
|
339
307
|
ContentLength, // : "3191",
|
|
340
308
|
ContentType, // : "image/jpeg",
|
|
@@ -342,22 +310,20 @@ Worker.prototype.stat = async function ({ filename }) {
|
|
|
342
310
|
LastModified // : "2016-12-15T01:19:41.000Z",
|
|
343
311
|
// Metadata": {},
|
|
344
312
|
// VersionId": "null"
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
};
|
|
313
|
+
} = response;
|
|
314
|
+
const modifiedAt = new Date(LastModified);
|
|
315
|
+
const createdAt = modifiedAt; // Same for S3
|
|
316
|
+
const size = parseInt(ContentLength, 10);
|
|
317
|
+
return {
|
|
318
|
+
createdAt,
|
|
319
|
+
modifiedAt,
|
|
320
|
+
contentType: ContentType,
|
|
321
|
+
size
|
|
322
|
+
};
|
|
356
323
|
};
|
|
357
324
|
Worker.prototype.stat.metadata = {
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
325
|
+
options: {
|
|
326
|
+
filename: {}
|
|
327
|
+
}
|
|
361
328
|
};
|
|
362
|
-
|
|
363
|
-
module.exports = Worker;
|
|
329
|
+
export default Worker;
|