Skip to content

Commit

Permalink
migrate copyObject api to ts
Browse files Browse the repository at this point in the history
  • Loading branch information
prakashsvmx committed May 13, 2024
1 parent fd89297 commit a51b147
Show file tree
Hide file tree
Showing 10 changed files with 198 additions and 246 deletions.
23 changes: 8 additions & 15 deletions docs/API.md
Original file line number Diff line number Diff line change
Expand Up @@ -1089,32 +1089,25 @@ minioClient.fPutObject('mybucket', '40mbfile', file, metaData, function (err, ob

<a name="copyObject"></a>

### copyObject(bucketName, objectName, sourceObject, conditions[, callback])
### copyObject(bucketName, objectName, sourceObject [,conditions])

Copy a source object into a new object in the specified bucket.

**Parameters**

| Param | Type | Description |
| ------------------------------------- | ---------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `bucketName` | _string_ | Name of the bucket. |
| `objectName` | _string_ | Name of the object. |
| `sourceObject` | _string_ | Path of the file to be copied. |
| `conditions` | _CopyConditions_ | Conditions to be satisfied before allowing object copy. |
| `callback(err, {etag, lastModified})` | _function_ | Non-null `err` indicates error, `etag` _string_ and lastModified _Date_ are the etag and the last modified date of the object newly copied. If no callback is passed, a `Promise` is returned. |
| Param | Type | Description |
| -------------- | ---------------- | ------------------------------------------------------- |
| `bucketName` | _string_ | Name of the bucket. |
| `objectName` | _string_ | Name of the object. |
| `sourceObject` | _string_ | Path of the file to be copied. |
| `conditions` | _CopyConditions_ | Conditions to be satisfied before allowing object copy. |

**Example**

```js
const conds = new Minio.CopyConditions()
conds.setMatchETag('bd891862ea3e22c93ed53a098218791d')
minioClient.copyObject('mybucket', 'newobject', '/mybucket/srcobject', conds, function (e, data) {
if (e) {
return console.log(e)
}
console.log('Successfully copied the object:')
console.log('etag = ' + data.etag + ', lastModified = ' + data.lastModified)
})
await minioClient.copyObject('mybucket', 'newobject', '/mybucket/srcobject', conds)
```

<a name="statObject"></a>
Expand Down
44 changes: 0 additions & 44 deletions examples/copy-object.js

This file was deleted.

12 changes: 12 additions & 0 deletions examples/copy-object.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import * as Minio from 'minio'

const s3Client = new Minio.Client({
endPoint: 's3.amazonaws.com',
accessKey: 'YOUR-ACCESSKEYID',
secretKey: 'YOUR-SECRETACCESSKEY',
})

const conds = new Minio.CopyConditions()
conds.setMatchETag('bd891862ea3e22c93ed53a098218791d')

await s3Client.copyObject('my-bucketname', 'my-objectname', '/my-src-bucketname/my-src-objectname', conds)
126 changes: 125 additions & 1 deletion src/internal/client.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import * as crypto from 'node:crypto'
import * as fs from 'node:fs'
import type { IncomingHttpHeaders } from 'node:http'
import * as http from 'node:http'
import * as https from 'node:https'
import * as path from 'node:path'
Expand All @@ -15,13 +16,22 @@ import xml2js from 'xml2js'
import { CredentialProvider } from '../CredentialProvider.ts'
import * as errors from '../errors.ts'
import type { SelectResults } from '../helpers.ts'
import { DEFAULT_REGION, LEGAL_HOLD_STATUS, RETENTION_MODES, RETENTION_VALIDITY_UNITS } from '../helpers.ts'
import {
CopyDestinationOptions,
CopySourceOptions,
DEFAULT_REGION,
LEGAL_HOLD_STATUS,
RETENTION_MODES,
RETENTION_VALIDITY_UNITS,
} from '../helpers.ts'
import { signV4 } from '../signing.ts'
import { fsp, streamPromise } from './async.ts'
import { CopyConditions } from './copy-conditions.ts'
import { Extensions } from './extensions.ts'
import {
extractMetadata,
getContentLength,
getSourceVersionId,
getVersionId,
hashBinary,
insertContentType,
Expand Down Expand Up @@ -59,6 +69,8 @@ import type {
BucketItemStat,
BucketStream,
BucketVersioningConfiguration,
CopyObjectResult,
CopyObjectResultV2,
EncryptionConfig,
GetObjectLegalHoldOptions,
GetObjectRetentionOpts,
Expand Down Expand Up @@ -2453,4 +2465,116 @@ export class TypedClient {
const batchResults = await Promise.all(batches.map(runDeleteObjects))
return batchResults.flat()
}

private async copyObjectV1(
bucketName: string,
objectName: string,
srcObject: string,
conditions?: null | CopyConditions,
) {
if (typeof conditions == 'function') {
conditions = null
}

if (!isValidBucketName(bucketName)) {
throw new errors.InvalidBucketNameError('Invalid bucket name: ' + bucketName)
}
if (!isValidObjectName(objectName)) {
throw new errors.InvalidObjectNameError(`Invalid object name: ${objectName}`)
}
if (!isString(srcObject)) {
throw new TypeError('srcObject should be of type "string"')
}
if (srcObject === '') {
throw new errors.InvalidPrefixError(`Empty source prefix`)
}

if (conditions != null && !(conditions instanceof CopyConditions)) {
throw new TypeError('conditions should be of type "CopyConditions"')
}

const headers: RequestHeaders = {}
headers['x-amz-copy-source'] = uriResourceEscape(srcObject)

if (conditions) {
if (conditions.modified !== '') {
headers['x-amz-copy-source-if-modified-since'] = conditions.modified
}
if (conditions.unmodified !== '') {
headers['x-amz-copy-source-if-unmodified-since'] = conditions.unmodified
}
if (conditions.matchETag !== '') {
headers['x-amz-copy-source-if-match'] = conditions.matchETag
}
if (conditions.matchETagExcept !== '') {
headers['x-amz-copy-source-if-none-match'] = conditions.matchETagExcept
}
}

const method = 'PUT'

const res = await this.makeRequestAsync({ method, bucketName, objectName, headers })
const body = await readAsString(res)
return xmlParsers.parseCopyObject(body)
}

private async copyObjectV2(
sourceConfig: CopySourceOptions,
destConfig: CopyDestinationOptions,
): Promise<CopyObjectResultV2> {
if (!(sourceConfig instanceof CopySourceOptions)) {
throw new errors.InvalidArgumentError('sourceConfig should of type CopySourceOptions ')
}
if (!(destConfig instanceof CopyDestinationOptions)) {
throw new errors.InvalidArgumentError('destConfig should of type CopyDestinationOptions ')
}
if (!destConfig.validate()) {
return Promise.reject()
}
if (!destConfig.validate()) {
return Promise.reject()
}

const headers = Object.assign({}, sourceConfig.getHeaders(), destConfig.getHeaders())

const bucketName = destConfig.Bucket
const objectName = destConfig.Object

const method = 'PUT'

const res = await this.makeRequestAsync({ method, bucketName, objectName, headers })
const body = await readAsString(res)
const copyRes = xmlParsers.parseCopyObject(body)
const resHeaders: IncomingHttpHeaders = res.headers

const sizeHeaderValue = resHeaders && resHeaders['content-length']
const size = typeof sizeHeaderValue === 'number' ? sizeHeaderValue : undefined

return {
Bucket: destConfig.Bucket,
Key: destConfig.Object,
LastModified: copyRes.lastModified,
MetaData: extractMetadata(resHeaders as ResponseHeader),
VersionId: getVersionId(resHeaders as ResponseHeader),
SourceVersionId: getSourceVersionId(resHeaders as ResponseHeader),
Etag: sanitizeETag(resHeaders.etag),
Size: size,
}
}

async copyObject(source: CopySourceOptions, dest: CopyDestinationOptions): Promise<CopyObjectResult>
async copyObject(
bucketName: string,
objectName: string,
srcObject: string,
conditions?: CopyConditions,
): Promise<CopyObjectResult>
async copyObject(...allArgs: unknown[]): Promise<CopyObjectResult> {
if (typeof allArgs[0] === 'string') {
// @ts-ignore
return await this.copyObjectV1(...allArgs)
}
// @ts-ignore
return await this.copyObjectV2(...allArgs)
}
}
17 changes: 17 additions & 0 deletions src/internal/type.ts
Original file line number Diff line number Diff line change
Expand Up @@ -416,3 +416,20 @@ export type RemoveObjectsResponse =
VersionId?: string
}
}

export type CopyObjectResultV1 = {
etag: string
lastModified: string | Date
}
export type CopyObjectResultV2 = {
Bucket?: string
Key?: string
LastModified: string | Date
MetaData?: ResponseHeader
VersionId?: string | null
SourceVersionId?: string | null
Etag?: string
Size?: number
}

export type CopyObjectResult = CopyObjectResultV1 | CopyObjectResultV2
35 changes: 34 additions & 1 deletion src/internal/xml-parser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,13 @@ import * as errors from '../errors.ts'
import { SelectResults } from '../helpers.ts'
import { isObject, parseXml, readableStream, sanitizeETag, sanitizeObjectKey, toArray } from './helper.ts'
import { readAsString } from './response.ts'
import type { BucketItemFromList, BucketItemWithMetadata, ObjectLockInfo, ReplicationConfig } from './type.ts'
import type {
BucketItemFromList,
BucketItemWithMetadata,
CopyObjectResultV1,
ObjectLockInfo,
ReplicationConfig,
} from './type.ts'
import { RETENTION_VALIDITY_UNITS } from './type.ts'

// parse XML response for bucket region
Expand Down Expand Up @@ -566,3 +572,30 @@ export function removeObjectsParser(xml: string) {
}
return []
}

// parse XML response for copy object
export function parseCopyObject(xml: string): CopyObjectResultV1 {
const result: CopyObjectResultV1 = {
etag: '',
lastModified: '',
}

let xmlobj = parseXml(xml)
if (!xmlobj.CopyObjectResult) {
throw new errors.InvalidXMLError('Missing tag: "CopyObjectResult"')
}
xmlobj = xmlobj.CopyObjectResult
if (xmlobj.ETag) {
result.etag = xmlobj.ETag.replace(/^"/g, '')
.replace(/"$/g, '')
.replace(/^&quot;/g, '')
.replace(/&quot;$/g, '')
.replace(/^&#34;/g, '')
.replace(/&#34;$/g, '')
}
if (xmlobj.LastModified) {
result.lastModified = new Date(xmlobj.LastModified)
}

return result
}
14 changes: 0 additions & 14 deletions src/minio.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -147,20 +147,6 @@ export class Client extends TypedClient {

listObjectsV2(bucketName: string, prefix?: string, recursive?: boolean, startAfter?: string): BucketStream<BucketItem>

copyObject(
bucketName: string,
objectName: string,
sourceObject: string,
conditions: CopyConditions,
callback: ResultCallback<BucketItemCopy>,
): void
copyObject(
bucketName: string,
objectName: string,
sourceObject: string,
conditions: CopyConditions,
): Promise<BucketItemCopy>

removeIncompleteUpload(bucketName: string, objectName: string, callback: NoResultCallback): void
removeIncompleteUpload(bucketName: string, objectName: string): Promise<void>
composeObject(
Expand Down
Loading

0 comments on commit a51b147

Please sign in to comment.