Compare commits

...

1 Commits

Author SHA1 Message Date
amir 77d56ee6be 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 <noreply@anthropic.com>
2026-05-26 00:56:45 +00:00
4 changed files with 36 additions and 16 deletions
@@ -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)
@@ -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))
}
}
}
+2 -2
View File
@@ -1,2 +1,2 @@
VERSION_NAME=1.0.52
VERSION_CODE=53
VERSION_NAME=1.0.53
VERSION_CODE=54