diff --git a/opencloudApp/src/main/java/eu/opencloud/android/workers/UploadFileFromContentUriWorker.kt b/opencloudApp/src/main/java/eu/opencloud/android/workers/UploadFileFromContentUriWorker.kt index bee2f350d..42f7eadee 100644 --- a/opencloudApp/src/main/java/eu/opencloud/android/workers/UploadFileFromContentUriWorker.kt +++ b/opencloudApp/src/main/java/eu/opencloud/android/workers/UploadFileFromContentUriWorker.kt @@ -50,7 +50,6 @@ import eu.opencloud.android.domain.transfers.TransferRepository import eu.opencloud.android.domain.transfers.model.OCTransfer import eu.opencloud.android.domain.transfers.model.TransferResult import eu.opencloud.android.domain.transfers.model.TransferStatus -import eu.opencloud.android.extensions.isContentUri import eu.opencloud.android.extensions.parseError import eu.opencloud.android.domain.capabilities.usecases.GetStoredCapabilitiesUseCase import eu.opencloud.android.lib.common.OpenCloudAccount @@ -140,9 +139,20 @@ class UploadFileFromContentUriWorker( getWebdavUrlForSpaceUseCase(GetWebDavUrlForSpaceUseCase.Params(accountName = account.name, spaceId = ocTransfer.spaceId)) val localStorageProvider: LocalStorageProvider by inject() - cachePath = localStorageProvider.getTemporalPath(account.name, ocTransfer.spaceId) + uploadPath - - if (ocTransfer.isContentUri(appContext)) { + // Prepend uploadId to the cache filename so two transfers targeting the same + // uploadPath (e.g. same filename in two different SAF source folders) can't collide + // on the same cache file and PUT each other's bytes (issue #78). Flat layout — no + // nested subdirs to clean up, original filename and extension preserved for debugging. + val flatCacheName = "${uploadIdInStorageManager}_" + File(uploadPath).name + cachePath = localStorageProvider.getTemporalPath(account.name, ocTransfer.spaceId) + + File.separator + flatCacheName + + // Re-copy if the cache file is missing or empty. A previous run may have copied it + // and then had it removed (e.g. by removeCacheFile() at the end of a successful run + // that the OS killed before bookkeeping). Only the contentUri from worker params is + // authoritative. + val cacheFile = File(cachePath) + if (!cacheFile.exists() || cacheFile.length() == 0L) { checkDocumentFileExists() checkPermissionsToReadDocumentAreGranted() copyFileToLocalStorage() diff --git a/opencloudComLibrary/src/main/java/eu/opencloud/android/lib/common/network/FileRequestBody.kt b/opencloudComLibrary/src/main/java/eu/opencloud/android/lib/common/network/FileRequestBody.kt index 5290e2601..9e2df7420 100644 --- a/opencloudComLibrary/src/main/java/eu/opencloud/android/lib/common/network/FileRequestBody.kt +++ b/opencloudComLibrary/src/main/java/eu/opencloud/android/lib/common/network/FileRequestBody.kt @@ -51,26 +51,24 @@ open class FileRequestBody( override fun contentLength(): Long = file.length() override fun writeTo(sink: BufferedSink) { - val source: Source - var it: Iterator - try { - source = file.source() - var transferred: Long = 0 - var read: Long - while (source.read(sink.buffer, BYTES_TO_READ).also { read = it } != -1L) { + // Don't swallow IO errors here — a missing source file used to silently produce + // a 0-byte PUT that the server happily stored (issue #78). + val source: Source = file.source() + var transferred: Long = 0 + var read: Long + source.use { src -> + while (src.read(sink.buffer, BYTES_TO_READ).also { read = it } != -1L) { transferred += read sink.flush() synchronized(dataTransferListeners) { - it = dataTransferListeners.iterator() + val it = dataTransferListeners.iterator() while (it.hasNext()) { it.next().onTransferProgress(read, transferred, file.length(), file.absolutePath) } } } - Timber.d("File with name ${file.name} and size ${file.length()} written in request body") - } catch (e: Exception) { - Timber.e(e) } + Timber.d("File with name ${file.name} and size ${file.length()} written in request body") } override fun addDatatransferProgressListener(listener: OnDatatransferProgressListener) {