mirror of
https://github.com/zarzet/SpotiFLAC-Mobile.git
synced 2026-10-20 14:37:33 +02:00
refactor(android): move RustCoreBackend.invokeApplication into its own file
NT-16 Kotlin part. The application method dispatch (about 290 lines) moves verbatim into RustCoreBackendApplication.kt as an internal extension `RustCoreBackend.dispatchApplication`. The `invokeApplication` override stays in SelectedCoreBackend.kt as a one-line delegate. `synchronized(this)` still locks the RustCoreBackend object. The object state and helpers the dispatch uses are widened from private to internal; method names, argument keys, results and error strings are unchanged.
This commit is contained in:
1 parent
b6b609f334
commit
20b4c73b8d
2 files changed
+324
-315
No files matched your search
@@ -0,0 +1,303 @@
|
||||
package com.zarz.spotiflac
|
||||
|
||||
import com.spotiflac.backend.LyricsRequest
|
||||
import java.io.File
|
||||
import java.util.UUID
|
||||
import org.json.JSONArray
|
||||
import org.json.JSONObject
|
||||
|
||||
// Application method dispatch behind RustCoreBackend.invokeApplication.
|
||||
|
||||
internal fun RustCoreBackend.dispatchApplication(method: String, arguments: Any?): Any? {
|
||||
val args = arguments as? Map<*, *> ?: emptyMap<Any, Any>()
|
||||
fun string(key: String, default: String = "") = args[key] as? String ?: default
|
||||
fun lyricsRequest(fileKey: String) = LyricsRequest(
|
||||
string("spotify_id"),
|
||||
string("track_name"),
|
||||
string("artist_name"),
|
||||
mediaPath(string(fileKey)),
|
||||
(args["duration_ms"] as? Number)?.toLong() ?: 0L,
|
||||
)
|
||||
fun ids(raw: String): List<String> {
|
||||
val values = JSONArray(raw)
|
||||
return (0 until values.length()).map { values.getString(it) }
|
||||
}
|
||||
when (method) {
|
||||
"cancelExtensionRequest" -> synchronized(this) {
|
||||
requestRegistry().cancel(string("request_id"))
|
||||
return null
|
||||
}
|
||||
"customSearchWithExtension", "getExtensionHomeFeed" -> {
|
||||
val (current, lease) = acquireRequest(string("request_id"))
|
||||
return lease.use {
|
||||
try {
|
||||
if (method == "customSearchWithExtension") {
|
||||
current.customSearchJson(string("extension_id"), string("query"), string("options"), it)
|
||||
} else {
|
||||
current.getExtensionHomeFeedJson(string("extension_id"), it)
|
||||
}
|
||||
} finally {
|
||||
it.release()
|
||||
}
|
||||
}
|
||||
}
|
||||
"prepareRuntimeState" -> synchronized(this) {
|
||||
val directory = File(string("data_dir"))
|
||||
require(directory.isAbsolute) { "Extension data directory must be absolute" }
|
||||
val data = directory.canonicalPath
|
||||
check(manager == null || identity?.second == data) {
|
||||
"Runtime state directory does not match the initialized owner"
|
||||
}
|
||||
val raw = string("runtime_state")
|
||||
manager?.environment()?.use { it.setRuntimeState(raw) }
|
||||
runtimeState = data to raw
|
||||
return null
|
||||
}
|
||||
"initExtensionSystem" -> {
|
||||
initializeOwner(args)
|
||||
return null
|
||||
}
|
||||
"setDownloadDirectory" -> {
|
||||
setDownloadDirectory(string("path"))
|
||||
return null
|
||||
}
|
||||
"acquireDownloadDirectory" -> synchronized(this) {
|
||||
val scope = openDownloadDirectory(string("path"))
|
||||
val token = UUID.randomUUID().toString()
|
||||
directoryScopes[token] = scope
|
||||
return token
|
||||
}
|
||||
"releaseDownloadDirectory" -> synchronized(this) {
|
||||
directoryScopes.remove(string("token"))?.close()
|
||||
return null
|
||||
}
|
||||
"cleanupExtensions" -> {
|
||||
shutdownOwner()
|
||||
return null
|
||||
}
|
||||
"buildFilename" -> return buildFilename(string("template"), string("metadata", "{}"))
|
||||
"sanitizeFilename" -> return sanitizeFilename(string("filename"))
|
||||
"getLogsSince" -> return synchronized(this) {
|
||||
manager?.environment()?.use { environment ->
|
||||
environment.logBuffer().use { it.since((args["index"] as? Number)?.toLong() ?: 0L) }
|
||||
} ?: """{"logs":[],"next_index":0}"""
|
||||
}
|
||||
"clearLogs" -> return synchronized(this) {
|
||||
manager?.environment()?.use { environment -> environment.logBuffer().use { it.clear() } }
|
||||
null
|
||||
}
|
||||
"setLoggingEnabled", "setAllowPrivateNetwork", "setDownloadFallbackExtensionIds",
|
||||
"setNetworkCompatibilityOptions",
|
||||
"setLyricsProviders", "setLyricsFetchOptions" -> synchronized(this) {
|
||||
when (method) {
|
||||
"setLoggingEnabled" -> {
|
||||
val enabled = args["enabled"] as? Boolean ?: false
|
||||
manager?.environment()?.use { environment ->
|
||||
environment.logBuffer().use { it.setEnabled(enabled) }
|
||||
}
|
||||
loggingEnabled = enabled
|
||||
}
|
||||
"setAllowPrivateNetwork" -> {
|
||||
val allowed = args["allowed"] as? Boolean ?: false
|
||||
manager?.environment()?.use { it.setAllowPrivateNetwork(allowed) }
|
||||
allowPrivateNetwork = allowed
|
||||
}
|
||||
"setNetworkCompatibilityOptions" -> {
|
||||
val allowed = args["allow_http"] as? Boolean ?: false
|
||||
val insecureTls = args["insecure_tls"] as? Boolean ?: false
|
||||
manager?.environment()?.use { it.setNetworkCompatibilityOptions(allowed, insecureTls) }
|
||||
allowHttpFallback = allowed
|
||||
}
|
||||
"setDownloadFallbackExtensionIds" -> {
|
||||
val raw = string("extension_ids")
|
||||
val value = if (raw.isBlank() || raw.trim() == "null") null else ids(raw)
|
||||
manager?.setFallbackProviders(value)
|
||||
fallbackProviders = value
|
||||
}
|
||||
"setLyricsProviders" -> {
|
||||
val raw = string("providers_json", "[]")
|
||||
if (manager != null) manager!!.setLyricsProvidersJson(raw) else ids(raw)
|
||||
return """{"success":true}"""
|
||||
}
|
||||
"setLyricsFetchOptions" -> {
|
||||
val raw = string("options_json", "{}")
|
||||
if (manager != null) manager!!.setLyricsFetchOptionsJson(raw) else JSONObject(raw)
|
||||
return """{"success":true}"""
|
||||
}
|
||||
}
|
||||
return null
|
||||
}
|
||||
"initExtensionRepo" -> {
|
||||
initializeRepository(string("cache_dir"))
|
||||
return null
|
||||
}
|
||||
"setRepoRegistryUrl" -> {
|
||||
repositoryOwner().setRegistryUrl(string("registry_url"))
|
||||
return null
|
||||
}
|
||||
"getRepoRegistryUrl" -> return repositoryOwner().registryUrl()
|
||||
"clearRepoRegistryUrl" -> {
|
||||
repositoryOwner().clearRegistryUrl()
|
||||
return null
|
||||
}
|
||||
"getRepoExtensions" -> return repositoryOwner().extensions(
|
||||
args["force_refresh"] as? Boolean ?: false,
|
||||
)
|
||||
"downloadRepoExtension" -> return repositoryOwner().download(
|
||||
string("extension_id"),
|
||||
string("dest_dir"),
|
||||
)
|
||||
}
|
||||
val current = owner()
|
||||
return when (method) {
|
||||
"loadExtensionsFromDir" -> {
|
||||
synchronized(this) {
|
||||
check(File(string("dir_path")).canonicalPath == identity?.first) { "Extension source directory does not match the initialized owner" }
|
||||
}
|
||||
current.loadAll()
|
||||
}
|
||||
"loadExtensionFromPath" -> current.install(string("file_path"))
|
||||
"upgradeExtension" -> current.upgrade(string("file_path"))
|
||||
"checkExtensionUpgrade" -> current.checkUpgrade(string("file_path"))
|
||||
"getInstalledExtensions" -> current.installed()
|
||||
"setExtensionEnabled" -> {
|
||||
current.setEnabled(string("extension_id"), args["enabled"] as? Boolean ?: false)
|
||||
null
|
||||
}
|
||||
"removeExtension" -> {
|
||||
current.remove(string("extension_id"))
|
||||
null
|
||||
}
|
||||
"getExtensionSettings" -> current.environment().use { it.settings(string("extension_id")) }
|
||||
"setExtensionSettings" -> {
|
||||
current.updateSettings(string("extension_id"), string("settings", "{}"))
|
||||
null
|
||||
}
|
||||
"invokeExtensionAction" -> if (args.containsKey("arguments_json")) {
|
||||
// Transient form input bypasses persistent extension settings.
|
||||
current.call(string("extension_id"), string("action"), string("arguments_json", "[]"), null, 120_000uL)
|
||||
} else current.invokeAction(string("extension_id"), string("action"))
|
||||
"checkExtensionHealth" -> current.checkExtensionHealthJson(string("extension_id"))
|
||||
"setProviderPriority", "setMetadataProviderPriority" -> {
|
||||
current.setProviderPriority(if (method == "setProviderPriority") "download" else "metadata", ids(string("priority", "[]")))
|
||||
null
|
||||
}
|
||||
"getProviderPriority", "getMetadataProviderPriority" -> {
|
||||
JSONObject(current.providerPriorities()).optJSONArray(if (method == "getProviderPriority") "download" else "metadata")?.toString() ?: "[]"
|
||||
}
|
||||
"searchTracksWithMetadataProviders" -> current.searchMetadataProviders(
|
||||
string("query"),
|
||||
(args["limit"] as? Number)?.toLong() ?: 20L,
|
||||
args["include_extensions"] as? Boolean ?: true,
|
||||
"",
|
||||
30_000uL,
|
||||
)
|
||||
"searchTracksWithMetadataProvider" -> current.searchMetadataProvider(
|
||||
string("extension_id"),
|
||||
string("query"),
|
||||
(args["limit"] as? Number)?.toLong() ?: 20L,
|
||||
30_000uL,
|
||||
)
|
||||
"getProviderMetadata" -> current.getProviderMetadataJson(
|
||||
string("provider_id"),
|
||||
string("resource_type"),
|
||||
string("resource_id"),
|
||||
null,
|
||||
)
|
||||
"searchDeezerByISRC" -> current.searchDeezerByIsrcForItemId(
|
||||
string("isrc"),
|
||||
string("item_id"),
|
||||
null,
|
||||
)
|
||||
"getDeezerExtendedMetadata" -> current.getDeezerExtendedMetadata(
|
||||
string("track_id"),
|
||||
null,
|
||||
)
|
||||
"convertSpotifyToDeezer" -> current.convertSpotifyToDeezer(
|
||||
string("resource_type"),
|
||||
string("spotify_id"),
|
||||
null,
|
||||
)
|
||||
"getTrackPlatformLinks" -> current.getTrackPlatformLinksJson(
|
||||
string("spotify_id"),
|
||||
string("isrc"),
|
||||
null,
|
||||
)
|
||||
"fetchMusicBrainzTags" -> {
|
||||
val genre = try {
|
||||
current.fetchMusicBrainzGenreByIsrc(string("isrc"), null)
|
||||
} catch (_: Exception) {
|
||||
""
|
||||
}
|
||||
val albumArtist = try {
|
||||
current.fetchMusicBrainzAlbumArtistByIsrc(
|
||||
string("isrc"),
|
||||
string("album_name"),
|
||||
null,
|
||||
)
|
||||
} catch (_: Exception) {
|
||||
""
|
||||
}
|
||||
JSONObject()
|
||||
.put("genre", genre)
|
||||
.put("album_artist", albumArtist)
|
||||
.toString()
|
||||
}
|
||||
"getTrackCacheSize" -> current.getTrackCacheSize().toInt()
|
||||
"clearTrackCache" -> {
|
||||
current.clearTrackIdCache()
|
||||
null
|
||||
}
|
||||
"setMetadataLanguage" -> {
|
||||
current.setMetadataLanguage(string("tag"))
|
||||
null
|
||||
}
|
||||
"findURLHandler" -> current.findUrlHandler(string("url")) ?: ""
|
||||
"handleURLWithExtension" -> current.handleUrlJson(string("url"))
|
||||
"getExtensionPendingAuth" -> current.getExtensionPendingAuthJson(string("extension_id")).ifEmpty { null }
|
||||
"completeExtensionSessionGrant" -> {
|
||||
completeSessionGrant(current, string("extension_id"), string("grant"))
|
||||
true
|
||||
}
|
||||
"getAllDownloadProgress" -> current.withDownloadState { state -> state.allProgress() }
|
||||
"cleanupConnections" -> current.environment().use { it.cleanupConnections(); null }
|
||||
"resetDownloadCancels", "cancelDownloads" -> {
|
||||
val values = args["item_ids"] as? List<*> ?: error("Expected item_ids list")
|
||||
val itemIds = values.map { it as? String ?: error("Expected string item ID") }
|
||||
current.withDownloadState { state ->
|
||||
if (method == "cancelDownloads") state.cancelDownloads(itemIds)
|
||||
else state.resetDownloadCancels(itemIds)
|
||||
}
|
||||
null
|
||||
}
|
||||
"clearItemProgress", "cancelDownload", "resetDownloadCancel" -> current.withDownloadState { state ->
|
||||
when (method) {
|
||||
"clearItemProgress" -> state.clearItemProgress(string("item_id"))
|
||||
"cancelDownload" -> state.cancelDownload(string("item_id"))
|
||||
else -> state.resetDownloadCancel(string("item_id"))
|
||||
}
|
||||
null
|
||||
}
|
||||
"getLyricsLRC", "getLyricsLRCWithSource" -> withMediaFiles(listOf(string("file_path"))) {
|
||||
val request = lyricsRequest("file_path")
|
||||
if (method == "getLyricsLRC") it.getLyricsLrc(request, null)
|
||||
else it.getLyricsLrcWithSource(request, null)
|
||||
}
|
||||
"embedLyricsToFile" -> withMediaFiles(listOf(string("file_path"))) {
|
||||
it.embedLyricsToFile(mediaPath(string("file_path")), string("lyrics"), null)
|
||||
}
|
||||
"fetchAndSaveLyrics" -> try {
|
||||
withMediaFiles(listOf(string("audio_file_path"), string("output_path"))) {
|
||||
it.fetchAndSaveLyrics(lyricsRequest("audio_file_path"), mediaPath(string("output_path")), null)
|
||||
"""{"success":true}"""
|
||||
}
|
||||
} catch (error: Exception) {
|
||||
JSONObject().put("success", false).put("error", error.message ?: "Lyrics operation failed").toString()
|
||||
}
|
||||
"findCollectionAcrossExtensions" -> current.findCollectionAcrossExtensionsJson(
|
||||
arguments as? String ?: "{}",
|
||||
null,
|
||||
)
|
||||
else -> error("Rust application method is not connected yet: $method")
|
||||
}
|
||||
}
|
||||
@@ -15,8 +15,6 @@ import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.withContext
|
||||
import java.io.File
|
||||
import java.security.MessageDigest
|
||||
import java.util.UUID
|
||||
import org.json.JSONArray
|
||||
import org.json.JSONObject
|
||||
|
||||
internal fun createCoreBackend(context: Context): CoreBackend = RustCoreBackend.initialize(context)
|
||||
@@ -63,16 +61,16 @@ internal suspend fun MainActivity.dispatchBackendApplication(call: MethodCall, r
|
||||
internal object RustCoreBackend : CoreBackend {
|
||||
override val implementation = "rust"
|
||||
private lateinit var workspace: NativeMediaWorkspace
|
||||
private var manager: ExtensionManager? = null
|
||||
internal var manager: ExtensionManager? = null
|
||||
private var repository: ExtensionRepository? = null
|
||||
private var requests: CancellationRegistry? = null
|
||||
private var identity: Triple<String, String, List<Byte>>? = null
|
||||
private var loggingEnabled = false
|
||||
private var allowPrivateNetwork = false
|
||||
private var allowHttpFallback = false
|
||||
private var fallbackProviders: List<String>? = null
|
||||
private var runtimeState: Pair<String, String>? = null
|
||||
private val directoryScopes = mutableMapOf<String, AutoCloseable>()
|
||||
internal var identity: Triple<String, String, List<Byte>>? = null
|
||||
internal var loggingEnabled = false
|
||||
internal var allowPrivateNetwork = false
|
||||
internal var allowHttpFallback = false
|
||||
internal var fallbackProviders: List<String>? = null
|
||||
internal var runtimeState: Pair<String, String>? = null
|
||||
internal val directoryScopes = mutableMapOf<String, AutoCloseable>()
|
||||
private var libraryCoverScope: AutoCloseable? = null
|
||||
@Volatile private var libraryCoverDirectory: File? = null
|
||||
|
||||
@@ -85,11 +83,11 @@ internal object RustCoreBackend : CoreBackend {
|
||||
}
|
||||
|
||||
@Synchronized
|
||||
private fun owner(): ExtensionManager {
|
||||
internal fun owner(): ExtensionManager {
|
||||
return checkNotNull(manager) { "Rust backend is not initialized" }
|
||||
}
|
||||
|
||||
private fun <T> ExtensionManager.withDownloadState(block: (DownloadState) -> T): T =
|
||||
internal fun <T> ExtensionManager.withDownloadState(block: (DownloadState) -> T): T =
|
||||
environment().use { environment ->
|
||||
environment.downloadState().use(block)
|
||||
}
|
||||
@@ -123,7 +121,7 @@ internal object RustCoreBackend : CoreBackend {
|
||||
}
|
||||
|
||||
@Synchronized
|
||||
private fun setDownloadDirectory(path: String) {
|
||||
internal fun setDownloadDirectory(path: String) {
|
||||
val current = owner()
|
||||
val storage = checkNotNull(identity)
|
||||
val files = workspace.ensureDirectory()
|
||||
@@ -133,12 +131,12 @@ internal object RustCoreBackend : CoreBackend {
|
||||
}
|
||||
|
||||
@Synchronized
|
||||
private fun requestRegistry(): CancellationRegistry = requests ?: CancellationRegistry(
|
||||
internal fun requestRegistry(): CancellationRegistry = requests ?: CancellationRegistry(
|
||||
CancellationDomain.EXTENSION_REQUEST,
|
||||
).also { requests = it }
|
||||
|
||||
@Synchronized
|
||||
private fun acquireRequest(id: String): Pair<ExtensionManager, RequestLease> =
|
||||
internal fun acquireRequest(id: String): Pair<ExtensionManager, RequestLease> =
|
||||
owner() to requestRegistry().acquire(id)
|
||||
|
||||
internal fun runDataJob(request: JSONObject, bytes: ByteArray, lease: RequestLease): String {
|
||||
@@ -160,11 +158,11 @@ internal object RustCoreBackend : CoreBackend {
|
||||
}
|
||||
|
||||
@Synchronized
|
||||
private fun repositoryOwner(): ExtensionRepository =
|
||||
internal fun repositoryOwner(): ExtensionRepository =
|
||||
checkNotNull(repository) { "Extension repository is not initialized" }
|
||||
|
||||
@Synchronized
|
||||
private fun initializeRepository(cachePath: String) {
|
||||
internal fun initializeRepository(cachePath: String) {
|
||||
val current = owner()
|
||||
if (repository != null) return
|
||||
val cache = File(cachePath)
|
||||
@@ -173,7 +171,7 @@ internal object RustCoreBackend : CoreBackend {
|
||||
}
|
||||
|
||||
@Synchronized
|
||||
private fun initializeOwner(arguments: Map<*, *>) {
|
||||
internal fun initializeOwner(arguments: Map<*, *>) {
|
||||
val sourcePath = File(arguments["extensions_dir"] as String)
|
||||
val dataPath = File(arguments["data_dir"] as String)
|
||||
require(sourcePath.isAbsolute && dataPath.isAbsolute) { "Extension storage directories must be absolute" }
|
||||
@@ -230,7 +228,7 @@ internal object RustCoreBackend : CoreBackend {
|
||||
}
|
||||
|
||||
@Synchronized
|
||||
private fun shutdownOwner() {
|
||||
internal fun shutdownOwner() {
|
||||
requests?.let {
|
||||
it.shutdown()
|
||||
it.close()
|
||||
@@ -305,7 +303,7 @@ internal object RustCoreBackend : CoreBackend {
|
||||
|
||||
// Native callers have already selected/resolved these files through app or
|
||||
// SAF access. Retain only their parent directories for this operation.
|
||||
private fun <T> withMediaFiles(paths: List<String>, block: (ExtensionManager) -> T): T =
|
||||
internal fun <T> withMediaFiles(paths: List<String>, block: (ExtensionManager) -> T): T =
|
||||
withLibraryDirectories(paths.filter { it.isNotBlank() }.map { path ->
|
||||
require(File(path).isAbsolute) { "Media paths must be absolute" }
|
||||
File(path).absoluteFile.parent!!
|
||||
@@ -415,7 +413,7 @@ internal object RustCoreBackend : CoreBackend {
|
||||
}
|
||||
}
|
||||
|
||||
private fun mediaPath(path: String): String = if (path.isEmpty()) "" else File(path).canonicalPath
|
||||
internal fun mediaPath(path: String): String = if (path.isEmpty()) "" else File(path).canonicalPath
|
||||
|
||||
override fun reEnrichFile(requestJson: String): String {
|
||||
val request = JSONObject(requestJson)
|
||||
@@ -566,304 +564,12 @@ internal object RustCoreBackend : CoreBackend {
|
||||
}
|
||||
}
|
||||
|
||||
private fun completeSessionGrant(current: ExtensionManager, id: String, grant: String) {
|
||||
internal fun completeSessionGrant(current: ExtensionManager, id: String, grant: String) {
|
||||
current.environment().use { it.setSessionGrant(id, grant) }
|
||||
requireSuccessfulExtensionAction(id, "completeGrant", current.invokeAction(id, "completeGrant"))
|
||||
}
|
||||
|
||||
override fun invokeApplication(method: String, arguments: Any?): Any? {
|
||||
val args = arguments as? Map<*, *> ?: emptyMap<Any, Any>()
|
||||
fun string(key: String, default: String = "") = args[key] as? String ?: default
|
||||
fun lyricsRequest(fileKey: String) = LyricsRequest(
|
||||
string("spotify_id"),
|
||||
string("track_name"),
|
||||
string("artist_name"),
|
||||
mediaPath(string(fileKey)),
|
||||
(args["duration_ms"] as? Number)?.toLong() ?: 0L,
|
||||
)
|
||||
fun ids(raw: String): List<String> {
|
||||
val values = JSONArray(raw)
|
||||
return (0 until values.length()).map { values.getString(it) }
|
||||
}
|
||||
when (method) {
|
||||
"cancelExtensionRequest" -> synchronized(this) {
|
||||
requestRegistry().cancel(string("request_id"))
|
||||
return null
|
||||
}
|
||||
"customSearchWithExtension", "getExtensionHomeFeed" -> {
|
||||
val (current, lease) = acquireRequest(string("request_id"))
|
||||
return lease.use {
|
||||
try {
|
||||
if (method == "customSearchWithExtension") {
|
||||
current.customSearchJson(string("extension_id"), string("query"), string("options"), it)
|
||||
} else {
|
||||
current.getExtensionHomeFeedJson(string("extension_id"), it)
|
||||
}
|
||||
} finally {
|
||||
it.release()
|
||||
}
|
||||
}
|
||||
}
|
||||
"prepareRuntimeState" -> synchronized(this) {
|
||||
val directory = File(string("data_dir"))
|
||||
require(directory.isAbsolute) { "Extension data directory must be absolute" }
|
||||
val data = directory.canonicalPath
|
||||
check(manager == null || identity?.second == data) {
|
||||
"Runtime state directory does not match the initialized owner"
|
||||
}
|
||||
val raw = string("runtime_state")
|
||||
manager?.environment()?.use { it.setRuntimeState(raw) }
|
||||
runtimeState = data to raw
|
||||
return null
|
||||
}
|
||||
"initExtensionSystem" -> {
|
||||
initializeOwner(args)
|
||||
return null
|
||||
}
|
||||
"setDownloadDirectory" -> {
|
||||
setDownloadDirectory(string("path"))
|
||||
return null
|
||||
}
|
||||
"acquireDownloadDirectory" -> synchronized(this) {
|
||||
val scope = openDownloadDirectory(string("path"))
|
||||
val token = UUID.randomUUID().toString()
|
||||
directoryScopes[token] = scope
|
||||
return token
|
||||
}
|
||||
"releaseDownloadDirectory" -> synchronized(this) {
|
||||
directoryScopes.remove(string("token"))?.close()
|
||||
return null
|
||||
}
|
||||
"cleanupExtensions" -> {
|
||||
shutdownOwner()
|
||||
return null
|
||||
}
|
||||
"buildFilename" -> return buildFilename(string("template"), string("metadata", "{}"))
|
||||
"sanitizeFilename" -> return sanitizeFilename(string("filename"))
|
||||
"getLogsSince" -> return synchronized(this) {
|
||||
manager?.environment()?.use { environment ->
|
||||
environment.logBuffer().use { it.since((args["index"] as? Number)?.toLong() ?: 0L) }
|
||||
} ?: """{"logs":[],"next_index":0}"""
|
||||
}
|
||||
"clearLogs" -> return synchronized(this) {
|
||||
manager?.environment()?.use { environment -> environment.logBuffer().use { it.clear() } }
|
||||
null
|
||||
}
|
||||
"setLoggingEnabled", "setAllowPrivateNetwork", "setDownloadFallbackExtensionIds",
|
||||
"setNetworkCompatibilityOptions",
|
||||
"setLyricsProviders", "setLyricsFetchOptions" -> synchronized(this) {
|
||||
when (method) {
|
||||
"setLoggingEnabled" -> {
|
||||
val enabled = args["enabled"] as? Boolean ?: false
|
||||
manager?.environment()?.use { environment ->
|
||||
environment.logBuffer().use { it.setEnabled(enabled) }
|
||||
}
|
||||
loggingEnabled = enabled
|
||||
}
|
||||
"setAllowPrivateNetwork" -> {
|
||||
val allowed = args["allowed"] as? Boolean ?: false
|
||||
manager?.environment()?.use { it.setAllowPrivateNetwork(allowed) }
|
||||
allowPrivateNetwork = allowed
|
||||
}
|
||||
"setNetworkCompatibilityOptions" -> {
|
||||
val allowed = args["allow_http"] as? Boolean ?: false
|
||||
val insecureTls = args["insecure_tls"] as? Boolean ?: false
|
||||
manager?.environment()?.use { it.setNetworkCompatibilityOptions(allowed, insecureTls) }
|
||||
allowHttpFallback = allowed
|
||||
}
|
||||
"setDownloadFallbackExtensionIds" -> {
|
||||
val raw = string("extension_ids")
|
||||
val value = if (raw.isBlank() || raw.trim() == "null") null else ids(raw)
|
||||
manager?.setFallbackProviders(value)
|
||||
fallbackProviders = value
|
||||
}
|
||||
"setLyricsProviders" -> {
|
||||
val raw = string("providers_json", "[]")
|
||||
if (manager != null) manager!!.setLyricsProvidersJson(raw) else ids(raw)
|
||||
return """{"success":true}"""
|
||||
}
|
||||
"setLyricsFetchOptions" -> {
|
||||
val raw = string("options_json", "{}")
|
||||
if (manager != null) manager!!.setLyricsFetchOptionsJson(raw) else JSONObject(raw)
|
||||
return """{"success":true}"""
|
||||
}
|
||||
}
|
||||
return null
|
||||
}
|
||||
"initExtensionRepo" -> {
|
||||
initializeRepository(string("cache_dir"))
|
||||
return null
|
||||
}
|
||||
"setRepoRegistryUrl" -> {
|
||||
repositoryOwner().setRegistryUrl(string("registry_url"))
|
||||
return null
|
||||
}
|
||||
"getRepoRegistryUrl" -> return repositoryOwner().registryUrl()
|
||||
"clearRepoRegistryUrl" -> {
|
||||
repositoryOwner().clearRegistryUrl()
|
||||
return null
|
||||
}
|
||||
"getRepoExtensions" -> return repositoryOwner().extensions(
|
||||
args["force_refresh"] as? Boolean ?: false,
|
||||
)
|
||||
"downloadRepoExtension" -> return repositoryOwner().download(
|
||||
string("extension_id"),
|
||||
string("dest_dir"),
|
||||
)
|
||||
}
|
||||
val current = owner()
|
||||
return when (method) {
|
||||
"loadExtensionsFromDir" -> {
|
||||
synchronized(this) {
|
||||
check(File(string("dir_path")).canonicalPath == identity?.first) { "Extension source directory does not match the initialized owner" }
|
||||
}
|
||||
current.loadAll()
|
||||
}
|
||||
"loadExtensionFromPath" -> current.install(string("file_path"))
|
||||
"upgradeExtension" -> current.upgrade(string("file_path"))
|
||||
"checkExtensionUpgrade" -> current.checkUpgrade(string("file_path"))
|
||||
"getInstalledExtensions" -> current.installed()
|
||||
"setExtensionEnabled" -> {
|
||||
current.setEnabled(string("extension_id"), args["enabled"] as? Boolean ?: false)
|
||||
null
|
||||
}
|
||||
"removeExtension" -> {
|
||||
current.remove(string("extension_id"))
|
||||
null
|
||||
}
|
||||
"getExtensionSettings" -> current.environment().use { it.settings(string("extension_id")) }
|
||||
"setExtensionSettings" -> {
|
||||
current.updateSettings(string("extension_id"), string("settings", "{}"))
|
||||
null
|
||||
}
|
||||
"invokeExtensionAction" -> if (args.containsKey("arguments_json")) {
|
||||
// Transient form input bypasses persistent extension settings.
|
||||
current.call(string("extension_id"), string("action"), string("arguments_json", "[]"), null, 120_000uL)
|
||||
} else current.invokeAction(string("extension_id"), string("action"))
|
||||
"checkExtensionHealth" -> current.checkExtensionHealthJson(string("extension_id"))
|
||||
"setProviderPriority", "setMetadataProviderPriority" -> {
|
||||
current.setProviderPriority(if (method == "setProviderPriority") "download" else "metadata", ids(string("priority", "[]")))
|
||||
null
|
||||
}
|
||||
"getProviderPriority", "getMetadataProviderPriority" -> {
|
||||
JSONObject(current.providerPriorities()).optJSONArray(if (method == "getProviderPriority") "download" else "metadata")?.toString() ?: "[]"
|
||||
}
|
||||
"searchTracksWithMetadataProviders" -> current.searchMetadataProviders(
|
||||
string("query"),
|
||||
(args["limit"] as? Number)?.toLong() ?: 20L,
|
||||
args["include_extensions"] as? Boolean ?: true,
|
||||
"",
|
||||
30_000uL,
|
||||
)
|
||||
"searchTracksWithMetadataProvider" -> current.searchMetadataProvider(
|
||||
string("extension_id"),
|
||||
string("query"),
|
||||
(args["limit"] as? Number)?.toLong() ?: 20L,
|
||||
30_000uL,
|
||||
)
|
||||
"getProviderMetadata" -> current.getProviderMetadataJson(
|
||||
string("provider_id"),
|
||||
string("resource_type"),
|
||||
string("resource_id"),
|
||||
null,
|
||||
)
|
||||
"searchDeezerByISRC" -> current.searchDeezerByIsrcForItemId(
|
||||
string("isrc"),
|
||||
string("item_id"),
|
||||
null,
|
||||
)
|
||||
"getDeezerExtendedMetadata" -> current.getDeezerExtendedMetadata(
|
||||
string("track_id"),
|
||||
null,
|
||||
)
|
||||
"convertSpotifyToDeezer" -> current.convertSpotifyToDeezer(
|
||||
string("resource_type"),
|
||||
string("spotify_id"),
|
||||
null,
|
||||
)
|
||||
"getTrackPlatformLinks" -> current.getTrackPlatformLinksJson(
|
||||
string("spotify_id"),
|
||||
string("isrc"),
|
||||
null,
|
||||
)
|
||||
"fetchMusicBrainzTags" -> {
|
||||
val genre = try {
|
||||
current.fetchMusicBrainzGenreByIsrc(string("isrc"), null)
|
||||
} catch (_: Exception) {
|
||||
""
|
||||
}
|
||||
val albumArtist = try {
|
||||
current.fetchMusicBrainzAlbumArtistByIsrc(
|
||||
string("isrc"),
|
||||
string("album_name"),
|
||||
null,
|
||||
)
|
||||
} catch (_: Exception) {
|
||||
""
|
||||
}
|
||||
JSONObject()
|
||||
.put("genre", genre)
|
||||
.put("album_artist", albumArtist)
|
||||
.toString()
|
||||
}
|
||||
"getTrackCacheSize" -> current.getTrackCacheSize().toInt()
|
||||
"clearTrackCache" -> {
|
||||
current.clearTrackIdCache()
|
||||
null
|
||||
}
|
||||
"setMetadataLanguage" -> {
|
||||
current.setMetadataLanguage(string("tag"))
|
||||
null
|
||||
}
|
||||
"findURLHandler" -> current.findUrlHandler(string("url")) ?: ""
|
||||
"handleURLWithExtension" -> current.handleUrlJson(string("url"))
|
||||
"getExtensionPendingAuth" -> current.getExtensionPendingAuthJson(string("extension_id")).ifEmpty { null }
|
||||
"completeExtensionSessionGrant" -> {
|
||||
completeSessionGrant(current, string("extension_id"), string("grant"))
|
||||
true
|
||||
}
|
||||
"getAllDownloadProgress" -> current.withDownloadState { state -> state.allProgress() }
|
||||
"cleanupConnections" -> current.environment().use { it.cleanupConnections(); null }
|
||||
"resetDownloadCancels", "cancelDownloads" -> {
|
||||
val values = args["item_ids"] as? List<*> ?: error("Expected item_ids list")
|
||||
val itemIds = values.map { it as? String ?: error("Expected string item ID") }
|
||||
current.withDownloadState { state ->
|
||||
if (method == "cancelDownloads") state.cancelDownloads(itemIds)
|
||||
else state.resetDownloadCancels(itemIds)
|
||||
}
|
||||
null
|
||||
}
|
||||
"clearItemProgress", "cancelDownload", "resetDownloadCancel" -> current.withDownloadState { state ->
|
||||
when (method) {
|
||||
"clearItemProgress" -> state.clearItemProgress(string("item_id"))
|
||||
"cancelDownload" -> state.cancelDownload(string("item_id"))
|
||||
else -> state.resetDownloadCancel(string("item_id"))
|
||||
}
|
||||
null
|
||||
}
|
||||
"getLyricsLRC", "getLyricsLRCWithSource" -> withMediaFiles(listOf(string("file_path"))) {
|
||||
val request = lyricsRequest("file_path")
|
||||
if (method == "getLyricsLRC") it.getLyricsLrc(request, null)
|
||||
else it.getLyricsLrcWithSource(request, null)
|
||||
}
|
||||
"embedLyricsToFile" -> withMediaFiles(listOf(string("file_path"))) {
|
||||
it.embedLyricsToFile(mediaPath(string("file_path")), string("lyrics"), null)
|
||||
}
|
||||
"fetchAndSaveLyrics" -> try {
|
||||
withMediaFiles(listOf(string("audio_file_path"), string("output_path"))) {
|
||||
it.fetchAndSaveLyrics(lyricsRequest("audio_file_path"), mediaPath(string("output_path")), null)
|
||||
"""{"success":true}"""
|
||||
}
|
||||
} catch (error: Exception) {
|
||||
JSONObject().put("success", false).put("error", error.message ?: "Lyrics operation failed").toString()
|
||||
}
|
||||
"findCollectionAcrossExtensions" -> current.findCollectionAcrossExtensionsJson(
|
||||
arguments as? String ?: "{}",
|
||||
null,
|
||||
)
|
||||
else -> error("Rust application method is not connected yet: $method")
|
||||
}
|
||||
}
|
||||
override fun invokeApplication(method: String, arguments: Any?): Any? = dispatchApplication(method, arguments)
|
||||
|
||||
override fun buildFilename(template: String, metadataJson: String): String =
|
||||
com.spotiflac.backend.buildFilename(template, metadataJson)
|
||||
|
||||
Reference in new issue
Block a user