const copy = require('copy-to'); const callback = require('./callback'); const { deepCopyWith } = require('./utils/deepCopy'); const { isBuffer } = require('./utils/isBuffer'); const { omit } = require('./utils/omit'); const proto = exports; /** * List the on-going multipart uploads * https://help.aliyun.com/document_detail/31997.html * @param {Object} options * @return {Array} the multipart uploads */ proto.listUploads = async function listUploads(query, options) { options = options || {}; const opt = {}; copy(options).to(opt); opt.subres = 'uploads'; const params = this._objectRequestParams('GET', '', opt); params.query = query; params.xmlResponse = true; params.successStatuses = [200]; const result = await this.request(params); let uploads = result.data.Upload || []; if (!Array.isArray(uploads)) { uploads = [uploads]; } uploads = uploads.map(up => ({ name: up.Key, uploadId: up.UploadId, initiated: up.Initiated })); return { res: result.res, uploads, bucket: result.data.Bucket, nextKeyMarker: result.data.NextKeyMarker, nextUploadIdMarker: result.data.NextUploadIdMarker, isTruncated: result.data.IsTruncated === 'true' }; }; /** * List the done uploadPart parts * @param {String} name object name * @param {String} uploadId multipart upload id * @param {Object} query * {Number} query.max-parts The maximum part number in the response of the OSS. Default value: 1000 * {Number} query.part-number-marker Starting position of a specific list. * {String} query.encoding-type Specify the encoding of the returned content and the encoding type. * @param {Object} options * @return {Object} result */ proto.listParts = async function listParts(name, uploadId, query, options) { options = options || {}; const opt = {}; copy(options).to(opt); opt.subres = { uploadId }; const params = this._objectRequestParams('GET', name, opt); params.query = query; params.xmlResponse = true; params.successStatuses = [200]; const result = await this.request(params); return { res: result.res, uploadId: result.data.UploadId, bucket: result.data.Bucket, name: result.data.Key, partNumberMarker: result.data.PartNumberMarker, nextPartNumberMarker: result.data.NextPartNumberMarker, maxParts: result.data.MaxParts, isTruncated: result.data.IsTruncated, parts: result.data.Part || [] }; }; /** * Abort a multipart upload transaction * @param {String} name the object name * @param {String} uploadId the upload id * @param {Object} options */ proto.abortMultipartUpload = async function abortMultipartUpload(name, uploadId, options) { this._stop(); options = options || {}; const opt = {}; copy(options).to(opt); opt.subres = { uploadId }; const params = this._objectRequestParams('DELETE', name, opt); params.successStatuses = [204]; const result = await this.request(params); return { res: result.res }; }; /** * Initiate a multipart upload transaction * @param {String} name the object name * @param {Object} options * @return {String} upload id */ proto.initMultipartUpload = async function initMultipartUpload(name, options) { options = options || {}; const opt = {}; copy(options).to(opt); opt.headers = opt.headers || {}; this._convertMetaToHeaders(options.meta, opt.headers); opt.subres = 'uploads'; const params = this._objectRequestParams('POST', name, opt); params.mime = options.mime; params.xmlResponse = true; params.successStatuses = [200]; const result = await this.request(params); return { res: result.res, bucket: result.data.Bucket, name: result.data.Key, uploadId: result.data.UploadId }; }; /** * Upload a part in a multipart upload transaction * @param {String} name the object name * @param {String} uploadId the upload id * @param {Integer} partNo the part number * @param {File} file upload File, whole File * @param {Integer} start part start bytes e.g: 102400 * @param {Integer} end part end bytes e.g: 204800 * @param {Object} options */ proto.uploadPart = async function uploadPart(name, uploadId, partNo, file, start, end, options) { const data = { size: end - start }; const isBrowserEnv = process && process.browser; isBrowserEnv ? (data.content = await this._createBuffer(file, start, end)) : (data.stream = await this._createStream(file, start, end)); return await this._uploadPart(name, uploadId, partNo, data, options); }; /** * Complete a multipart upload transaction * @param {String} name the object name * @param {String} uploadId the upload id * @param {Array} parts the uploaded parts, each in the structure: * {Integer} number partNo * {String} etag part etag uploadPartCopy result.res.header.etag * @param {Object} options * {Object} [options.callback] The callback parameter is composed of a JSON string encoded in Base64 * {String} options.callback.url the OSS sends a callback request to this URL * {String} [options.callback.host] The host header value for initiating callback requests * {String} options.callback.body The value of the request body when a callback is initiated * {String} [options.callback.contentType] The Content-Type of the callback requests initiated * {Boolean} [options.callback.callbackSNI] Whether OSS sends SNI to the origin address specified by callbackUrl when a callback request is initiated from the client * {Object} [options.callback.customValue] Custom parameters are a map of key-values, e.g: * customValue = { * key1: 'value1', * key2: 'value2' * } */ proto.completeMultipartUpload = async function completeMultipartUpload(name, uploadId, parts, options) { const completeParts = parts .concat() .sort((a, b) => a.number - b.number) .filter((item, index, arr) => !index || item.number !== arr[index - 1].number); let xml = '\n\n'; for (let i = 0; i < completeParts.length; i++) { const p = completeParts[i]; xml += '\n'; xml += `${p.number}\n`; xml += `${p.etag}\n`; xml += '\n'; } xml += ''; options = options || {}; let opt = {}; opt = deepCopyWith(options, _ => { if (isBuffer(_)) return null; }); opt.subres = { uploadId }; opt.headers = omit(opt.headers, ['x-oss-server-side-encryption', 'x-oss-storage-class']); const params = this._objectRequestParams('POST', name, opt); callback.encodeCallback(params, opt); params.mime = 'xml'; params.content = xml; if (!(params.headers && params.headers['x-oss-callback'])) { params.xmlResponse = true; } params.successStatuses = [200]; const result = await this.request(params); if (options.progress) { await options.progress(1, null, result.res); } const ret = { res: result.res, bucket: params.bucket, name, etag: result.res.headers.etag }; if (params.headers && params.headers['x-oss-callback']) { ret.data = JSON.parse(result.data.toString()); } return ret; }; /** * Upload a part in a multipart upload transaction * @param {String} name the object name * @param {String} uploadId the upload id * @param {Integer} partNo the part number * @param {Object} data the body data * @param {Object} options */ proto._uploadPart = async function _uploadPart(name, uploadId, partNo, data, options) { options = options || {}; const opt = {}; copy(options).to(opt); opt.headers = opt.headers || {}; opt.headers['Content-Length'] = data.size; // Uploading shards does not require x-oss headers. opt.headers = omit(opt.headers, ['x-oss-server-side-encryption', 'x-oss-storage-class']); opt.subres = { partNumber: partNo, uploadId }; const params = this._objectRequestParams('PUT', name, opt); params.mime = opt.mime; const isBrowserEnv = process && process.browser; isBrowserEnv ? (params.content = data.content) : (params.stream = data.stream); params.successStatuses = [200]; params.disabledMD5 = options.disabledMD5; const result = await this.request(params); if (!result.res.headers.etag) { throw new Error( 'Please set the etag of expose-headers in OSS \n https://help.aliyun.com/document_detail/32069.html' ); } if (data.stream) { data.stream = null; params.stream = null; } return { name, etag: result.res.headers.etag, res: result.res }; };