|                                                                                                                                                                                                                                                                                                                                                                                                                              |  | const debug = require('debug')('ali-oss:object');const fs = require('fs');const is = require('is-type-of');const copy = require('copy-to');const path = require('path');const mime = require('mime');const callback = require('./common/callback');const { Transform } = require('stream');const pump = require('pump');const { isBuffer } = require('./common/utils/isBuffer');const { retry } = require('./common/utils/retry');const { obj2xml } = require('./common/utils/obj2xml');const { parseRestoreInfo } = require('./common/utils/parseRestoreInfo');
const proto = exports;
/** * Object operations */
/** * append an object from String(file path)/Buffer/ReadableStream * @param {String} name the object key * @param {Mixed} file String(file path)/Buffer/ReadableStream * @param {Object} options * @return {Object} */proto.append = async function append(name, file, options) {  options = options || {};  if (options.position === undefined) options.position = '0';  options.subres = {    append: '',    position: options.position  };  options.method = 'POST';
  const result = await this.put(name, file, options);  result.nextAppendPosition = result.res.headers['x-oss-next-append-position'];  return result;};
/** * put an object from String(file path)/Buffer/ReadableStream * @param {String} name the object key * @param {Mixed} file String(file path)/Buffer/ReadableStream * @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' *                  } * @return {Object} */proto.put = async function put(name, file, options) {  let content;  options = options || {};  name = this._objectName(name);
  if (isBuffer(file)) {    content = file;  } else if (is.string(file)) {    const stats = fs.statSync(file);    if (!stats.isFile()) {      throw new Error(`${file} is not file`);    }    options.mime = options.mime || mime.getType(path.extname(file));    options.contentLength = await this._getFileSize(file);    const getStream = () => fs.createReadStream(file);    const putStreamStb = (objectName, makeStream, configOption) => {      return this.putStream(objectName, makeStream(), configOption);    };    return await retry(putStreamStb, this.options.retryMax, {      errorHandler: err => {        const _errHandle = _err => {          const statusErr = [-1, -2].includes(_err.status);          const requestErrorRetryHandle = this.options.requestErrorRetryHandle || (() => true);          return statusErr && requestErrorRetryHandle(_err);        };        if (_errHandle(err)) return true;        return false;      }    })(name, getStream, options);  } else if (is.readableStream(file)) {    return await this.putStream(name, file, options);  } else {    throw new TypeError('Must provide String/Buffer/ReadableStream for put.');  }
  options.headers = options.headers || {};  this._convertMetaToHeaders(options.meta, options.headers);
  const method = options.method || 'PUT';  const params = this._objectRequestParams(method, name, options);
  callback.encodeCallback(params, options);
  params.mime = options.mime;  params.content = content;  params.successStatuses = [200];
  const result = await this.request(params);
  const ret = {    name,    url: this._objectUrl(name),    res: result.res  };
  if (params.headers && params.headers['x-oss-callback']) {    ret.data = JSON.parse(result.data.toString());  }
  return ret;};
/** * put an object from ReadableStream. If `options.contentLength` is * not provided, chunked encoding is used. * @param {String} name the object key * @param {Readable} stream the ReadableStream * @param {Object} options * @return {Object} */proto.putStream = async function putStream(name, stream, options) {  options = options || {};  options.headers = options.headers || {};  name = this._objectName(name);  if (options.contentLength) {    options.headers['Content-Length'] = options.contentLength;  } else {    options.headers['Transfer-Encoding'] = 'chunked';  }  this._convertMetaToHeaders(options.meta, options.headers);
  const method = options.method || 'PUT';  const params = this._objectRequestParams(method, name, options);  callback.encodeCallback(params, options);  params.mime = options.mime;  const transform = new Transform();  // must remove http stream header for signature
  transform._transform = function _transform(chunk, encoding, done) {    this.push(chunk);    done();  };  params.stream = pump(stream, transform);  params.successStatuses = [200];
  const result = await this.request(params);
  const ret = {    name,    url: this._objectUrl(name),    res: result.res  };
  if (params.headers && params.headers['x-oss-callback']) {    ret.data = JSON.parse(result.data.toString());  }
  return ret;};
proto.getStream = async function getStream(name, options) {  options = options || {};
  if (options.process) {    options.subres = options.subres || {};    options.subres['x-oss-process'] = options.process;  }
  const params = this._objectRequestParams('GET', name, options);  params.customResponse = true;  params.successStatuses = [200, 206, 304];
  const result = await this.request(params);
  return {    stream: result.res,    res: {      status: result.status,      headers: result.headers    }  };};
proto.putMeta = async function putMeta(name, meta, options) {  return await this.copy(name, name, {    meta: meta || {},    timeout: options && options.timeout,    ctx: options && options.ctx  });};
proto.list = async function list(query, options) {  // prefix, marker, max-keys, delimiter
  const params = this._objectRequestParams('GET', '', options);  params.query = query;  params.xmlResponse = true;  params.successStatuses = [200];
  const result = await this.request(params);  let objects = result.data.Contents || [];  const that = this;  if (objects) {    if (!Array.isArray(objects)) {      objects = [objects];    }
    objects = objects.map(obj => ({      name: obj.Key,      url: that._objectUrl(obj.Key),      lastModified: obj.LastModified,      etag: obj.ETag,      type: obj.Type,      size: Number(obj.Size),      storageClass: obj.StorageClass,      owner: {        id: obj.Owner.ID,        displayName: obj.Owner.DisplayName      },      restoreInfo: parseRestoreInfo(obj.RestoreInfo)    }));  }  let prefixes = result.data.CommonPrefixes || null;  if (prefixes) {    if (!Array.isArray(prefixes)) {      prefixes = [prefixes];    }    prefixes = prefixes.map(item => item.Prefix);  }  return {    res: result.res,    objects,    prefixes,    nextMarker: result.data.NextMarker || null,    isTruncated: result.data.IsTruncated === 'true'  };};
proto.listV2 = async function listV2(query = {}, options = {}) {  const continuation_token = query['continuation-token'] || query.continuationToken;  delete query['continuation-token'];  delete query.continuationToken;  if (continuation_token) {    options.subres = Object.assign(      {        'continuation-token': continuation_token      },      options.subres    );  }  const params = this._objectRequestParams('GET', '', options);  params.query = Object.assign({ 'list-type': 2 }, query);  delete params.query['continuation-token'];  delete query.continuationToken;  params.xmlResponse = true;  params.successStatuses = [200];
  const result = await this.request(params);  let objects = result.data.Contents || [];  const that = this;  if (objects) {    if (!Array.isArray(objects)) {      objects = [objects];    }
    objects = objects.map(obj => {      let owner = null;      if (obj.Owner) {        owner = {          id: obj.Owner.ID,          displayName: obj.Owner.DisplayName        };      }
      return {        name: obj.Key,        url: that._objectUrl(obj.Key),        lastModified: obj.LastModified,        etag: obj.ETag,        type: obj.Type,        size: Number(obj.Size),        storageClass: obj.StorageClass,        owner,        restoreInfo: parseRestoreInfo(obj.RestoreInfo)      };    });  }  let prefixes = result.data.CommonPrefixes || null;  if (prefixes) {    if (!Array.isArray(prefixes)) {      prefixes = [prefixes];    }    prefixes = prefixes.map(item => item.Prefix);  }  return {    res: result.res,    objects,    prefixes,    isTruncated: result.data.IsTruncated === 'true',    keyCount: +result.data.KeyCount,    continuationToken: result.data.ContinuationToken || null,    nextContinuationToken: result.data.NextContinuationToken || null  };};
/** * Restore Object * @param {String} name the object key * @param {Object} options {type : Archive or ColdArchive} * @returns {{res}} */proto.restore = async function restore(name, options = { type: 'Archive' }) {  options = options || {};  options.subres = Object.assign({ restore: '' }, options.subres);  if (options.versionId) {    options.subres.versionId = options.versionId;  }  const params = this._objectRequestParams('POST', name, options);  const paramsXMLObj = {    RestoreRequest: {      Days: options.Days ? options.Days : 2    }  };
  if (options.type === 'ColdArchive' || options.type === 'DeepColdArchive') {    paramsXMLObj.RestoreRequest.JobParameters = {      Tier: options.JobParameters ? options.JobParameters : 'Standard'    };  }
  params.content = obj2xml(paramsXMLObj, {    headers: true  });  params.mime = 'xml';  params.successStatuses = [202];
  const result = await this.request(params);
  return {    res: result.res  };};
proto._objectUrl = function _objectUrl(name) {  return this._getReqUrl({ bucket: this.options.bucket, object: name });};
/** * generator request params * @return {Object} params * * @api private */
proto._objectRequestParams = function (method, name, options) {  if (!this.options.bucket && !this.options.cname) {    throw new Error('Please create a bucket first');  }
  options = options || {};  name = this._objectName(name);  const params = {    object: name,    bucket: this.options.bucket,    method,    subres: options && options.subres,    additionalHeaders: options && options.additionalHeaders,    timeout: options && options.timeout,    ctx: options && options.ctx  };
  if (options.headers) {    params.headers = {};    copy(options.headers).to(params.headers);  }  return params;};
proto._objectName = function (name) {  return name.replace(/^\/+/, '');};
proto._statFile = function (filepath) {  return new Promise((resolve, reject) => {    fs.stat(filepath, (err, stats) => {      if (err) {        reject(err);      } else {        resolve(stats);      }    });  });};
proto._convertMetaToHeaders = function (meta, headers) {  if (!meta) {    return;  }
  Object.keys(meta).forEach(k => {    headers[`x-oss-meta-${k}`] = meta[k];  });};
proto._deleteFileSafe = function (filepath) {  return new Promise(resolve => {    fs.exists(filepath, exists => {      if (!exists) {        resolve();      } else {        fs.unlink(filepath, err => {          if (err) {            debug('unlink %j error: %s', filepath, err);          }          resolve();        });      }    });  });};
 |