Skip to content

Commit

Permalink
feat: allow overwriting objects when copying (#530)
Browse files Browse the repository at this point in the history
  • Loading branch information
fenos authored Aug 8, 2024
1 parent 5a2ffbd commit d68eb9e
Show file tree
Hide file tree
Showing 5 changed files with 63 additions and 24 deletions.
5 changes: 5 additions & 0 deletions src/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,7 @@ type StorageConfigType = {
rateLimiterRedisCommandTimeout: number
uploadSignedUrlExpirationTime: number
tusUrlExpiryMs: number
tusMaxConcurrentUploads: number
tusPath: string
tusPartSize: number
tusUseFileVersionSeparator: boolean
Expand Down Expand Up @@ -231,6 +232,10 @@ export function getConfig(options?: { reload?: boolean }): StorageConfigType {
getOptionalConfigFromEnv('TUS_URL_EXPIRY_MS') || (1000 * 60 * 60).toString(),
10
),
tusMaxConcurrentUploads: parseInt(
getOptionalConfigFromEnv('TUS_MAX_CONCURRENT_UPLOADS') || '500',
10
),
tusUseFileVersionSeparator:
getOptionalConfigFromEnv('TUS_USE_FILE_VERSION_SEPARATOR') === 'true',

Expand Down
2 changes: 1 addition & 1 deletion src/http/routes/tus/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ function createTusStore() {
partSize: tusPartSize * 1024 * 1024, // Each uploaded part will have ${tusPartSize}MB,
expirationPeriodInMilliseconds: tusUrlExpiryMs,
cache: new AlsMemoryKV(),
maxConcurrentPartUploads: 100,
maxConcurrentPartUploads: 500,
s3ClientConfig: {
requestHandler: new NodeHttpHandler({
...agent,
Expand Down
50 changes: 40 additions & 10 deletions src/storage/object.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ interface CopyObjectParams {
destinationKey: string
owner?: string
copyMetadata?: boolean
upsert?: boolean
conditions?: {
ifMatch?: string
ifNoneMatch?: string
Expand Down Expand Up @@ -280,6 +281,7 @@ export class ObjectStorage {
owner,
conditions,
copyMetadata,
upsert,
}: CopyObjectParams) {
mustBeValidKey(destinationKey)

Expand Down Expand Up @@ -310,7 +312,7 @@ export class ObjectStorage {
bucketId: destinationBucket,
objectName: destinationKey,
owner,
isUpsert: false,
isUpsert: upsert,
})

try {
Expand All @@ -325,14 +327,42 @@ export class ObjectStorage {

const metadata = await this.backend.headObject(storageS3Bucket, s3DestinationKey, newVersion)

const destObject = await this.db.createObject({
...originObject,
bucket_id: destinationBucket,
name: destinationKey,
owner,
metadata,
user_metadata: copyMetadata ? originObject.user_metadata : undefined,
version: newVersion,
const destinationObject = await this.db.asSuperUser().withTransaction(async (db) => {
await db.waitObjectLock(destinationBucket, destinationKey, undefined, {
timeout: 3000,
})

const existingDestObject = await db.findObject(
this.bucketId,
destinationKey,
'id,name,metadata,version,bucket_id',
{
dontErrorOnEmpty: true,
forUpdate: true,
}
)

const destinationObject = await db.upsertObject({
...originObject,
bucket_id: destinationBucket,
name: destinationKey,
owner,
metadata,
user_metadata: copyMetadata ? originObject.user_metadata : undefined,
version: newVersion,
})

if (existingDestObject) {
await ObjectAdminDelete.send({
name: existingDestObject.name,
bucketId: existingDestObject.bucket_id,
tenant: this.db.tenant(),
version: existingDestObject.version,
reqId: this.db.reqId,
})
}

return destinationObject
})

await ObjectCreatedCopyEvent.sendWebhook({
Expand All @@ -345,7 +375,7 @@ export class ObjectStorage {
})

return {
destObject,
destObject: destinationObject,
httpStatusCode: copyResult.httpStatusCode,
eTag: copyResult.eTag,
lastModified: copyResult.lastModified,
Expand Down
1 change: 1 addition & 0 deletions src/storage/protocols/s3/s3-handler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -996,6 +996,7 @@ export class S3ProtocolHandler {
destinationBucket: Bucket,
destinationKey: Key,
owner: this.owner,
upsert: true,
conditions: {
ifMatch: command.CopySourceIfMatch,
ifNoneMatch: command.CopySourceIfNoneMatch,
Expand Down
29 changes: 16 additions & 13 deletions src/storage/protocols/tus/postgres-locker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -53,21 +53,24 @@ export class PgLock implements Lock {
this.db
.withTransaction(async (db) => {
const abortController = new AbortController()
const acquired = await Promise.race([
this.waitTimeout(15000, abortController.signal),
this.acquireLock(db, this.id, abortController.signal),
])

abortController.abort()

if (!acquired) {
throw ERRORS.LockTimeout()
try {
const acquired = await Promise.race([
this.waitTimeout(5000, abortController.signal),
this.acquireLock(db, this.id, abortController.signal),
])

if (!acquired) {
throw ERRORS.LockTimeout()
}

await new Promise<void>((innerResolve) => {
this.tnxResolver = innerResolve
resolve()
})
} finally {
abortController.abort()
}

await new Promise<void>((innerResolve) => {
this.tnxResolver = innerResolve
resolve()
})
})
.catch(reject)
})
Expand Down

0 comments on commit d68eb9e

Please sign in to comment.