fix: upload progress no longer stuck at 0%

UploadWorker now promotes to foreground + reports phase immediately:
- 'Hashing…' while computing SHA256
- 'Checking server…' during resume status check
- 'Uploading…' with per-chunk percentage
Downloads tab renders the phase label for uploads
This commit is contained in:
Mathieu Moran
2026-08-12 15:44:53 -04:00
parent 5b3c04b83d
commit 03358587ca
3 changed files with 17 additions and 3 deletions
@@ -45,6 +45,7 @@ data class ActiveDownload(
val filename: String, val filename: String,
val progress: Int, val progress: Int,
val done: Boolean, val done: Boolean,
val status: String? = null,
) )
class DashcamViewModel(application: Application) : AndroidViewModel(application) { class DashcamViewModel(application: Application) : AndroidViewModel(application) {
@@ -317,9 +318,10 @@ class DashcamViewModel(application: Application) : AndroidViewModel(application)
wm.getWorkInfoByIdLiveData(uuid).observeForever { info -> wm.getWorkInfoByIdLiveData(uuid).observeForever { info ->
if (info == null) return@observeForever if (info == null) return@observeForever
val progress = info.progress.getInt("pct", 0) val progress = info.progress.getInt("pct", 0)
val status = info.progress.getString("status")
val done = info.state == WorkInfo.State.SUCCEEDED || info.state == WorkInfo.State.FAILED val done = info.state == WorkInfo.State.SUCCEEDED || info.state == WorkInfo.State.FAILED
_uploadProgress.value = _uploadProgress.value + (file.name to ActiveDownload(workerId, file.name, progress, done)) _uploadProgress.value = _uploadProgress.value + (file.name to ActiveDownload(workerId, file.name, progress, done, status))
when (info.state) { when (info.state) {
WorkInfo.State.SUCCEEDED -> { WorkInfo.State.SUCCEEDED -> {
@@ -107,7 +107,7 @@ fun DownloadsScreen(
Spacer(Modifier.width(10.dp)) Spacer(Modifier.width(10.dp))
Column { Column {
Text("↑ $filename", style = MaterialTheme.typography.bodyMedium, fontWeight = FontWeight.Medium, maxLines = 1, overflow = TextOverflow.Ellipsis) Text("↑ $filename", style = MaterialTheme.typography.bodyMedium, fontWeight = FontWeight.Medium, maxLines = 1, overflow = TextOverflow.Ellipsis)
Text("${dl.progress}%", style = MaterialTheme.typography.bodySmall, color = MaterialTheme.colorScheme.onSurfaceVariant) Text(uploadLabel(dl), style = MaterialTheme.typography.bodySmall, color = MaterialTheme.colorScheme.onSurfaceVariant)
} }
} }
} }
@@ -245,6 +245,12 @@ fun DownloadsScreen(
} }
} }
private fun uploadLabel(dl: ActiveDownload): String {
val pct = "${dl.progress}%"
val status = dl.status ?: return pct
return if (dl.progress > 0) "$status · $pct" else status
}
private fun formatSize(bytes: Long): String = when { private fun formatSize(bytes: Long): String = when {
bytes >= 1_000_000_000 -> "%.1f GB".format(bytes / 1_000_000_000.0) bytes >= 1_000_000_000 -> "%.1f GB".format(bytes / 1_000_000_000.0)
bytes >= 1_000_000 -> "%.1f MB".format(bytes / 1_000_000.0) bytes >= 1_000_000 -> "%.1f MB".format(bytes / 1_000_000.0)
@@ -53,11 +53,16 @@ class UploadWorker(
if (!file.exists()) return@withContext Result.failure(workDataOf("error" to "File not found")) if (!file.exists()) return@withContext Result.failure(workDataOf("error" to "File not found"))
try { try {
// Promote to foreground immediately so large files aren't killed during hashing
setForeground(DownloadWorker.createForegroundNotification(applicationContext, "Preparing $filename", 0))
setProgressAsync(workDataOf("pct" to 0, "status" to "Hashing…"))
// Compute SHA256 // Compute SHA256
val sha256 = computeSha256(file) val sha256 = computeSha256(file)
val relativePath = filename // Using filename as relative_path since flat dir val relativePath = filename // Using filename as relative_path since flat dir
// Check status // Check status
setProgressAsync(workDataOf("pct" to 0, "status" to "Checking server…"))
val statusUrl = "$serverUrl/api/v1/dashcam/upload/status?relative_path=$relativePath&sha256=$sha256" val statusUrl = "$serverUrl/api/v1/dashcam/upload/status?relative_path=$relativePath&sha256=$sha256"
val statusReq = Request.Builder().url(statusUrl) val statusReq = Request.Builder().url(statusUrl)
.addHeader("X-Device-Name", deviceName) .addHeader("X-Device-Name", deviceName)
@@ -77,6 +82,7 @@ class UploadWorker(
val totalChunks = ((file.length() + CHUNK_SIZE - 1) / CHUNK_SIZE).toInt() val totalChunks = ((file.length() + CHUNK_SIZE - 1) / CHUNK_SIZE).toInt()
setForeground(DownloadWorker.createForegroundNotification(applicationContext, "Uploading $filename", 0)) setForeground(DownloadWorker.createForegroundNotification(applicationContext, "Uploading $filename", 0))
setProgressAsync(workDataOf("pct" to 0, "status" to "Uploading…"))
// Upload missing chunks // Upload missing chunks
for (i in 0 until totalChunks) { for (i in 0 until totalChunks) {
@@ -114,7 +120,7 @@ class UploadWorker(
val pct = ((i + 1) * 100) / totalChunks val pct = ((i + 1) * 100) / totalChunks
setForeground(DownloadWorker.createForegroundNotification(applicationContext, "Uploading $filename", pct)) setForeground(DownloadWorker.createForegroundNotification(applicationContext, "Uploading $filename", pct))
setProgressAsync(workDataOf("pct" to pct)) setProgressAsync(workDataOf("pct" to pct, "status" to "Uploading…"))
} }
return@withContext Result.success(workDataOf("filename" to filename)) return@withContext Result.success(workDataOf("filename" to filename))