diff --git a/android/app/src/main/kotlin/com/zarz/spotiflac/CoreBackend.kt b/android/app/src/main/kotlin/com/zarz/spotiflac/CoreBackend.kt index 37ba7236..a2618434 100644 --- a/android/app/src/main/kotlin/com/zarz/spotiflac/CoreBackend.kt +++ b/android/app/src/main/kotlin/com/zarz/spotiflac/CoreBackend.kt @@ -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 diff --git a/android/app/src/main/kotlin/com/zarz/spotiflac/MainActivity.kt b/android/app/src/main/kotlin/com/zarz/spotiflac/MainActivity.kt index 53e35776..dd9c31d4 100644 --- a/android/app/src/main/kotlin/com/zarz/spotiflac/MainActivity.kt +++ b/android/app/src/main/kotlin/com/zarz/spotiflac/MainActivity.kt @@ -1169,7 +1169,7 @@ class MainActivity: FlutterFragmentActivity() { mapOf( "filename" to coreBackend.implementation, "file_metadata" to coreBackend.fileMetadataImplementation(call.argument("file_path") ?: ""), - "extensions" to if (coreBackend.routesApplication) "rust" else "go", + "extensions" to coreBackend.implementation, "downloads" to coreBackend.implementation, ) ) diff --git a/android/app/src/main/kotlin/com/zarz/spotiflac/SafDownloadHandler.kt b/android/app/src/main/kotlin/com/zarz/spotiflac/SafDownloadHandler.kt index a983d968..f05db018 100644 --- a/android/app/src/main/kotlin/com/zarz/spotiflac/SafDownloadHandler.kt +++ b/android/app/src/main/kotlin/com/zarz/spotiflac/SafDownloadHandler.kt @@ -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) { - } - } } } diff --git a/android/app/src/rust/kotlin/com/zarz/spotiflac/SelectedCoreBackend.kt b/android/app/src/rust/kotlin/com/zarz/spotiflac/SelectedCoreBackend.kt index 18b93bed..ce9a3778 100644 --- a/android/app/src/rust/kotlin/com/zarz/spotiflac/SelectedCoreBackend.kt +++ b/android/app/src/rust/kotlin/com/zarz/spotiflac/SelectedCoreBackend.kt @@ -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 -> diff --git a/ios/Runner/AppDelegate.swift b/ios/Runner/AppDelegate.swift index 073d1ba6..b6b3f182 100644 --- a/ios/Runner/AppDelegate.swift +++ b/ios/Runner/AppDelegate.swift @@ -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, ] diff --git a/ios/Runner/CoreBackend.swift b/ios/Runner/CoreBackend.swift index b6f821ce..07e5861b 100644 --- a/ios/Runner/CoreBackend.swift +++ b/ios/Runner/CoreBackend.swift @@ -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? diff --git a/rust_backend/crates/mobile/src/progress.rs b/rust_backend/crates/mobile/src/progress.rs index c9c0d018..71962302 100644 --- a/rust_backend/crates/mobile/src/progress.rs +++ b/rust_backend/crates/mobile/src/progress.rs @@ -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 { - self.inner - .progress - .wait_delta(since, timeout_ms) - .map_err(Into::into) - } - pub fn subscribe_progress( &self, ) -> Result, DownloadProgressError> { diff --git a/rust_backend/crates/mobile/src/tags.rs b/rust_backend/crates/mobile/src/tags.rs index 90b39b8b..2e8e2f10 100644 --- a/rust_backend/crates/mobile/src/tags.rs +++ b/rust_backend/crates/mobile/src/tags.rs @@ -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>, - ) -> Result { - 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,