mirror of
https://github.com/zarzet/SpotiFLAC-Mobile.git
synced 2026-10-21 06:52:18 +02:00
refactor(android): move the SAF backend calls into MainActivitySafCalls.kt
MainActivity.kt should be the dispatcher only. The backend `when` keeps every case label in the same order, so the routing set check and the dispatch order are unchanged, and each SAF case now calls a MainActivity.handle<Name> function in MainActivitySafCalls.kt. Bodies move verbatim; the only edit is this@MainActivity becoming the extension function's own receiver label.
This commit is contained in:
1 parent
cf4c05cd93
commit
01c51bf751
2 files changed
+383
-327
No files matched your search
@@ -8,7 +8,6 @@ import android.net.Uri
|
||||
import android.os.Build
|
||||
import android.os.Bundle
|
||||
import android.os.ParcelFileDescriptor
|
||||
import android.provider.DocumentsContract
|
||||
import androidx.activity.OnBackPressedCallback
|
||||
import androidx.activity.result.contract.ActivityResultContracts
|
||||
import androidx.documentfile.provider.DocumentFile
|
||||
@@ -533,291 +532,23 @@ class MainActivity: FlutterFragmentActivity() {
|
||||
setupFolderPickerStore.acknowledge(call.argument<String>("payload") ?: "")
|
||||
result.success(null)
|
||||
}
|
||||
"safExists" -> {
|
||||
val uriStr = call.argument<String>("uri") ?: ""
|
||||
val exists = withContext(Dispatchers.IO) {
|
||||
val uri = Uri.parse(uriStr)
|
||||
DocumentFile.fromSingleUri(this@MainActivity, uri)?.exists() == true
|
||||
}
|
||||
result.success(exists)
|
||||
}
|
||||
"safExistsBatch" -> {
|
||||
val urisJson = call.argument<String>("uris_json") ?: "[]"
|
||||
val response = withContext(Dispatchers.IO) {
|
||||
safExistsBatch(urisJson)
|
||||
}
|
||||
result.success(response)
|
||||
}
|
||||
"probeSafTreeReadAccess" -> {
|
||||
val uriStr = call.argument<String>("tree_uri") ?: ""
|
||||
val readable = withContext(Dispatchers.IO) {
|
||||
probeSafTreeReadAccess(
|
||||
hasReadPermission = {
|
||||
val uri = Uri.parse(uriStr)
|
||||
val root = DocumentsContract.buildDocumentUriUsingTree(
|
||||
uri,
|
||||
DocumentsContract.getTreeDocumentId(uri),
|
||||
)
|
||||
checkUriPermission(
|
||||
root,
|
||||
android.os.Process.myPid(),
|
||||
android.os.Process.myUid(),
|
||||
Intent.FLAG_GRANT_READ_URI_PERMISSION,
|
||||
) == android.content.pm.PackageManager.PERMISSION_GRANTED
|
||||
},
|
||||
readRoot = {
|
||||
val uri = Uri.parse(uriStr)
|
||||
val root = DocumentsContract.buildDocumentUriUsingTree(
|
||||
uri,
|
||||
DocumentsContract.getTreeDocumentId(uri),
|
||||
)
|
||||
val rootPresent = contentResolver.query(
|
||||
root,
|
||||
arrayOf(DocumentsContract.Document.COLUMN_DOCUMENT_ID),
|
||||
null, null, null,
|
||||
)?.use { cursor ->
|
||||
when {
|
||||
cursor.moveToFirst() -> true
|
||||
cursor.extras.getBoolean(DocumentsContract.EXTRA_LOADING, false) -> null
|
||||
else -> false
|
||||
}
|
||||
}
|
||||
if (rootPresent == true) {
|
||||
true
|
||||
} else {
|
||||
// A reconnecting remote provider may not have
|
||||
// queryDocument metadata yet. A completed child
|
||||
// listing is stronger evidence of read access.
|
||||
val directory = DocumentFile.fromTreeUri(this@MainActivity, uri)
|
||||
if (directory != null && listSafChildrenOrThrow(directory).isNotEmpty()) {
|
||||
true
|
||||
} else {
|
||||
rootPresent
|
||||
}
|
||||
}
|
||||
},
|
||||
onFailure = { error ->
|
||||
android.util.Log.w("SpotiFLAC", "SAF library read probe failed", error)
|
||||
},
|
||||
)
|
||||
}
|
||||
result.success(readable)
|
||||
}
|
||||
"isSafTreeAccessible" -> {
|
||||
val uriStr = call.argument<String>("tree_uri") ?: ""
|
||||
val accessible = withContext(Dispatchers.IO) {
|
||||
try {
|
||||
val uri = Uri.parse(uriStr)
|
||||
val persisted = contentResolver.persistedUriPermissions.any {
|
||||
it.uri == uri && it.isReadPermission && it.isWritePermission
|
||||
}
|
||||
if (!persisted) {
|
||||
false
|
||||
} else {
|
||||
val doc = DocumentFile.fromTreeUri(this@MainActivity, uri)
|
||||
doc != null && doc.exists() && doc.canWrite()
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
android.util.Log.w("SpotiFLAC", "SAF tree access check failed: ${e.message}")
|
||||
false
|
||||
}
|
||||
}
|
||||
result.success(accessible)
|
||||
}
|
||||
"safDelete" -> {
|
||||
val uriStr = call.argument<String>("uri") ?: ""
|
||||
val deleted = withContext(Dispatchers.IO) {
|
||||
val uri = Uri.parse(uriStr)
|
||||
DocumentFile.fromSingleUri(this@MainActivity, uri)?.delete() == true
|
||||
}
|
||||
result.success(deleted)
|
||||
}
|
||||
"safStat" -> {
|
||||
val uriStr = call.argument<String>("uri") ?: ""
|
||||
val response = withContext(Dispatchers.IO) {
|
||||
querySafDocumentStat(contentResolver, Uri.parse(uriStr)).toString()
|
||||
}
|
||||
result.success(response)
|
||||
}
|
||||
"resolveSafFile" -> {
|
||||
val treeUriStr = call.argument<String>("tree_uri") ?: ""
|
||||
val relativeDir = call.argument<String>("relative_dir") ?: ""
|
||||
val fileName = call.argument<String>("file_name") ?: ""
|
||||
val response = withContext(Dispatchers.IO) {
|
||||
resolveSafFile(treeUriStr, relativeDir, fileName)
|
||||
}
|
||||
result.success(response)
|
||||
}
|
||||
"inspectSafFiles" -> {
|
||||
val requestsJson = call.argument<String>("requests_json") ?: "[]"
|
||||
val response = withContext(Dispatchers.IO) {
|
||||
inspectSafFiles(requestsJson)
|
||||
}
|
||||
result.success(response)
|
||||
}
|
||||
"safCopyToTemp" -> {
|
||||
val uriStr = call.argument<String>("uri") ?: ""
|
||||
val tempPath = withContext(Dispatchers.IO) {
|
||||
copyUriToTemp(Uri.parse(uriStr))
|
||||
}
|
||||
result.success(tempPath)
|
||||
}
|
||||
"safOpenPlaybackLease" -> {
|
||||
val uriStr = call.argument<String>("uri") ?: ""
|
||||
val lease = withContext(Dispatchers.IO) {
|
||||
openSafPlaybackLease(uriStr)
|
||||
}
|
||||
result.success(lease)
|
||||
}
|
||||
"safClosePlaybackLease" -> {
|
||||
val token = call.argument<String>("token") ?: ""
|
||||
closeSafPlaybackLease(token)
|
||||
result.success(null)
|
||||
}
|
||||
"safCreateFromPath" -> {
|
||||
val treeUriStr = call.argument<String>("tree_uri") ?: ""
|
||||
val relativeDir = call.argument<String>("relative_dir") ?: ""
|
||||
val fileName = SafDownloadHandler.sanitizeFilename(call.argument<String>("file_name") ?: "")
|
||||
val mimeType = call.argument<String>("mime_type") ?: "application/octet-stream"
|
||||
val srcPath = call.argument<String>("src_path") ?: ""
|
||||
val createdUri = withContext(Dispatchers.IO) {
|
||||
if (treeUriStr.isBlank()) return@withContext null
|
||||
if (fileName.isBlank()) return@withContext null
|
||||
val dir = SafDownloadHandler.ensureDocumentDir(this@MainActivity, Uri.parse(treeUriStr), relativeDir) ?: return@withContext null
|
||||
val existing = findSafChild(this@MainActivity, dir, fileName)
|
||||
val createdNew = existing == null
|
||||
val doc = SafDownloadHandler.createOrReuseDocumentFile(this@MainActivity, dir, mimeType, fileName)
|
||||
?: return@withContext null
|
||||
if (!writeUriFromPath(doc.uri, srcPath)) {
|
||||
if (createdNew) {
|
||||
doc.delete()
|
||||
}
|
||||
return@withContext null
|
||||
}
|
||||
doc.uri.toString()
|
||||
}
|
||||
result.success(createdUri)
|
||||
}
|
||||
"safCreateIfAbsentFromPath" -> {
|
||||
val treeUriStr = call.argument<String>("tree_uri") ?: ""
|
||||
val relativeDir = call.argument<String>("relative_dir") ?: ""
|
||||
val fileName = call.argument<String>("file_name") ?: ""
|
||||
val mimeType = call.argument<String>("mime_type") ?: "application/octet-stream"
|
||||
val srcPath = call.argument<String>("src_path") ?: ""
|
||||
val response = withContext(Dispatchers.IO) {
|
||||
if (treeUriStr.isBlank() || fileName.isBlank()) return@withContext null
|
||||
SafDownloadHandler.writeFileToSafIfAbsent(
|
||||
context = this@MainActivity,
|
||||
treeUriStr = treeUriStr,
|
||||
relativeDir = relativeDir,
|
||||
fileName = fileName,
|
||||
mimeType = mimeType,
|
||||
srcPath = srcPath,
|
||||
)?.let { writeResult ->
|
||||
JSONObject()
|
||||
.put("uri", writeResult.uri)
|
||||
.put("file_name", writeResult.fileName)
|
||||
.put("already_exists", writeResult.alreadyExists)
|
||||
.put("publish_timings_ms", JSONObject(writeResult.publishTimingsMs))
|
||||
.toString()
|
||||
}
|
||||
}
|
||||
result.success(response)
|
||||
}
|
||||
"safCreateUniqueFromPath" -> {
|
||||
val treeUriStr = call.argument<String>("tree_uri") ?: ""
|
||||
val relativeDir = call.argument<String>("relative_dir") ?: ""
|
||||
val fileName = call.argument<String>("file_name") ?: ""
|
||||
val mimeType = call.argument<String>("mime_type") ?: "application/octet-stream"
|
||||
val srcPath = call.argument<String>("src_path") ?: ""
|
||||
val preservedSuffix = call.argument<String>("preserved_suffix") ?: ""
|
||||
val response = withContext(Dispatchers.IO) {
|
||||
if (treeUriStr.isBlank() || fileName.isBlank()) return@withContext null
|
||||
SafDownloadHandler.writeFileToSafUnique(
|
||||
context = this@MainActivity,
|
||||
treeUriStr = treeUriStr,
|
||||
relativeDir = relativeDir,
|
||||
fileName = fileName,
|
||||
mimeType = mimeType,
|
||||
srcPath = srcPath,
|
||||
preservedSuffix = preservedSuffix,
|
||||
)?.let { writeResult ->
|
||||
JSONObject()
|
||||
.put("uri", writeResult.uri)
|
||||
.put("file_name", writeResult.fileName)
|
||||
.put("publish_timings_ms", JSONObject(writeResult.publishTimingsMs))
|
||||
.toString()
|
||||
}
|
||||
}
|
||||
result.success(response)
|
||||
}
|
||||
"safCreateCollisionAwareFromPath" -> {
|
||||
val treeUriStr = call.argument<String>("tree_uri") ?: ""
|
||||
val relativeDir = call.argument<String>("relative_dir") ?: ""
|
||||
val cleanFileName = call.argument<String>("clean_file_name") ?: ""
|
||||
val variantFileName = call.argument<String>("variant_file_name") ?: ""
|
||||
val mimeType = call.argument<String>("mime_type") ?: "application/octet-stream"
|
||||
val srcPath = call.argument<String>("src_path") ?: ""
|
||||
val preservedSuffix = call.argument<String>("preserved_suffix") ?: ""
|
||||
val response = withContext(Dispatchers.IO) {
|
||||
if (
|
||||
treeUriStr.isBlank() ||
|
||||
cleanFileName.isBlank() ||
|
||||
variantFileName.isBlank()
|
||||
) return@withContext null
|
||||
SafDownloadHandler.writeFileToSafCollisionAware(
|
||||
context = this@MainActivity,
|
||||
treeUriStr = treeUriStr,
|
||||
relativeDir = relativeDir,
|
||||
cleanFileName = cleanFileName,
|
||||
variantFileName = variantFileName,
|
||||
mimeType = mimeType,
|
||||
srcPath = srcPath,
|
||||
preservedSuffix = preservedSuffix,
|
||||
)?.let { writeResult ->
|
||||
JSONObject()
|
||||
.put("uri", writeResult.uri)
|
||||
.put("file_name", writeResult.fileName)
|
||||
.put("publish_timings_ms", JSONObject(writeResult.publishTimingsMs))
|
||||
.toString()
|
||||
}
|
||||
}
|
||||
result.success(response)
|
||||
}
|
||||
"openContentUri" -> {
|
||||
val uriStr = call.argument<String>("uri") ?: ""
|
||||
val mimeType = call.argument<String>("mime_type") ?: ""
|
||||
try {
|
||||
val uri = Uri.parse(uriStr)
|
||||
val type = if (mimeType.isNotBlank()) mimeType else contentResolver.getType(uri) ?: "*/*"
|
||||
val intent = Intent(Intent.ACTION_VIEW).setDataAndType(uri, type)
|
||||
.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
|
||||
startActivity(intent)
|
||||
result.success(null)
|
||||
} catch (e: Exception) {
|
||||
result.error("open_failed", e.message, null)
|
||||
}
|
||||
}
|
||||
"shareContentUri" -> {
|
||||
val uriStr = call.argument<String>("uri") ?: ""
|
||||
val title = call.argument<String>("title") ?: ""
|
||||
try {
|
||||
val uri = Uri.parse(uriStr)
|
||||
val type = contentResolver.getType(uri) ?: "audio/*"
|
||||
val shareIntent = Intent(Intent.ACTION_SEND).apply {
|
||||
putExtra(Intent.EXTRA_STREAM, uri)
|
||||
setType(type)
|
||||
addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
|
||||
if (title.isNotBlank()) {
|
||||
putExtra(Intent.EXTRA_SUBJECT, title)
|
||||
}
|
||||
}
|
||||
startActivity(Intent.createChooser(shareIntent, title.ifBlank { "Share" }))
|
||||
result.success(true)
|
||||
} catch (e: Exception) {
|
||||
result.error("share_failed", e.message, null)
|
||||
}
|
||||
}
|
||||
"safExists" -> handleSafExists(call, result)
|
||||
"safExistsBatch" -> handleSafExistsBatch(call, result)
|
||||
"probeSafTreeReadAccess" -> handleProbeSafTreeReadAccess(call, result)
|
||||
"isSafTreeAccessible" -> handleIsSafTreeAccessible(call, result)
|
||||
"safDelete" -> handleSafDelete(call, result)
|
||||
"safStat" -> handleSafStat(call, result)
|
||||
"resolveSafFile" -> handleResolveSafFile(call, result)
|
||||
"inspectSafFiles" -> handleInspectSafFiles(call, result)
|
||||
"safCopyToTemp" -> handleSafCopyToTemp(call, result)
|
||||
"safOpenPlaybackLease" -> handleSafOpenPlaybackLease(call, result)
|
||||
"safClosePlaybackLease" -> handleSafClosePlaybackLease(call, result)
|
||||
"safCreateFromPath" -> handleSafCreateFromPath(call, result)
|
||||
"safCreateIfAbsentFromPath" -> handleSafCreateIfAbsentFromPath(call, result)
|
||||
"safCreateUniqueFromPath" -> handleSafCreateUniqueFromPath(call, result)
|
||||
"safCreateCollisionAwareFromPath" -> handleSafCreateCollisionAwareFromPath(call, result)
|
||||
"openContentUri" -> handleOpenContentUri(call, result)
|
||||
"shareContentUri" -> handleShareContentUri(call, result)
|
||||
"rewriteSplitArtistTags" -> {
|
||||
val filePath = call.argument<String>("file_path") ?: ""
|
||||
val artist = call.argument<String>("artist") ?: ""
|
||||
@@ -969,40 +700,8 @@ class MainActivity: FlutterFragmentActivity() {
|
||||
}
|
||||
result.success(response)
|
||||
}
|
||||
"writeTempToSaf" -> {
|
||||
val tempPath = call.argument<String>("temp_path") ?: ""
|
||||
val safUri = call.argument<String>("saf_uri") ?: ""
|
||||
val response = withContext(Dispatchers.IO) {
|
||||
try {
|
||||
val uri = Uri.parse(safUri)
|
||||
if (writeUriFromPath(uri, tempPath)) {
|
||||
"""{"success":true}"""
|
||||
} else {
|
||||
"""{"success":false,"error":"Failed to write back to SAF"}"""
|
||||
}
|
||||
} finally {
|
||||
try { File(tempPath).delete() } catch (_: Exception) {}
|
||||
}
|
||||
}
|
||||
result.success(response)
|
||||
}
|
||||
"writeSafSidecarLrc" -> {
|
||||
val safUri = call.argument<String>("saf_uri") ?: ""
|
||||
val lyrics = call.argument<String>("lyrics") ?: ""
|
||||
val response = withContext(Dispatchers.IO) {
|
||||
try {
|
||||
val uri = Uri.parse(safUri)
|
||||
if (writeSafSidecarLrc(uri, lyrics)) {
|
||||
"""{"success":true}"""
|
||||
} else {
|
||||
"""{"success":false,"error":"Failed to write LRC sidecar"}"""
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
"""{"success":false,"error":"${e.message?.replace("\"", "'")}"}"""
|
||||
}
|
||||
}
|
||||
result.success(response)
|
||||
}
|
||||
"writeTempToSaf" -> handleWriteTempToSaf(call, result)
|
||||
"writeSafSidecarLrc" -> handleWriteSafSidecarLrc(call, result)
|
||||
"downloadCoverToFile" -> {
|
||||
val coverUrl = call.argument<String>("cover_url") ?: ""
|
||||
val outputPath = call.argument<String>("output_path") ?: ""
|
||||
@@ -1328,13 +1027,7 @@ class MainActivity: FlutterFragmentActivity() {
|
||||
}
|
||||
result.success(response)
|
||||
}
|
||||
"getSafFileModTimes" -> {
|
||||
val uris = call.argument<String>("uris") ?: "[]"
|
||||
val response = withContext(Dispatchers.IO) {
|
||||
getSafFileModTimes(uris)
|
||||
}
|
||||
result.success(response)
|
||||
}
|
||||
"getSafFileModTimes" -> handleGetSafFileModTimes(call, result)
|
||||
"getLibraryScanProgress" -> {
|
||||
val response = withContext(Dispatchers.IO) {
|
||||
if (safScanActive) {
|
||||
|
||||
@@ -0,0 +1,363 @@
|
||||
package com.zarz.spotiflac
|
||||
|
||||
import android.content.Intent
|
||||
import android.net.Uri
|
||||
import android.provider.DocumentsContract
|
||||
import androidx.documentfile.provider.DocumentFile
|
||||
import io.flutter.plugin.common.MethodCall
|
||||
import io.flutter.plugin.common.MethodChannel
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.withContext
|
||||
import org.json.JSONObject
|
||||
import java.io.File
|
||||
|
||||
// SAF backend calls: document existence, access probes, stat and lookup,
|
||||
// temp copies, playback leases, document creation from a local file,
|
||||
// content URI open/share, and writes back into SAF. MainActivity's backend
|
||||
// `when` dispatches each case here, in the order it lists them.
|
||||
|
||||
internal suspend fun MainActivity.handleSafExists(call: MethodCall, result: MethodChannel.Result) {
|
||||
val uriStr = call.argument<String>("uri") ?: ""
|
||||
val exists = withContext(Dispatchers.IO) {
|
||||
val uri = Uri.parse(uriStr)
|
||||
DocumentFile.fromSingleUri(this@handleSafExists, uri)?.exists() == true
|
||||
}
|
||||
result.success(exists)
|
||||
}
|
||||
|
||||
internal suspend fun MainActivity.handleSafExistsBatch(call: MethodCall, result: MethodChannel.Result) {
|
||||
val urisJson = call.argument<String>("uris_json") ?: "[]"
|
||||
val response = withContext(Dispatchers.IO) {
|
||||
safExistsBatch(urisJson)
|
||||
}
|
||||
result.success(response)
|
||||
}
|
||||
|
||||
internal suspend fun MainActivity.handleProbeSafTreeReadAccess(call: MethodCall, result: MethodChannel.Result) {
|
||||
val uriStr = call.argument<String>("tree_uri") ?: ""
|
||||
val readable = withContext(Dispatchers.IO) {
|
||||
probeSafTreeReadAccess(
|
||||
hasReadPermission = {
|
||||
val uri = Uri.parse(uriStr)
|
||||
val root = DocumentsContract.buildDocumentUriUsingTree(
|
||||
uri,
|
||||
DocumentsContract.getTreeDocumentId(uri),
|
||||
)
|
||||
checkUriPermission(
|
||||
root,
|
||||
android.os.Process.myPid(),
|
||||
android.os.Process.myUid(),
|
||||
Intent.FLAG_GRANT_READ_URI_PERMISSION,
|
||||
) == android.content.pm.PackageManager.PERMISSION_GRANTED
|
||||
},
|
||||
readRoot = {
|
||||
val uri = Uri.parse(uriStr)
|
||||
val root = DocumentsContract.buildDocumentUriUsingTree(
|
||||
uri,
|
||||
DocumentsContract.getTreeDocumentId(uri),
|
||||
)
|
||||
val rootPresent = contentResolver.query(
|
||||
root,
|
||||
arrayOf(DocumentsContract.Document.COLUMN_DOCUMENT_ID),
|
||||
null, null, null,
|
||||
)?.use { cursor ->
|
||||
when {
|
||||
cursor.moveToFirst() -> true
|
||||
cursor.extras.getBoolean(DocumentsContract.EXTRA_LOADING, false) -> null
|
||||
else -> false
|
||||
}
|
||||
}
|
||||
if (rootPresent == true) {
|
||||
true
|
||||
} else {
|
||||
// A reconnecting remote provider may not have
|
||||
// queryDocument metadata yet. A completed child
|
||||
// listing is stronger evidence of read access.
|
||||
val directory = DocumentFile.fromTreeUri(this@handleProbeSafTreeReadAccess, uri)
|
||||
if (directory != null && listSafChildrenOrThrow(directory).isNotEmpty()) {
|
||||
true
|
||||
} else {
|
||||
rootPresent
|
||||
}
|
||||
}
|
||||
},
|
||||
onFailure = { error ->
|
||||
android.util.Log.w("SpotiFLAC", "SAF library read probe failed", error)
|
||||
},
|
||||
)
|
||||
}
|
||||
result.success(readable)
|
||||
}
|
||||
|
||||
internal suspend fun MainActivity.handleIsSafTreeAccessible(call: MethodCall, result: MethodChannel.Result) {
|
||||
val uriStr = call.argument<String>("tree_uri") ?: ""
|
||||
val accessible = withContext(Dispatchers.IO) {
|
||||
try {
|
||||
val uri = Uri.parse(uriStr)
|
||||
val persisted = contentResolver.persistedUriPermissions.any {
|
||||
it.uri == uri && it.isReadPermission && it.isWritePermission
|
||||
}
|
||||
if (!persisted) {
|
||||
false
|
||||
} else {
|
||||
val doc = DocumentFile.fromTreeUri(this@handleIsSafTreeAccessible, uri)
|
||||
doc != null && doc.exists() && doc.canWrite()
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
android.util.Log.w("SpotiFLAC", "SAF tree access check failed: ${e.message}")
|
||||
false
|
||||
}
|
||||
}
|
||||
result.success(accessible)
|
||||
}
|
||||
|
||||
internal suspend fun MainActivity.handleSafDelete(call: MethodCall, result: MethodChannel.Result) {
|
||||
val uriStr = call.argument<String>("uri") ?: ""
|
||||
val deleted = withContext(Dispatchers.IO) {
|
||||
val uri = Uri.parse(uriStr)
|
||||
DocumentFile.fromSingleUri(this@handleSafDelete, uri)?.delete() == true
|
||||
}
|
||||
result.success(deleted)
|
||||
}
|
||||
|
||||
internal suspend fun MainActivity.handleSafStat(call: MethodCall, result: MethodChannel.Result) {
|
||||
val uriStr = call.argument<String>("uri") ?: ""
|
||||
val response = withContext(Dispatchers.IO) {
|
||||
querySafDocumentStat(contentResolver, Uri.parse(uriStr)).toString()
|
||||
}
|
||||
result.success(response)
|
||||
}
|
||||
|
||||
internal suspend fun MainActivity.handleResolveSafFile(call: MethodCall, result: MethodChannel.Result) {
|
||||
val treeUriStr = call.argument<String>("tree_uri") ?: ""
|
||||
val relativeDir = call.argument<String>("relative_dir") ?: ""
|
||||
val fileName = call.argument<String>("file_name") ?: ""
|
||||
val response = withContext(Dispatchers.IO) {
|
||||
resolveSafFile(treeUriStr, relativeDir, fileName)
|
||||
}
|
||||
result.success(response)
|
||||
}
|
||||
|
||||
internal suspend fun MainActivity.handleInspectSafFiles(call: MethodCall, result: MethodChannel.Result) {
|
||||
val requestsJson = call.argument<String>("requests_json") ?: "[]"
|
||||
val response = withContext(Dispatchers.IO) {
|
||||
inspectSafFiles(requestsJson)
|
||||
}
|
||||
result.success(response)
|
||||
}
|
||||
|
||||
internal suspend fun MainActivity.handleSafCopyToTemp(call: MethodCall, result: MethodChannel.Result) {
|
||||
val uriStr = call.argument<String>("uri") ?: ""
|
||||
val tempPath = withContext(Dispatchers.IO) {
|
||||
copyUriToTemp(Uri.parse(uriStr))
|
||||
}
|
||||
result.success(tempPath)
|
||||
}
|
||||
|
||||
internal suspend fun MainActivity.handleSafOpenPlaybackLease(call: MethodCall, result: MethodChannel.Result) {
|
||||
val uriStr = call.argument<String>("uri") ?: ""
|
||||
val lease = withContext(Dispatchers.IO) {
|
||||
openSafPlaybackLease(uriStr)
|
||||
}
|
||||
result.success(lease)
|
||||
}
|
||||
|
||||
internal fun MainActivity.handleSafClosePlaybackLease(call: MethodCall, result: MethodChannel.Result) {
|
||||
val token = call.argument<String>("token") ?: ""
|
||||
closeSafPlaybackLease(token)
|
||||
result.success(null)
|
||||
}
|
||||
|
||||
internal suspend fun MainActivity.handleSafCreateFromPath(call: MethodCall, result: MethodChannel.Result) {
|
||||
val treeUriStr = call.argument<String>("tree_uri") ?: ""
|
||||
val relativeDir = call.argument<String>("relative_dir") ?: ""
|
||||
val fileName = SafDownloadHandler.sanitizeFilename(call.argument<String>("file_name") ?: "")
|
||||
val mimeType = call.argument<String>("mime_type") ?: "application/octet-stream"
|
||||
val srcPath = call.argument<String>("src_path") ?: ""
|
||||
val createdUri = withContext(Dispatchers.IO) {
|
||||
if (treeUriStr.isBlank()) return@withContext null
|
||||
if (fileName.isBlank()) return@withContext null
|
||||
val dir = SafDownloadHandler.ensureDocumentDir(this@handleSafCreateFromPath, Uri.parse(treeUriStr), relativeDir) ?: return@withContext null
|
||||
val existing = findSafChild(this@handleSafCreateFromPath, dir, fileName)
|
||||
val createdNew = existing == null
|
||||
val doc = SafDownloadHandler.createOrReuseDocumentFile(this@handleSafCreateFromPath, dir, mimeType, fileName)
|
||||
?: return@withContext null
|
||||
if (!writeUriFromPath(doc.uri, srcPath)) {
|
||||
if (createdNew) {
|
||||
doc.delete()
|
||||
}
|
||||
return@withContext null
|
||||
}
|
||||
doc.uri.toString()
|
||||
}
|
||||
result.success(createdUri)
|
||||
}
|
||||
|
||||
internal suspend fun MainActivity.handleSafCreateIfAbsentFromPath(call: MethodCall, result: MethodChannel.Result) {
|
||||
val treeUriStr = call.argument<String>("tree_uri") ?: ""
|
||||
val relativeDir = call.argument<String>("relative_dir") ?: ""
|
||||
val fileName = call.argument<String>("file_name") ?: ""
|
||||
val mimeType = call.argument<String>("mime_type") ?: "application/octet-stream"
|
||||
val srcPath = call.argument<String>("src_path") ?: ""
|
||||
val response = withContext(Dispatchers.IO) {
|
||||
if (treeUriStr.isBlank() || fileName.isBlank()) return@withContext null
|
||||
SafDownloadHandler.writeFileToSafIfAbsent(
|
||||
context = this@handleSafCreateIfAbsentFromPath,
|
||||
treeUriStr = treeUriStr,
|
||||
relativeDir = relativeDir,
|
||||
fileName = fileName,
|
||||
mimeType = mimeType,
|
||||
srcPath = srcPath,
|
||||
)?.let { writeResult ->
|
||||
JSONObject()
|
||||
.put("uri", writeResult.uri)
|
||||
.put("file_name", writeResult.fileName)
|
||||
.put("already_exists", writeResult.alreadyExists)
|
||||
.put("publish_timings_ms", JSONObject(writeResult.publishTimingsMs))
|
||||
.toString()
|
||||
}
|
||||
}
|
||||
result.success(response)
|
||||
}
|
||||
|
||||
internal suspend fun MainActivity.handleSafCreateUniqueFromPath(call: MethodCall, result: MethodChannel.Result) {
|
||||
val treeUriStr = call.argument<String>("tree_uri") ?: ""
|
||||
val relativeDir = call.argument<String>("relative_dir") ?: ""
|
||||
val fileName = call.argument<String>("file_name") ?: ""
|
||||
val mimeType = call.argument<String>("mime_type") ?: "application/octet-stream"
|
||||
val srcPath = call.argument<String>("src_path") ?: ""
|
||||
val preservedSuffix = call.argument<String>("preserved_suffix") ?: ""
|
||||
val response = withContext(Dispatchers.IO) {
|
||||
if (treeUriStr.isBlank() || fileName.isBlank()) return@withContext null
|
||||
SafDownloadHandler.writeFileToSafUnique(
|
||||
context = this@handleSafCreateUniqueFromPath,
|
||||
treeUriStr = treeUriStr,
|
||||
relativeDir = relativeDir,
|
||||
fileName = fileName,
|
||||
mimeType = mimeType,
|
||||
srcPath = srcPath,
|
||||
preservedSuffix = preservedSuffix,
|
||||
)?.let { writeResult ->
|
||||
JSONObject()
|
||||
.put("uri", writeResult.uri)
|
||||
.put("file_name", writeResult.fileName)
|
||||
.put("publish_timings_ms", JSONObject(writeResult.publishTimingsMs))
|
||||
.toString()
|
||||
}
|
||||
}
|
||||
result.success(response)
|
||||
}
|
||||
|
||||
internal suspend fun MainActivity.handleSafCreateCollisionAwareFromPath(call: MethodCall, result: MethodChannel.Result) {
|
||||
val treeUriStr = call.argument<String>("tree_uri") ?: ""
|
||||
val relativeDir = call.argument<String>("relative_dir") ?: ""
|
||||
val cleanFileName = call.argument<String>("clean_file_name") ?: ""
|
||||
val variantFileName = call.argument<String>("variant_file_name") ?: ""
|
||||
val mimeType = call.argument<String>("mime_type") ?: "application/octet-stream"
|
||||
val srcPath = call.argument<String>("src_path") ?: ""
|
||||
val preservedSuffix = call.argument<String>("preserved_suffix") ?: ""
|
||||
val response = withContext(Dispatchers.IO) {
|
||||
if (
|
||||
treeUriStr.isBlank() ||
|
||||
cleanFileName.isBlank() ||
|
||||
variantFileName.isBlank()
|
||||
) return@withContext null
|
||||
SafDownloadHandler.writeFileToSafCollisionAware(
|
||||
context = this@handleSafCreateCollisionAwareFromPath,
|
||||
treeUriStr = treeUriStr,
|
||||
relativeDir = relativeDir,
|
||||
cleanFileName = cleanFileName,
|
||||
variantFileName = variantFileName,
|
||||
mimeType = mimeType,
|
||||
srcPath = srcPath,
|
||||
preservedSuffix = preservedSuffix,
|
||||
)?.let { writeResult ->
|
||||
JSONObject()
|
||||
.put("uri", writeResult.uri)
|
||||
.put("file_name", writeResult.fileName)
|
||||
.put("publish_timings_ms", JSONObject(writeResult.publishTimingsMs))
|
||||
.toString()
|
||||
}
|
||||
}
|
||||
result.success(response)
|
||||
}
|
||||
|
||||
internal fun MainActivity.handleOpenContentUri(call: MethodCall, result: MethodChannel.Result) {
|
||||
val uriStr = call.argument<String>("uri") ?: ""
|
||||
val mimeType = call.argument<String>("mime_type") ?: ""
|
||||
try {
|
||||
val uri = Uri.parse(uriStr)
|
||||
val type = if (mimeType.isNotBlank()) mimeType else contentResolver.getType(uri) ?: "*/*"
|
||||
val intent = Intent(Intent.ACTION_VIEW).setDataAndType(uri, type)
|
||||
.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
|
||||
startActivity(intent)
|
||||
result.success(null)
|
||||
} catch (e: Exception) {
|
||||
result.error("open_failed", e.message, null)
|
||||
}
|
||||
}
|
||||
|
||||
internal fun MainActivity.handleShareContentUri(call: MethodCall, result: MethodChannel.Result) {
|
||||
val uriStr = call.argument<String>("uri") ?: ""
|
||||
val title = call.argument<String>("title") ?: ""
|
||||
try {
|
||||
val uri = Uri.parse(uriStr)
|
||||
val type = contentResolver.getType(uri) ?: "audio/*"
|
||||
val shareIntent = Intent(Intent.ACTION_SEND).apply {
|
||||
putExtra(Intent.EXTRA_STREAM, uri)
|
||||
setType(type)
|
||||
addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
|
||||
if (title.isNotBlank()) {
|
||||
putExtra(Intent.EXTRA_SUBJECT, title)
|
||||
}
|
||||
}
|
||||
startActivity(Intent.createChooser(shareIntent, title.ifBlank { "Share" }))
|
||||
result.success(true)
|
||||
} catch (e: Exception) {
|
||||
result.error("share_failed", e.message, null)
|
||||
}
|
||||
}
|
||||
|
||||
internal suspend fun MainActivity.handleWriteTempToSaf(call: MethodCall, result: MethodChannel.Result) {
|
||||
val tempPath = call.argument<String>("temp_path") ?: ""
|
||||
val safUri = call.argument<String>("saf_uri") ?: ""
|
||||
val response = withContext(Dispatchers.IO) {
|
||||
try {
|
||||
val uri = Uri.parse(safUri)
|
||||
if (writeUriFromPath(uri, tempPath)) {
|
||||
"""{"success":true}"""
|
||||
} else {
|
||||
"""{"success":false,"error":"Failed to write back to SAF"}"""
|
||||
}
|
||||
} finally {
|
||||
try { File(tempPath).delete() } catch (_: Exception) {}
|
||||
}
|
||||
}
|
||||
result.success(response)
|
||||
}
|
||||
|
||||
internal suspend fun MainActivity.handleWriteSafSidecarLrc(call: MethodCall, result: MethodChannel.Result) {
|
||||
val safUri = call.argument<String>("saf_uri") ?: ""
|
||||
val lyrics = call.argument<String>("lyrics") ?: ""
|
||||
val response = withContext(Dispatchers.IO) {
|
||||
try {
|
||||
val uri = Uri.parse(safUri)
|
||||
if (writeSafSidecarLrc(uri, lyrics)) {
|
||||
"""{"success":true}"""
|
||||
} else {
|
||||
"""{"success":false,"error":"Failed to write LRC sidecar"}"""
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
"""{"success":false,"error":"${e.message?.replace("\"", "'")}"}"""
|
||||
}
|
||||
}
|
||||
result.success(response)
|
||||
}
|
||||
|
||||
internal suspend fun MainActivity.handleGetSafFileModTimes(call: MethodCall, result: MethodChannel.Result) {
|
||||
val uris = call.argument<String>("uris") ?: "[]"
|
||||
val response = withContext(Dispatchers.IO) {
|
||||
getSafFileModTimes(uris)
|
||||
}
|
||||
result.success(response)
|
||||
}
|
||||
Reference in new issue
Block a user