Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ class TusUploadHelper(

var tusUrl = transfer.tusUploadUrl
val checksumHex = transfer.tusUploadChecksum?.substringAfter("sha256:")
var createdOffset: Long? = null

if (tusUrl.isNullOrBlank()) {
val fileName = File(remotePath).name
Expand All @@ -69,24 +70,26 @@ class TusUploadHelper(

// Use creation-with-upload like the browser does for OpenCloud compatibility
val firstChunkSize = minOf(CreateTusUploadRemoteOperation.DEFAULT_FIRST_CHUNK, fileSize)
val createdLocation = executeRemoteOperation {
val creationResult = executeRemoteOperation {
CreateTusUploadRemoteOperation(
file = File(localPath),
remotePath = remotePath,
mimetype = mimeType,
metadata = metadata,
useCreationWithUpload = true,
useCreationWithUpload = true,
firstChunkSize = firstChunkSize,
tusUrl = "",
collectionUrlOverride = collectionUrl,
).execute(client)
}

if (createdLocation.isNullOrBlank()) {
throw IllegalStateException("TUS: unable to create upload resource for $remotePath")
if (creationResult == null) {
throw java.io.IOException("TUS: unable to create upload resource for $remotePath")
}

tusUrl = createdLocation
tusUrl = creationResult.uploadUrl
createdOffset = creationResult.uploadOffset
val metadataString = metadata.entries.joinToString(";") { (key, value) -> "$key=$value" }

transferRepository.updateTusState(
Expand All @@ -103,16 +106,20 @@ class TusUploadHelper(

val resolvedTusUrl = tusUrl ?: throw IllegalStateException("TUS: missing upload URL for $remotePath")

var offset = try {
executeRemoteOperation {
GetTusUploadOffsetRemoteOperation(resolvedTusUrl).execute(client)
var offset = if (createdOffset != null) {
createdOffset
} else {
try {
executeRemoteOperation {
GetTusUploadOffsetRemoteOperation(resolvedTusUrl).execute(client)
}
} catch (e: java.io.IOException) {
Timber.w(e, "TUS: failed to fetch current offset")
throw e
} catch (e: Throwable) {
Timber.w(e, "TUS: failed to fetch current offset")
0L
}
} catch (e: java.io.IOException) {
Timber.w(e, "TUS: failed to fetch current offset")
throw e
} catch (e: Throwable) {
Timber.w(e, "TUS: failed to fetch current offset")
0L
}.coerceAtLeast(0L)
Timber.d("TUS: resume offset %d / %d", offset, fileSize)
progressCallback?.invoke(offset, fileSize)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,12 @@ class CreateTusUploadRemoteOperation(
private val tusUrl: String?,
private val collectionUrlOverride: String? = null,
private val base64Encoder: Base64Encoder = DefaultBase64Encoder()
) : RemoteOperation<String>() {
) : RemoteOperation<CreateTusUploadRemoteOperation.CreationResult>() {

data class CreationResult(
val uploadUrl: String,
val uploadOffset: Long
)

interface Base64Encoder {
fun encode(bytes: ByteArray): String
Expand All @@ -41,7 +46,7 @@ class CreateTusUploadRemoteOperation(
Base64.encodeToString(bytes, Base64.NO_WRAP)
}

override fun run(client: OpenCloudClient): RemoteOperationResult<String> = try {
override fun run(client: OpenCloudClient): RemoteOperationResult<CreationResult> = try {
// Determine TUS endpoint URL based on provided parameters
val targetFileUrl = if (!tusUrl.isNullOrBlank()) {
tusUrl
Expand Down Expand Up @@ -135,21 +140,26 @@ class CreateTusUploadRemoteOperation(
val base = URL(postMethod.getFinalUrl().toString())
val resolved = resolveLocationToAbsolute(locationHeader, base)

val offsetHeader = postMethod.getResponseHeader(HttpConstants.UPLOAD_OFFSET)
val offset = offsetHeader?.toLongOrNull() ?: 0L

if (resolved != null) {
Timber.d("TUS upload resource created: %s", resolved)
RemoteOperationResult<String>(ResultCode.OK).apply { data = resolved }
Timber.d("TUS upload resource created: %s (offset=%d)", resolved, offset)
RemoteOperationResult<CreationResult>(ResultCode.OK).apply {
data = CreationResult(resolved, offset)
}
} else {
Timber.e("Location header is missing in TUS creation response")
RemoteOperationResult<String>(IllegalStateException("Location header missing")).apply {
data = ""
RemoteOperationResult<CreationResult>(IllegalStateException("Location header missing")).apply {
data = null
}
}
} else {
Timber.w("TUS creation failed with status: %d", status)
RemoteOperationResult<String>(postMethod).apply { data = "" }
RemoteOperationResult<CreationResult>(postMethod).apply { data = null }
}
} catch (e: Exception) {
val result = RemoteOperationResult<String>(e)
val result = RemoteOperationResult<CreationResult>(e)
Timber.e(e, "TUS creation operation failed")
result
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -119,12 +119,15 @@ class TusIntegrationTest {
throw RuntimeException(msg, createResult.exception)
}
assertTrue("Create operation failed", createResult.isSuccess)
val absoluteLocation = createResult.data
assertNotNull(absoluteLocation)
val creationResult = createResult.data
assertNotNull(creationResult)
val absoluteLocation = creationResult!!.uploadUrl
val offset = creationResult.uploadOffset
println("absoluteLocation: $absoluteLocation")
println("locationPath: $locationPath")
println("endsWith: ${absoluteLocation!!.endsWith(locationPath)}")
println("endsWith: ${absoluteLocation.endsWith(locationPath)}")
assertTrue(absoluteLocation.endsWith(locationPath))
assertEquals(0L, offset)

// Verify POST request headers
val postReq = server.takeRequest()
Expand Down
Loading