mirror of
https://github.com/zarzet/SpotiFLAC-Mobile.git
synced 2026-10-20 22:41:38 +02:00
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:
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 ->
|
||||
|
||||
@@ -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,
|
||||
]
|
||||
|
||||
|
||||
@@ -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> {
|
||||
|
||||
@@ -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,
|
||||
|
||||
Reference in new issue
Block a user