perf(library): page missing-file cleanup with safe SAF batch checks

This commit is contained in:
zarzet
2026-09-06 02:34:49 +07:00
parent 90d3a5e3de
commit 62ef538690
8 changed files with 456 additions and 54 deletions
@@ -1115,6 +1115,13 @@ class MainActivity: FlutterFragmentActivity() {
}
result.success(exists)
}
"safExistsBatch" -> {
val urisJson = call.argument<String>("uris_json") ?: "[]"
val response = withContext(Dispatchers.IO) {
safExistsBatch(urisJson)
}
result.success(response)
}
"isSafTreeAccessible" -> {
val uriStr = call.argument<String>("tree_uri") ?: ""
val accessible = withContext(Dispatchers.IO) {
@@ -1558,10 +1558,51 @@ internal fun MainActivity.scanSafTreeIncremental(
return spill.result()
}
/**
* Resolve SAF file last-modified values for a list of content URIs.
* Returns JSON object mapping uri -> lastModified (unix millis).
*/
// A failed DocumentsProvider query must never be mistaken for a missing file.
// Unlike DocumentFile.exists(), this keeps null cursors/exceptions inconclusive.
internal fun MainActivity.safExistsBatch(urisJson: String): String {
val result = JSONObject()
val uris = JSONArray(urisJson)
val accessibleTrees = mutableMapOf<String, Boolean>()
val permissions = contentResolver.persistedUriPermissions
val projection = arrayOf(DocumentsContract.Document.COLUMN_DOCUMENT_ID)
for (index in 0 until uris.length()) {
val path = uris.optString(index)
if (path.isBlank()) continue
var status = "unknown"
try {
val uri = Uri.parse(path)
val treeAccessible = if (DocumentsContract.isTreeUri(uri)) {
val treeId = DocumentsContract.getTreeDocumentId(uri)
val tree = DocumentsContract.buildTreeDocumentUri(uri.authority, treeId)
accessibleTrees.getOrPut(tree.toString()) {
val granted = permissions.any { it.uri == tree && it.isReadPermission }
if (!granted) false else {
val root = DocumentsContract.buildDocumentUriUsingTree(tree, treeId)
contentResolver.query(root, projection, null, null, null)?.use {
it.moveToFirst()
} == true
}
}
} else false
contentResolver.query(uri, projection, null, null, null)?.use { cursor ->
status = if (cursor.moveToFirst()) "found"
else if (treeAccessible &&
!cursor.extras.getBoolean(DocumentsContract.EXTRA_LOADING, false))
"missing" else "unknown"
}
} catch (_: Exception) {
// Revoked permission, offline storage and provider errors: retain row.
}
result.put(path, status)
}
return result.toString()
}
/**
* Resolve SAF file last-modified values for a list of content URIs.
* Returns JSON object mapping uri -> lastModified (unix millis).
*/
internal fun MainActivity.getSafFileModTimes(urisJson: String): String {
val result = JSONObject()
val uris = try {