Skip to content

Commit 9d14d7d

Browse files
authored
@tus/s3-store: Upload as multipart upload when incomplete part grows beyond minimal part size (#494)
@tus/s3-store: Upload as multipart upload when incomplete part grows
1 parent ef33aae commit 9d14d7d

File tree

2 files changed

+39
-1
lines changed

2 files changed

+39
-1
lines changed

packages/s3-store/index.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -249,6 +249,7 @@ export class S3Store extends DataStore {
249249
// eslint-disable-next-line no-async-promise-executor
250250
const deferred = new Promise<void>(async (resolve, reject) => {
251251
try {
252+
let incompletePartSize = 0
252253
// Only the first chunk of each PATCH request can prepend
253254
// an incomplete part (last chunk) from the previous request.
254255
if (isFirstChunk) {
@@ -259,6 +260,7 @@ export class S3Store extends DataStore {
259260
if (incompletePart) {
260261
// We found an incomplete part, prepend it to the chunk on disk we were about to upload,
261262
// and delete the incomplete part from the bucket. This can be done in parallel.
263+
incompletePartSize = incompletePart.length
262264
await Promise.all([
263265
this.prependIncompletePart(path, incompletePart),
264266
this.deleteIncompletePart(metadata.file.id),
@@ -268,7 +270,7 @@ export class S3Store extends DataStore {
268270

269271
const readable = fs.createReadStream(path)
270272
readable.on('error', reject)
271-
if (partSize >= this.minPartSize || isFinalPart) {
273+
if (partSize + incompletePartSize >= this.minPartSize || isFinalPart) {
272274
await this.uploadPart(metadata, readable, partNumber)
273275
} else {
274276
await this.uploadIncompletePart(metadata.file.id, readable)

packages/s3-store/test.ts

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -104,6 +104,42 @@ describe('S3DataStore', function () {
104104
}
105105
})
106106

107+
it('upload as multipart upload when incomplete part grows beyond minimal part size', async function () {
108+
const store = this.datastore
109+
const size = 10 * 1024 * 1024 // 10MB
110+
const incompleteSize = 2 * 1024 * 1024 // 2MB
111+
const getIncompletePart = sinon.spy(store, 'getIncompletePart')
112+
const uploadIncompletePart = sinon.spy(store, 'uploadIncompletePart')
113+
const uploadPart = sinon.spy(store, 'uploadPart')
114+
const upload = new Upload({
115+
id: 'incomplete-part-test-' + Uid.rand(),
116+
size: size + incompleteSize,
117+
offset: 0,
118+
})
119+
120+
let offset = upload.offset
121+
122+
await store.create(upload)
123+
offset = await store.write(
124+
Readable.from(Buffer.alloc(incompleteSize)),
125+
upload.id,
126+
offset
127+
)
128+
offset = await store.write(
129+
Readable.from(Buffer.alloc(incompleteSize)),
130+
upload.id,
131+
offset
132+
)
133+
134+
assert.equal(getIncompletePart.called, true)
135+
assert.equal(uploadIncompletePart.called, true)
136+
assert.equal(uploadPart.called, false)
137+
138+
await store.write(Readable.from(Buffer.alloc(incompleteSize)), upload.id, offset)
139+
140+
assert.equal(uploadPart.called, true)
141+
})
142+
107143
it('should process chunk size of exactly the min size', async function () {
108144
this.datastore.minPartSize = 1024 * 1024 * 5
109145
const store = this.datastore

0 commit comments

Comments
 (0)