1 | const debug = require('debug')('ali-oss:object');
|
2 | const fs = require('fs');
|
3 | const is = require('is-type-of');
|
4 | const copy = require('copy-to');
|
5 | const path = require('path');
|
6 | const mime = require('mime');
|
7 | const callback = require('./common/callback');
|
8 | const { Transform } = require('stream');
|
9 | const pump = require('pump');
|
10 | const { isBuffer } = require('./common/utils/isBuffer');
|
11 |
|
12 | const proto = exports;
|
13 |
|
14 |
|
15 |
|
16 |
|
17 |
|
18 |
|
19 |
|
20 |
|
21 |
|
22 |
|
23 |
|
24 |
|
25 | proto.append = async function append(name, file, options) {
|
26 | options = options || {};
|
27 | if (options.position === undefined) options.position = '0';
|
28 | options.subres = {
|
29 | append: '',
|
30 | position: options.position
|
31 | };
|
32 | options.method = 'POST';
|
33 |
|
34 | const result = await this.put(name, file, options);
|
35 | result.nextAppendPosition = result.res.headers['x-oss-next-append-position'];
|
36 | return result;
|
37 | };
|
38 |
|
39 |
|
40 |
|
41 |
|
42 |
|
43 |
|
44 |
|
45 |
|
46 |
|
47 |
|
48 |
|
49 |
|
50 |
|
51 |
|
52 |
|
53 |
|
54 |
|
55 |
|
56 | proto.put = async function put(name, file, options) {
|
57 | let content;
|
58 | options = options || {};
|
59 | name = this._objectName(name);
|
60 |
|
61 | if (isBuffer(file)) {
|
62 | content = file;
|
63 | } else if (is.string(file)) {
|
64 | const stats = fs.statSync(file);
|
65 | if (!stats.isFile()) {
|
66 | throw new Error(`${file} is not file`);
|
67 | }
|
68 | options.mime = options.mime || mime.getType(path.extname(file));
|
69 | const stream = fs.createReadStream(file);
|
70 | options.contentLength = await this._getFileSize(file);
|
71 | return await this.putStream(name, stream, options);
|
72 | } else if (is.readableStream(file)) {
|
73 | return await this.putStream(name, file, options);
|
74 | } else {
|
75 | throw new TypeError('Must provide String/Buffer/ReadableStream for put.');
|
76 | }
|
77 |
|
78 | options.headers = options.headers || {};
|
79 | this._convertMetaToHeaders(options.meta, options.headers);
|
80 |
|
81 | const method = options.method || 'PUT';
|
82 | const params = this._objectRequestParams(method, name, options);
|
83 |
|
84 | callback.encodeCallback(params, options);
|
85 |
|
86 | params.mime = options.mime;
|
87 | params.content = content;
|
88 | params.successStatuses = [200];
|
89 |
|
90 | const result = await this.request(params);
|
91 |
|
92 | const ret = {
|
93 | name,
|
94 | url: this._objectUrl(name),
|
95 | res: result.res
|
96 | };
|
97 |
|
98 | if (params.headers && params.headers['x-oss-callback']) {
|
99 | ret.data = JSON.parse(result.data.toString());
|
100 | }
|
101 |
|
102 | return ret;
|
103 | };
|
104 |
|
105 |
|
106 |
|
107 |
|
108 |
|
109 |
|
110 |
|
111 |
|
112 |
|
113 | proto.putStream = async function putStream(name, stream, options) {
|
114 | options = options || {};
|
115 | options.headers = options.headers || {};
|
116 | name = this._objectName(name);
|
117 | if (options.contentLength) {
|
118 | options.headers['Content-Length'] = options.contentLength;
|
119 | } else {
|
120 | options.headers['Transfer-Encoding'] = 'chunked';
|
121 | }
|
122 | this._convertMetaToHeaders(options.meta, options.headers);
|
123 |
|
124 | const method = options.method || 'PUT';
|
125 | const params = this._objectRequestParams(method, name, options);
|
126 | callback.encodeCallback(params, options);
|
127 | params.mime = options.mime;
|
128 | const transform = new Transform();
|
129 |
|
130 | transform._transform = function _transform(chunk, encoding, done) {
|
131 | this.push(chunk);
|
132 | done();
|
133 | };
|
134 | params.stream = pump(stream, transform);
|
135 | params.successStatuses = [200];
|
136 |
|
137 | const result = await this.request(params);
|
138 |
|
139 | const ret = {
|
140 | name,
|
141 | url: this._objectUrl(name),
|
142 | res: result.res
|
143 | };
|
144 |
|
145 | if (params.headers && params.headers['x-oss-callback']) {
|
146 | ret.data = JSON.parse(result.data.toString());
|
147 | }
|
148 |
|
149 | return ret;
|
150 | };
|
151 |
|
152 | proto.getStream = async function getStream(name, options) {
|
153 | options = options || {};
|
154 |
|
155 | if (options.process) {
|
156 | options.subres = options.subres || {};
|
157 | options.subres['x-oss-process'] = options.process;
|
158 | }
|
159 |
|
160 | const params = this._objectRequestParams('GET', name, options);
|
161 | params.customResponse = true;
|
162 | params.successStatuses = [200, 206, 304];
|
163 |
|
164 | const result = await this.request(params);
|
165 |
|
166 | return {
|
167 | stream: result.res,
|
168 | res: {
|
169 | status: result.status,
|
170 | headers: result.headers
|
171 | }
|
172 | };
|
173 | };
|
174 |
|
175 | proto.putMeta = async function putMeta(name, meta, options) {
|
176 | return await this.copy(name, name, {
|
177 | meta: meta || {},
|
178 | timeout: options && options.timeout,
|
179 | ctx: options && options.ctx
|
180 | });
|
181 | };
|
182 |
|
183 | proto.list = async function list(query, options) {
|
184 |
|
185 |
|
186 | const params = this._objectRequestParams('GET', '', options);
|
187 | params.query = query;
|
188 | params.xmlResponse = true;
|
189 | params.successStatuses = [200];
|
190 |
|
191 | const result = await this.request(params);
|
192 | let objects = result.data.Contents;
|
193 | const that = this;
|
194 | if (objects) {
|
195 | if (!Array.isArray(objects)) {
|
196 | objects = [objects];
|
197 | }
|
198 | objects = objects.map(obj => ({
|
199 | name: obj.Key,
|
200 | url: that._objectUrl(obj.Key),
|
201 | lastModified: obj.LastModified,
|
202 | etag: obj.ETag,
|
203 | type: obj.Type,
|
204 | size: Number(obj.Size),
|
205 | storageClass: obj.StorageClass,
|
206 | owner: {
|
207 | id: obj.Owner.ID,
|
208 | displayName: obj.Owner.DisplayName
|
209 | }
|
210 | }));
|
211 | }
|
212 | let prefixes = result.data.CommonPrefixes || null;
|
213 | if (prefixes) {
|
214 | if (!Array.isArray(prefixes)) {
|
215 | prefixes = [prefixes];
|
216 | }
|
217 | prefixes = prefixes.map(item => item.Prefix);
|
218 | }
|
219 | return {
|
220 | res: result.res,
|
221 | objects,
|
222 | prefixes,
|
223 | nextMarker: result.data.NextMarker || null,
|
224 | isTruncated: result.data.IsTruncated === 'true'
|
225 | };
|
226 | };
|
227 |
|
228 | proto.listV2 = async function listV2(query, options = {}) {
|
229 | const continuation_token = query['continuation-token'] || query.continuationToken;
|
230 | delete query['continuation-token'];
|
231 | delete query.continuationToken;
|
232 | if (continuation_token) {
|
233 | options.subres = Object.assign(
|
234 | {
|
235 | 'continuation-token': continuation_token
|
236 | },
|
237 | options.subres
|
238 | );
|
239 | }
|
240 | const params = this._objectRequestParams('GET', '', options);
|
241 | params.query = {
|
242 | 'list-type': 2,
|
243 | ...query
|
244 | };
|
245 | params.xmlResponse = true;
|
246 | params.successStatuses = [200];
|
247 |
|
248 | const result = await this.request(params);
|
249 | let objects = result.data.Contents;
|
250 | const that = this;
|
251 | if (objects) {
|
252 | if (!Array.isArray(objects)) {
|
253 | objects = [objects];
|
254 | }
|
255 | objects = objects.map(obj => ({
|
256 | name: obj.Key,
|
257 | url: that._objectUrl(obj.Key),
|
258 | lastModified: obj.LastModified,
|
259 | etag: obj.ETag,
|
260 | type: obj.Type,
|
261 | size: Number(obj.Size),
|
262 | storageClass: obj.StorageClass,
|
263 | owner: obj.Owner
|
264 | ? {
|
265 | id: obj.Owner.ID,
|
266 | displayName: obj.Owner.DisplayName
|
267 | }
|
268 | : null
|
269 | }));
|
270 | }
|
271 | let prefixes = result.data.CommonPrefixes || null;
|
272 | if (prefixes) {
|
273 | if (!Array.isArray(prefixes)) {
|
274 | prefixes = [prefixes];
|
275 | }
|
276 | prefixes = prefixes.map(item => item.Prefix);
|
277 | }
|
278 | return {
|
279 | res: result.res,
|
280 | objects,
|
281 | prefixes,
|
282 | isTruncated: result.data.IsTruncated === 'true',
|
283 | keyCount: +result.data.KeyCount,
|
284 | continuationToken: result.data.ContinuationToken || null,
|
285 | nextContinuationToken: result.data.NextContinuationToken || null
|
286 | };
|
287 | };
|
288 |
|
289 |
|
290 |
|
291 |
|
292 |
|
293 |
|
294 |
|
295 | proto.restore = async function restore(name, options) {
|
296 | options = options || {};
|
297 | options.subres = Object.assign({ restore: '' }, options.subres);
|
298 | if (options.versionId) {
|
299 | options.subres.versionId = options.versionId;
|
300 | }
|
301 | const params = this._objectRequestParams('POST', name, options);
|
302 | params.successStatuses = [202];
|
303 |
|
304 | const result = await this.request(params);
|
305 |
|
306 | return {
|
307 | res: result.res
|
308 | };
|
309 | };
|
310 |
|
311 | proto._objectUrl = function _objectUrl(name) {
|
312 | return this._getReqUrl({ bucket: this.options.bucket, object: name });
|
313 | };
|
314 |
|
315 |
|
316 |
|
317 |
|
318 |
|
319 |
|
320 |
|
321 |
|
322 | proto._objectRequestParams = function (method, name, options) {
|
323 | if (!this.options.bucket && !this.options.cname) {
|
324 | throw new Error('Please create a bucket first');
|
325 | }
|
326 |
|
327 | options = options || {};
|
328 | name = this._objectName(name);
|
329 | const params = {
|
330 | object: name,
|
331 | bucket: this.options.bucket,
|
332 | method,
|
333 | subres: options && options.subres,
|
334 | timeout: options && options.timeout,
|
335 | ctx: options && options.ctx
|
336 | };
|
337 |
|
338 | if (options.headers) {
|
339 | params.headers = {};
|
340 | copy(options.headers).to(params.headers);
|
341 | }
|
342 | return params;
|
343 | };
|
344 |
|
345 | proto._objectName = function (name) {
|
346 | return name.replace(/^\/+/, '');
|
347 | };
|
348 |
|
349 | proto._statFile = function (filepath) {
|
350 | return new Promise((resolve, reject) => {
|
351 | fs.stat(filepath, (err, stats) => {
|
352 | if (err) {
|
353 | reject(err);
|
354 | } else {
|
355 | resolve(stats);
|
356 | }
|
357 | });
|
358 | });
|
359 | };
|
360 |
|
361 | proto._convertMetaToHeaders = function (meta, headers) {
|
362 | if (!meta) {
|
363 | return;
|
364 | }
|
365 |
|
366 | Object.keys(meta).forEach(k => {
|
367 | headers[`x-oss-meta-${k}`] = meta[k];
|
368 | });
|
369 | };
|
370 |
|
371 | proto._deleteFileSafe = function (filepath) {
|
372 | return new Promise(resolve => {
|
373 | fs.exists(filepath, exists => {
|
374 | if (!exists) {
|
375 | resolve();
|
376 | } else {
|
377 | fs.unlink(filepath, err => {
|
378 | if (err) {
|
379 | debug('unlink %j error: %s', filepath, err);
|
380 | }
|
381 | resolve();
|
382 | });
|
383 | }
|
384 | });
|
385 | });
|
386 | };
|