-
Notifications
You must be signed in to change notification settings - Fork 10
Upload workers β foreground mode & retry polish #65
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. Weβll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
3371296 to
b7f305e
Compare
f462314 to
7e7cdf8
Compare
|
@guruz Can u start with this PR as next, thanks. Take your time. |
5245dc4 to
da98999
Compare
- Added FOREGROUND_SERVICE_DATA_SYNC permission and configured SystemForegroundService with dataSync type - Implemented foreground notifications with progress updates for both upload worker types - Fixed TUS upload cleanup to only clear state after successful completion
- Configured exponential backoff with 10-second initial delay for upload work requests - Changed TUS upload exceptions from IllegalStateException to IOException to enable WorkManager retry behavior
da98999 to
d7ba049
Compare
So basically: I fixed the bug with the useless round-trip and there is less overhead now, as guruz mentioned in the now-closed PR (since it was merged). @kulmann @guruz Hey. I hope you're both doing fine. So my problem is: the detekt and test run failed when I opened a separate PR for those commits. However, the checks passed here (detekt and test run). As far as I know, both the upload-improvement and upload cancellation PRs need those commit changes. Could we keep them in those 2 PRs? I closed the separate PR because it failed and I was really frustrated at the end after wasting hours managing it. I also accidentally almost deleted commits from this current PR branch because I was overwhelmed (luckily I had a backup before the rebase etc.). Could we just make an exception? I told @guruz that this PR should be the next review so I could rebase the cancellation PR to the upstream main after this PR is merged. I am sorry for this complication. |
|
hey :) Thanks, I will take a look at this one. |
guruz
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Code looks OK i guess :) i will test it and then merge
| .setContentIntent(NotificationUtils.composePendingIntentToUploadList(appContext)) | ||
| .setOnlyAlertOnce(true) | ||
| .setOngoing(true) | ||
| .setSubText(fileName) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I wonder how this behaves if there is multiple (big) uploads going on at same time. But fine for now, can solve this later if there is an issue.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe. Are those two differently named files? At least on server one of them should be (2) if it's two concurrent uploads?
How does it look in the uploads tab in the app?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
No they have same file name, wait. Let me reproduce it to u with real different file names.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
And i just uploaded the same file twice. We need to see how it behaves at end! If it does rename automatically to (2) !! :)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Really good catch from u. If it has the same file name, the second upload gets cancelled basically. Thats not a good result. Can we merge the PRs first, then we go for the fix in a seperate PR? It is really a chaotic situation otherwise. We will find a good solution, pretty sure :)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can also do it in this PR if u dont mind...
TUS Creation-with-Upload Test WalkthroughGoalVerify that the Changes[TusIntegrationTest.kt]Added a new test method
@Test
fun creation_with_upload_returns_offset() {
val client = newClient()
val collectionPath = "/remote.php/dav/uploads/$userId"
val locationPath = "$collectionPath/UPLD-WITH-DATA"
val localFile = File.createTempFile("tus", ".bin").apply {
writeBytes(ByteArray(100) { it.toByte() })
}
val firstChunkSize = 50L
// POST Create with Upload -> 201 + Location + Upload-Offset
server.enqueue(
MockResponse()
.setResponseCode(201)
.addHeader("Tus-Resumable", "1.0.0")
.addHeader("Location", locationPath)
.addHeader("Upload-Offset", firstChunkSize.toString())
)
val create = CreateTusUploadRemoteOperation(
file = localFile,
remotePath = "/test-with-data.bin",
mimetype = "application/octet-stream",
metadata = mapOf("filename" to "test-with-data.bin"),
useCreationWithUpload = true,
firstChunkSize = firstChunkSize,
tusUrl = null,
collectionUrlOverride = server.url(collectionPath).toString(),
base64Encoder = object : Base64Encoder {
override fun encode(bytes: ByteArray): String =
Base64.getEncoder().encodeToString(bytes)
}
)
val createResult = create.execute(client)
assertTrue("Create operation failed", createResult.isSuccess)
val creationResult = createResult.data
assertNotNull(creationResult)
assertEquals(firstChunkSize, creationResult!!.uploadOffset)
assertTrue(creationResult.uploadUrl.endsWith(locationPath))
// Verify POST request
val postReq = server.takeRequest()
assertEquals("POST", postReq.method)
assertEquals("Bearer $token", postReq.getHeader("Authorization"))
assertEquals("1.0.0", postReq.getHeader("Tus-Resumable"))
// creation-with-upload sends Content-Type and Content-Length for the chunk
assertEquals("application/offset+octet-stream", postReq.getHeader("Content-Type"))
assertEquals(firstChunkSize.toString(), postReq.getHeader("Content-Length"))
}Verification ResultsAutomated TestsRan the unit tests for Result: The new test case passed, confirming that the offset is correctly extracted from the creation response. |
|
By the way, I don't know what your workflow is, but it's much less confusing to me if you put a commit only in one PR :) |
ok lets continue here then |
|
@guruz is this better now? I Squashed, better structure now |
β¦pload, and error handling
563beaf to
db52ee4
Compare
ok im just stupid git has "auto merge" which git can solve himself, i won't confuse more, just tested out now... |
thanks for checking it out. Please check #36 (comment) first before continuing on this one. :) |
|
This might trigger #71 |


π Upload Improvements: Foreground Service & Smart Retry
Summary
This PR transforms our upload architecture from simple background workers into robust, resilient Foreground Services. It ensures uploads survive app backgrounding, provides real-time user feedback, and intelligently recovers from network failures.
Important
Dependency Note: This branch is built on top of PR #36 (TUS Implementation).
You will see TUS-related commits in the history, but they are NOT part of this review.
This PR requires the TUS implementation as a foundation. Once PR #36 is merged/rebased, this branch will be updated.
Please review ONLY the 4 commits listed below.
π― Review Scope
Please focus your review strictly on these 4 commits:
634932780e334e44dd5c987bf1e7π οΈ Technical Deep Dive
1. Foreground Service Support (
6349327)The Challenge:
Standard
CoroutineWorkers are deprioritized by Android's battery optimizer (Doze Mode) when the screen is off. This leads to "silent failures" where uploads just stop halfway.The Solution:
We promote the worker to a Foreground Service. This tells the OS: "This is a user-initiated, long-running task. Do not kill it."
Implementation Details:
android:foregroundServiceType="dataSync"(Required for Android 14+ compliance).setForeground()immediately upon start.2. Real-Time Progress Updates (
80e334e)The Challenge:
TUS uploads chunk data, but the notification wasn't updating between chunks. Users saw "Uploading..." but the bar was stuck, leading to "Is it frozen?" anxiety.
The Solution:
We implemented a dedicated
scheduleForegroundUpdatemethod that forces a notification refresh whenever a chunk completes.3. I/O Optimization (
44dd5c9)The Challenge:
The worker was calling
file.length()inside the progress loop. On some file systems, this is a blocking I/O operation. Doing this 100 times per second during a fast upload is wasteful.The Solution:
Read once, cache forever.
4. Exponential Backoff Retry Policy (
87bf1e7)The Challenge:
Mobile networks are flaky. A 1-second tunnel drop shouldn't fail a 2GB upload. However, retrying immediately is bad because it hammers the server if it's down.
The Solution:
We implemented Exponential Backoff. If an upload fails due to network issues, we wait longer and longer between retries.
Configuration:
Smart Exception Handling:
We now distinguish between "Retryable" and "Fatal" errors:
IOException(Network) β‘οΈ Retry (Trigger backoff)FileNotFoundException(User deleted file) β‘οΈ Fail (Don't retry)π§ͺ Verification
ForegroundServiceStartNotAllowedExceptioncrashes.Ready for review! π