123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383 |
- 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 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 initiatiated
- * {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));
- const stream = fs.createReadStream(file);
- options.contentLength = await this._getFileSize(file);
- return await this.putStream(name, stream, 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
- }
- }));
- }
- 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);
- 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: obj.Owner
- ? {
- id: obj.Owner.ID,
- displayName: obj.Owner.DisplayName
- }
- : null
- }));
- }
- 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
- * @returns {{res}}
- */
- proto.restore = async function restore(name, options) {
- options = options || {};
- options.subres = Object.assign({ restore: '' }, options.subres);
- if (options.versionId) {
- options.subres.versionId = options.versionId;
- }
- const params = this._objectRequestParams('POST', name, options);
- 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,
- 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();
- });
- }
- });
- });
- };
|