mirror of
https://github.com/diced/zipline.git
synced 2025-12-12 15:50:11 -08:00
fix: #931
This commit is contained in:
@@ -1,11 +1,15 @@
|
|||||||
import {
|
import {
|
||||||
|
CompleteMultipartUploadCommand,
|
||||||
CopyObjectCommand,
|
CopyObjectCommand,
|
||||||
|
CreateMultipartUploadCommand,
|
||||||
DeleteObjectCommand,
|
DeleteObjectCommand,
|
||||||
DeleteObjectsCommand,
|
DeleteObjectsCommand,
|
||||||
GetObjectCommand,
|
GetObjectCommand,
|
||||||
|
HeadObjectCommand,
|
||||||
ListObjectsCommand,
|
ListObjectsCommand,
|
||||||
PutObjectCommand,
|
PutObjectCommand,
|
||||||
S3Client,
|
S3Client,
|
||||||
|
UploadPartCopyCommand,
|
||||||
} from '@aws-sdk/client-s3';
|
} from '@aws-sdk/client-s3';
|
||||||
import { NodeHttpHandler } from '@smithy/node-http-handler';
|
import { NodeHttpHandler } from '@smithy/node-http-handler';
|
||||||
import { createReadStream } from 'fs';
|
import { createReadStream } from 'fs';
|
||||||
@@ -225,7 +229,7 @@ export class S3Datasource extends Datasource {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public async size(file: string): Promise<number> {
|
public async size(file: string): Promise<number> {
|
||||||
const command = new GetObjectCommand({
|
const command = new HeadObjectCommand({
|
||||||
Bucket: this.options.bucket,
|
Bucket: this.options.bucket,
|
||||||
Key: this.key(file),
|
Key: this.key(file),
|
||||||
});
|
});
|
||||||
@@ -323,6 +327,96 @@ export class S3Datasource extends Datasource {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public async rename(from: string, to: string): Promise<void> {
|
public async rename(from: string, to: string): Promise<void> {
|
||||||
|
const size = await this.size(from);
|
||||||
|
|
||||||
|
if (size !== 0 && size > 5 * 1024 * 1024 * 1024) {
|
||||||
|
this.logger.debug('object larger than 5GB, using multipart copy for rename', { from, to, size });
|
||||||
|
|
||||||
|
const createCommand = new CreateMultipartUploadCommand({
|
||||||
|
Bucket: this.options.bucket,
|
||||||
|
Key: this.key(to),
|
||||||
|
});
|
||||||
|
|
||||||
|
let uploadId: string;
|
||||||
|
try {
|
||||||
|
const createRes = await this.client.send(createCommand);
|
||||||
|
if (!isOk(createRes.$metadata.httpStatusCode || 0)) {
|
||||||
|
this.logger.error('there was an error while initiating multipart upload');
|
||||||
|
this.logger.error('error metadata', createRes.$metadata as Record<string, unknown>);
|
||||||
|
throw new Error('Failed to initiate multipart upload');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!createRes.UploadId) {
|
||||||
|
this.logger.error('no upload ID returned while initiating multipart upload');
|
||||||
|
throw new Error('Failed to initiate multipart upload');
|
||||||
|
}
|
||||||
|
|
||||||
|
uploadId = createRes.UploadId;
|
||||||
|
} catch (e) {
|
||||||
|
this.logger.error('there was an error while initiating multipart upload');
|
||||||
|
this.logger.error('error metadata', e as Record<string, unknown>);
|
||||||
|
|
||||||
|
throw new Error('Failed to initiate multipart upload');
|
||||||
|
}
|
||||||
|
|
||||||
|
const partSize = 5 * 1024 * 1024;
|
||||||
|
const eTags = [];
|
||||||
|
|
||||||
|
for (let start = 0, part = 1; start < size; start += partSize, part++) {
|
||||||
|
const end = Math.min(start + partSize - 1, size - 1);
|
||||||
|
|
||||||
|
const uploadPartCopyCommand = new UploadPartCopyCommand({
|
||||||
|
Bucket: this.options.bucket,
|
||||||
|
Key: this.key(to),
|
||||||
|
CopySource: this.options.bucket + '/' + this.key(from),
|
||||||
|
CopySourceRange: `bytes=${start}-${end}`,
|
||||||
|
PartNumber: part,
|
||||||
|
UploadId: uploadId,
|
||||||
|
});
|
||||||
|
|
||||||
|
try {
|
||||||
|
const copyRes = await this.client.send(uploadPartCopyCommand);
|
||||||
|
if (!isOk(copyRes.$metadata.httpStatusCode || 0)) {
|
||||||
|
this.logger.error('there was an error while copying part of the object');
|
||||||
|
this.logger.error('error metadata', copyRes.$metadata as Record<string, unknown>);
|
||||||
|
throw new Error('Failed to copy part of the object');
|
||||||
|
}
|
||||||
|
|
||||||
|
eTags.push({ ETag: copyRes.CopyPartResult?.ETag, PartNumber: part });
|
||||||
|
} catch (e) {
|
||||||
|
this.logger.error('there was an error while renaming object using multipart copy');
|
||||||
|
this.logger.error('error metadata', e as Record<string, unknown>);
|
||||||
|
|
||||||
|
throw new Error('Failed to rename object using multipart copy');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const completeMultipartUploadCommand = new CompleteMultipartUploadCommand({
|
||||||
|
Bucket: this.options.bucket,
|
||||||
|
Key: this.key(to),
|
||||||
|
UploadId: uploadId,
|
||||||
|
MultipartUpload: {
|
||||||
|
Parts: eTags,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
try {
|
||||||
|
const completeRes = await this.client.send(completeMultipartUploadCommand);
|
||||||
|
if (!isOk(completeRes.$metadata.httpStatusCode || 0)) {
|
||||||
|
this.logger.error('there was an error while completing multipart upload');
|
||||||
|
this.logger.error('error metadata', completeRes.$metadata as Record<string, unknown>);
|
||||||
|
throw new Error('Failed to complete multipart upload');
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
this.logger.error('there was an error while completing multipart upload');
|
||||||
|
this.logger.error('error metadata', e as Record<string, unknown>);
|
||||||
|
|
||||||
|
throw new Error('Failed to complete multipart upload');
|
||||||
|
}
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
const copyCommand = new CopyObjectCommand({
|
const copyCommand = new CopyObjectCommand({
|
||||||
Bucket: this.options.bucket,
|
Bucket: this.options.bucket,
|
||||||
Key: this.key(to),
|
Key: this.key(to),
|
||||||
|
|||||||
Reference in New Issue
Block a user