refactor(android): remove backend switches that became constants

Rust is the only backend, so several single-backend switches had become
constants and their other branches could never run:

- CoreBackend.supportsOutputDescriptors was always false. The
  descriptor branch in SafDownloadHandler and its pfd/detachFd cleanup
  never ran, and the Rust planner rejects output_fd anyway.
- routesApplication was always true on Android and iOS. Drop the member,
  the "go" value of getBackendImplementations (the reply keys and the
  "rust" value Dart receives are unchanged), the iOS routing guard and
  the interface defaults that threw for unrouted methods.
- cleanupExtensions(), waitForDownloadProgressDelta and
  scanCueForLibrary had no production caller. Dart reaches cleanup
  through the cleanupExtensions channel method, and RustCoreBackend
  overrides openDownloadProgress, so the polling default goes too.

The UniFFI exports DownloadState.wait_progress_delta and
ExtensionManager.scan_cue_file_for_library were used only by those
Kotlin overrides. Bindings are generated at build time by
scripts/build_rust_backend.sh; no generated binding is tracked.
This commit is contained in:
zarzet committed 2026-10-18 23:01:04 +07:00
1 parent b64b08c4d0
commit ca950dc35a
8 files changed
+16 -107

No files matched your search

@@ -163,13 +163,7 @@ internal fun withCoreFFmpegExecution(
/** Native migration boundary; each process selects one stateful backend. */
internal interface CoreBackend {
val implementation: String
val routesApplication: Boolean get() = false
val supportsOutputDescriptors: Boolean get() = false
fun invokeApplication(method: String, arguments: Any?): Any? =
error("Application routing is unavailable for $method")
fun cleanupExtensions() {
invokeApplication("cleanupExtensions", null)
}
fun invokeApplication(method: String, arguments: Any?): Any?
fun setRuntimeState(dataDirectory: String, payload: String) {
invokeApplication(
"prepareRuntimeState",
@@ -179,11 +173,7 @@ internal interface CoreBackend {
fun completeAuthCallback(state: String, code: String, sessionGrant: Boolean, onResolved: (String) -> Unit)
fun downloadByStrategy(requestJson: String): String =
withCoreFFmpegExecution(openExtensionExecution()) { it.download(requestJson) }
fun waitForDownloadProgressDelta(since: Long, timeoutMs: Long): String
fun openDownloadProgress(): CoreDownloadProgress = object : CoreDownloadProgress {
override fun waitDelta(since: Long, timeoutMs: Long) = waitForDownloadProgressDelta(since, timeoutMs)
override fun close() {}
}
fun openDownloadProgress(): CoreDownloadProgress
fun initItemProgress(itemId: String)
fun clearItemProgress(itemId: String)
fun cancelDownload(itemId: String)
@@ -207,7 +197,6 @@ internal interface CoreBackend {
fun resumeLibraryScan()
fun parseCueSheet(path: String, audioDirectory: String): String
fun parseCueSheetWithResolvedAudio(path: String, audioPath: String): String
fun scanCueForLibrary(path: String, audioDirectory: String, virtualPrefix: String, modTime: Long, cacheKey: String): String
fun scanCueForLibraryWithResolvedAudio(path: String, audioPath: String, audioName: String, virtualPrefix: String, modTime: Long, cacheKey: String): String
fun editFileMetadata(path: String, metadataJson: String): String
fun reEnrichFile(requestJson: String): String
@@ -1169,7 +1169,7 @@ class MainActivity: FlutterFragmentActivity() {
mapOf(
"filename" to coreBackend.implementation,
"file_metadata" to coreBackend.fileMetadataImplementation(call.argument<String>("file_path") ?: ""),
"extensions" to if (coreBackend.routesApplication) "rust" else "go",
"extensions" to coreBackend.implementation,
"downloads" to coreBackend.implementation,
)
)
@@ -185,30 +185,19 @@ object SafDownloadHandler {
var document = createOrReuseDocumentFile(context, targetDir, stagedMimeType, stagedFileName)
?: return errorJson("Failed to create SAF file")
var pfd: android.os.ParcelFileDescriptor? = null
var detachedFd: Int? = null
var workingFile: File? = null
try {
if (backend.supportsOutputDescriptors) {
val descriptor = context.contentResolver.openFileDescriptor(document.uri, "rw")
?: throw IllegalStateException("Failed to open SAF file")
pfd = descriptor
detachedFd = descriptor.detachFd()
req.put("output_path", "")
req.put("output_fd", detachedFd)
} else {
// The OS adapter retains descriptor ownership. A path-based
// backend writes into its granted staging directory, then the
// existing SAF copy/promotion publishes the completed output.
val staged = backend.createTemporaryMediaFile(
context,
"native_saf_work_",
outputExt.ifBlank { ".tmp" },
)
workingFile = staged
req.put("output_path", staged.absolutePath)
req.remove("output_fd")
}
// The OS adapter retains descriptor ownership. The backend writes
// into its granted staging directory, then the existing SAF
// copy/promotion publishes the completed output.
val staged = backend.createTemporaryMediaFile(
context,
"native_saf_work_",
outputExt.ifBlank { ".tmp" },
)
workingFile = staged
req.put("output_path", staged.absolutePath)
req.remove("output_fd")
req.put("output_ext", outputExt)
val response = backend.downloadByStrategy(req.toString())
val respObj = JSONObject(response)
@@ -301,12 +290,6 @@ object SafDownloadHandler {
return errorJson("SAF download failed: ${e.message}")
} finally {
workingFile?.delete()
if (detachedFd == null) {
try {
pfd?.close()
} catch (_: Exception) {
}
}
}
}
@@ -62,7 +62,6 @@ internal suspend fun MainActivity.dispatchBackendApplication(call: MethodCall, r
internal object RustCoreBackend : CoreBackend {
override val implementation = "rust"
override val routesApplication = true
private lateinit var workspace: NativeMediaWorkspace
private var manager: ExtensionManager? = null
private var repository: ExtensionRepository? = null
@@ -395,14 +394,6 @@ internal object RustCoreBackend : CoreBackend {
}
}
override fun scanCueForLibrary(path: String, audioDirectory: String, virtualPrefix: String, modTime: Long, cacheKey: String): String {
val cue = File(path).canonicalFile
val audio = if (audioDirectory.isEmpty()) cue.parentFile!! else File(audioDirectory).canonicalFile
return withLibraryDirectories(listOf(cue.parent, audio.path)) {
it.scanCueFileForLibrary(cue.path, audio.path, virtualPrefix, modTime, cacheKey, java.time.Instant.now().toString(), null)
}
}
override fun scanCueForLibraryWithResolvedAudio(path: String, audioPath: String, audioName: String, virtualPrefix: String, modTime: Long, cacheKey: String): String {
val cue = File(path).canonicalFile
fun scan(descriptor: Int): String {
@@ -539,11 +530,6 @@ internal object RustCoreBackend : CoreBackend {
}
}
override fun waitForDownloadProgressDelta(since: Long, timeoutMs: Long): String {
val current = owner()
return current.withDownloadState { state -> state.waitProgressDelta(since, timeoutMs) }
}
override fun openDownloadProgress(): CoreDownloadProgress {
val current = owner()
val subscription = current.environment().use { environment ->
+2 -2
View File
@@ -277,7 +277,7 @@ import UniformTypeIdentifiers
result(nil)
return
}
if coreBackend.routesApplication && !osMethods.contains(call.method) {
if !osMethods.contains(call.method) {
if call.method == "cancelNativeDataJob" {
do {
result(try coreBackend.invokeApplication(method: call.method, arguments: call.arguments))
@@ -484,7 +484,7 @@ import UniformTypeIdentifiers
return [
"filename": coreBackend.implementation,
"file_metadata": coreBackend.fileMetadataImplementation(path: (call.arguments as? [String: Any])?["file_path"] as? String ?? ""),
"extensions": coreBackend.routesApplication ? "rust" : "go",
"extensions": coreBackend.implementation,
"downloads": coreBackend.implementation,
]
-13
View File
@@ -154,7 +154,6 @@ final class CoreDirectoryScope {
/// Native migration boundary; each process selects one stateful backend.
protocol CoreBackend {
var implementation: String { get }
var routesApplication: Bool { get }
func prepareApplicationArguments(method: String, arguments: Any?) throws -> Any?
func invokeApplication(method: String, arguments: Any?) throws -> Any?
func completeAuthCallback(state: String, code: String, sessionGrant: Bool, onResolved: (String) -> Void) throws
@@ -189,21 +188,9 @@ protocol CoreBackend {
func releaseMemoryUnderPressure() throws
}
extension CoreBackend {
var routesApplication: Bool { false }
func prepareApplicationArguments(method: String, arguments: Any?) throws -> Any? { arguments }
func invokeApplication(method: String, arguments: Any?) throws -> Any? {
throw NSError(domain: "CoreBackend", code: 1, userInfo: [NSLocalizedDescriptionKey: "Application routing is unavailable for \(method)"])
}
}
final class RustCoreBackend: CoreBackend {
static let shared = RustCoreBackend()
let implementation = "rust"
let routesApplication = true
private let root = FileManager.default.urls(for: .cachesDirectory, in: .userDomainMask)[0].appendingPathComponent("rust-core-pilot", isDirectory: true)
private let ownerLock = NSLock()
private var manager: ExtensionManager?
@@ -100,17 +100,6 @@ impl DownloadState {
self.inner.progress.snapshot().map_err(Into::into)
}
pub fn wait_progress_delta(
&self,
since: i64,
timeout_ms: i64,
) -> Result<String, DownloadProgressError> {
self.inner
.progress
.wait_delta(since, timeout_ms)
.map_err(Into::into)
}
pub fn subscribe_progress(
&self,
) -> Result<Arc<DownloadProgressSubscription>, DownloadProgressError> {
-25
View File
@@ -126,31 +126,6 @@ impl ExtensionManager {
.map_err(ExtensionManagerError::Operation)
}
#[allow(clippy::too_many_arguments)]
pub fn scan_cue_file_for_library(
&self,
path: String,
audio_directory: String,
virtual_prefix: String,
mod_time: i64,
cache_key: String,
scan_time: String,
lease: Option<Arc<RequestLease>>,
) -> Result<String, ExtensionManagerError> {
self.inner
.scan_cue_file_for_library(
&path,
&audio_directory,
&virtual_prefix,
mod_time,
&cache_key,
&scan_time,
&|| check_lease(lease.as_deref()),
)
.map(|value| value.to_string())
.map_err(ExtensionManagerError::Operation)
}
pub fn scan_library_folder_incremental(
&self,
folder: String,