From 77d56ee6be390c07536a7fba341b7e7f19014708 Mon Sep 17 00:00:00 2001 From: Amir Khodak Date: Tue, 26 May 2026 00:56:45 +0000 Subject: [PATCH] v1.0.53: block storage-root sync paths Android 11+ denies writes to /storage/emulated/0 directly. SyncEngine now catches this early and returns FAILED with an actionable message instead of silently logging PARTIAL. LocalBrowserDialog disables the Select button at the storage root with an inline warning. Co-Authored-By: Claude Sonnet 4.6 --- ...tlin-compiler-17831070670597191982.salive} | 0 .../com/syncflow/domain/sync/SyncEngine.kt | 10 +++++ .../syncflow/ui/browser/LocalBrowserDialog.kt | 38 ++++++++++++------- version.properties | 4 +- 4 files changed, 36 insertions(+), 16 deletions(-) rename .kotlin/sessions/{kotlin-compiler-17775491840772701511.salive => kotlin-compiler-17831070670597191982.salive} (100%) diff --git a/.kotlin/sessions/kotlin-compiler-17775491840772701511.salive b/.kotlin/sessions/kotlin-compiler-17831070670597191982.salive similarity index 100% rename from .kotlin/sessions/kotlin-compiler-17775491840772701511.salive rename to .kotlin/sessions/kotlin-compiler-17831070670597191982.salive diff --git a/app/src/main/kotlin/com/syncflow/domain/sync/SyncEngine.kt b/app/src/main/kotlin/com/syncflow/domain/sync/SyncEngine.kt index 45ead7f..853bd1d 100644 --- a/app/src/main/kotlin/com/syncflow/domain/sync/SyncEngine.kt +++ b/app/src/main/kotlin/com/syncflow/domain/sync/SyncEngine.kt @@ -34,6 +34,16 @@ class SyncEngine @Inject constructor( @ApplicationContext private val context: Context, ) { suspend fun sync(pair: SyncPair, provider: CloudProvider): SyncResult { + if (!pair.localPath.startsWith("content://")) { + val canonical = runCatching { File(pair.localPath).canonicalPath }.getOrElse { pair.localPath } + if (canonical == "/storage/emulated/0") { + val msg = "Local folder is the storage root — Android blocks writes here. Edit the pair and select a subfolder." + syncPairDao.updateSyncResult(pair.id, Instant.now(), SyncStatus.FAILED, 0) + logEvent(pair.id, SyncEventType.SYNC_FAILED, null, msg, 0) + return SyncResult(failedFiles = 1, error = Exception(msg)) + } + } + syncPairDao.updateStatus(pair.id, SyncStatus.SYNCING) logEvent(pair.id, SyncEventType.SYNC_STARTED, null, null, 0) diff --git a/app/src/main/kotlin/com/syncflow/ui/browser/LocalBrowserDialog.kt b/app/src/main/kotlin/com/syncflow/ui/browser/LocalBrowserDialog.kt index 25d28b9..7dc21ef 100644 --- a/app/src/main/kotlin/com/syncflow/ui/browser/LocalBrowserDialog.kt +++ b/app/src/main/kotlin/com/syncflow/ui/browser/LocalBrowserDialog.kt @@ -263,22 +263,32 @@ fun LocalBrowserDialog( } // ── Select button ──────────────────────────────────────────── + val isStorageRoot = currentPath.absolutePath == STORAGE_ROOT.absolutePath Surface(tonalElevation = 4.dp, modifier = Modifier.fillMaxWidth()) { Column { - Button( - onClick = { onSelect(currentPath.absolutePath) }, - modifier = Modifier - .fillMaxWidth() - .padding(start = 16.dp, top = 12.dp, end = 16.dp, bottom = 12.dp) - .height(52.dp), - shape = RoundedCornerShape(14.dp), - ) { - Icon(Icons.Default.CheckCircle, null, Modifier.size(20.dp)) - Spacer(Modifier.width(8.dp)) - Text("Select \"$currentFolderName\"", - style = MaterialTheme.typography.titleSmall, fontWeight = FontWeight.SemiBold) - } - Spacer(Modifier.height(bottomInset)) + if (isStorageRoot) { + Text( + "Android blocks writes to the storage root — please open a subfolder first", + style = MaterialTheme.typography.bodySmall, + color = MaterialTheme.colorScheme.error, + modifier = Modifier.padding(horizontal = 20.dp, vertical = 8.dp), + ) + } + Button( + onClick = { onSelect(currentPath.absolutePath) }, + enabled = !isStorageRoot, + modifier = Modifier + .fillMaxWidth() + .padding(start = 16.dp, top = 4.dp, end = 16.dp, bottom = 12.dp) + .height(52.dp), + shape = RoundedCornerShape(14.dp), + ) { + Icon(Icons.Default.CheckCircle, null, Modifier.size(20.dp)) + Spacer(Modifier.width(8.dp)) + Text("Select \"$currentFolderName\"", + style = MaterialTheme.typography.titleSmall, fontWeight = FontWeight.SemiBold) + } + Spacer(Modifier.height(bottomInset)) } } } diff --git a/version.properties b/version.properties index 22acddf..3a6c9f3 100644 --- a/version.properties +++ b/version.properties @@ -1,2 +1,2 @@ -VERSION_NAME=1.0.52 -VERSION_CODE=53 +VERSION_NAME=1.0.53 +VERSION_CODE=54