Skip to content

Commit

Permalink
Ah yes the infamous injectable class
Browse files Browse the repository at this point in the history
  • Loading branch information
brahmkshatriya committed Jan 13, 2025
1 parent 6984158 commit a48484f
Show file tree
Hide file tree
Showing 28 changed files with 357 additions and 267 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import dev.brahmkshatriya.echo.common.TrackerExtension
import dev.brahmkshatriya.echo.common.clients.ExtensionClient
import dev.brahmkshatriya.echo.common.clients.LoginClient
import dev.brahmkshatriya.echo.common.helpers.ExtensionType
import dev.brahmkshatriya.echo.common.helpers.Injectable
import dev.brahmkshatriya.echo.common.models.Metadata
import dev.brahmkshatriya.echo.common.providers.LyricsExtensionsProvider
import dev.brahmkshatriya.echo.common.providers.MusicExtensionsProvider
Expand All @@ -23,7 +24,6 @@ import dev.brahmkshatriya.echo.db.models.UserEntity
import dev.brahmkshatriya.echo.db.models.UserEntity.Companion.toUser
import dev.brahmkshatriya.echo.extensions.plugger.FileChangeListener
import dev.brahmkshatriya.echo.extensions.plugger.PackageChangeListener
import dev.brahmkshatriya.echo.extensions.plugger.catchLazy
import dev.brahmkshatriya.echo.offline.OfflineExtension
import dev.brahmkshatriya.echo.offline.UnifiedExtension
import dev.brahmkshatriya.echo.utils.catchWith
Expand Down Expand Up @@ -64,12 +64,17 @@ class ExtensionLoader(
private val listener = PackageChangeListener(context)
val fileListener = FileChangeListener(scope)

val offline = OfflineExtension.metadata to catchLazy { OfflineExtension(context, cache) }
private val unified = UnifiedExtension.metadata to catchLazy { UnifiedExtension(context) }
val offline by lazy { OfflineExtension(context, cache) }
private val offlinePair: Pair<Metadata, Injectable<ExtensionClient>> =
OfflineExtension.metadata to Injectable { offline }

private val unified: Pair<Metadata, Injectable<ExtensionClient>> =
UnifiedExtension.metadata to Injectable { UnifiedExtension(context) }

// private val test = TestExtension.metadata to catchLazy { TestExtension() }

private val musicExtensionRepo =
MusicExtensionRepo(context, listener, fileListener, offline, unified)
MusicExtensionRepo(context, listener, fileListener, offlinePair, unified)

private val trackerExtensionRepo =
TrackerExtensionRepo(context, listener, fileListener)
Expand All @@ -91,7 +96,7 @@ class ExtensionLoader(
private suspend fun Extension<*>.setLoginUser(trigger: Boolean = false) {
println("Setting login user for $name")
val user = userDao.getCurrentUser(id)
get<LoginClient, Unit>(throwableFlow) {
inject<LoginClient> {
withTimeout(TIMEOUT) { onSetLoginUser(user?.toUser()) }
}
if (trigger) {
Expand All @@ -109,6 +114,7 @@ class ExtensionLoader(
lyricsListFlow.getExtension(clientId)?.setLoginUser()
extensionListFlow.getExtension(clientId)?.setLoginUser(true)
}

private val combined = extensionListFlow
.combine(trackerListFlow) { a, b -> a.orEmpty() + b.orEmpty() }
.combine(lyricsListFlow) { a, b -> a + b.orEmpty() }
Expand Down Expand Up @@ -144,17 +150,17 @@ class ExtensionLoader(
val lyricsExtensions = lyricsListFlow.value.orEmpty()
val musicExtensions = extensionListFlow.value.orEmpty()
list.forEach { extension ->
extension.get<TrackerExtensionsProvider, Unit>(throwableFlow) {
extension.inject<TrackerExtensionsProvider> {
inject(extension.name, requiredTrackerExtensions, trackerExtensions) {
setTrackerExtensions(it)
}
}
extension.get<LyricsExtensionsProvider, Unit>(throwableFlow) {
extension.inject<LyricsExtensionsProvider> {
inject(extension.name, requiredLyricsExtensions, lyricsExtensions) {
setLyricsExtensions(it)
}
}
extension.get<MusicExtensionsProvider, Unit>(throwableFlow) {
extension.inject<MusicExtensionsProvider> {
inject(extension.name, requiredMusicExtensions, musicExtensions) {
setMusicExtensions(it)
}
Expand Down Expand Up @@ -232,7 +238,7 @@ class ExtensionLoader(
}

private suspend fun <T : ExtensionClient> ExtensionRepo<T>.getPlugins(
collector: FlowCollector<List<Pair<Metadata, Lazy<Result<T>>>>>
collector: FlowCollector<List<Pair<Metadata, Injectable<T>>>>
) {
val pluginFlow = getAllPlugins().catchWith(throwableFlow).map { list ->
list.mapNotNull { result ->
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,63 +6,44 @@ import dev.brahmkshatriya.echo.common.clients.LyricsClient
import dev.brahmkshatriya.echo.common.clients.TrackerClient
import dev.brahmkshatriya.echo.common.helpers.ExtensionType
import dev.brahmkshatriya.echo.common.helpers.ImportType
import dev.brahmkshatriya.echo.common.helpers.Injectable
import dev.brahmkshatriya.echo.common.models.Metadata
import dev.brahmkshatriya.echo.extensions.plugger.AndroidPluginLoader
import dev.brahmkshatriya.echo.extensions.plugger.ApkFileManifestParser
import dev.brahmkshatriya.echo.extensions.plugger.ApkManifestParser
import dev.brahmkshatriya.echo.extensions.plugger.ApkPluginSource
import dev.brahmkshatriya.echo.extensions.plugger.FileChangeListener
import dev.brahmkshatriya.echo.extensions.plugger.FilePluginSource
import dev.brahmkshatriya.echo.extensions.plugger.LazyPluginRepo
import dev.brahmkshatriya.echo.extensions.plugger.LazyPluginRepoImpl
import dev.brahmkshatriya.echo.extensions.plugger.LazyRepoComposer
import dev.brahmkshatriya.echo.extensions.plugger.PackageChangeListener
import dev.brahmkshatriya.echo.extensions.plugger.catchLazy
import dev.brahmkshatriya.echo.utils.getSettings
import kotlinx.coroutines.flow.MutableStateFlow
import tel.jeelpa.plugger.utils.mapState
import java.io.File

sealed class ExtensionRepo<T : ExtensionClient>(
private val context: Context,
private val listener: PackageChangeListener,
private val fileChangeListener: FileChangeListener,
private vararg val repo: Pair<Metadata, Lazy<Result<T>>>
) : LazyPluginRepo<Metadata, T> {
private vararg val repo: Pair<Metadata, Injectable<T>>
) : InjectablePluginRepo<Metadata, T> {
abstract val type: ExtensionType

private val composed by lazy {
val loader = AndroidPluginLoader<T>(context)
val dir = context.getPluginFileDir(type)
val apkFilePluginRepo = LazyPluginRepoImpl(
val apkFilePluginRepo = InjectablePluginRepoImpl(
FilePluginSource(dir, fileChangeListener.scope, fileChangeListener.getFlow(type)),
ApkFileManifestParser(context.packageManager, ApkManifestParser(ImportType.File)),
loader,
)
val appPluginRepo = LazyPluginRepoImpl(
val appPluginRepo = InjectablePluginRepoImpl(
ApkPluginSource(listener, context, "$FEATURE${type.feature}"),
ApkManifestParser(ImportType.App),
loader
)
val builtInRepo = BuiltInRepo(repo.toList())
LazyRepoComposer(builtInRepo, appPluginRepo, apkFilePluginRepo)
InjectableRepoComposer(context, type, builtInRepo, appPluginRepo, apkFilePluginRepo)
}

private fun injected() = composed.getAllPlugins().mapState { list ->
list.map {
runCatching {
val plugin = it.getOrThrow()
val (metadata, resultLazy) = plugin
metadata to catchLazy {
val instance = resultLazy.value.getOrThrow()
instance.setSettings(getSettings(context, type, metadata))
instance
}
}
}
}

override fun getAllPlugins() = injected()
override fun getAllPlugins() = composed.getAllPlugins()

companion object {
const val FEATURE = "dev.brahmkshatriya.echo."
Expand All @@ -75,7 +56,7 @@ class MusicExtensionRepo(
context: Context,
listener: PackageChangeListener,
fileChangeListener: FileChangeListener,
vararg repo: Pair<Metadata, Lazy<Result<ExtensionClient>>>
vararg repo: Pair<Metadata, Injectable<ExtensionClient>>
) : ExtensionRepo<ExtensionClient>(context, listener, fileChangeListener, *repo) {
override val type = ExtensionType.MUSIC
}
Expand All @@ -84,7 +65,7 @@ class TrackerExtensionRepo(
context: Context,
listener: PackageChangeListener,
fileChangeListener: FileChangeListener,
vararg repo: Pair<Metadata, Lazy<Result<TrackerClient>>>
vararg repo: Pair<Metadata, Injectable<TrackerClient>>
) : ExtensionRepo<TrackerClient>(context, listener, fileChangeListener, *repo) {
override val type = ExtensionType.TRACKER
}
Expand All @@ -93,13 +74,13 @@ class LyricsExtensionRepo(
context: Context,
listener: PackageChangeListener,
fileChangeListener: FileChangeListener,
vararg repo: Pair<Metadata, Lazy<Result<LyricsClient>>>
vararg repo: Pair<Metadata, Injectable<LyricsClient>>
) : ExtensionRepo<LyricsClient>(context, listener, fileChangeListener, *repo) {
override val type = ExtensionType.LYRICS
}

class BuiltInRepo<T : ExtensionClient>(
private val list: List<Pair<Metadata, Lazy<Result<T>>>>
) : LazyPluginRepo<Metadata, T> {
private val list: List<Pair<Metadata, Injectable<T>>>
) : InjectablePluginRepo<Metadata, T> {
override fun getAllPlugins() = MutableStateFlow(list.map { Result.success(it) })
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,30 +6,37 @@ import dev.brahmkshatriya.echo.ui.exception.AppException.Companion.toAppExceptio
import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.flow.StateFlow

suspend fun <T : ExtensionClient, R> Extension<T>.run(
suspend fun <T : ExtensionClient, R> Extension<T>.with(
throwableFlow: MutableSharedFlow<Throwable>,
block: suspend T.() -> R
block: suspend () -> R
): R? = runCatching {
block(instance.value.getOrThrow())
block()
}.getOrElse {
throwableFlow.emit(it.toAppException(this))
it.printStackTrace()
null
}

suspend fun <T : ExtensionClient, R> Extension<T>.run(
throwableFlow: MutableSharedFlow<Throwable>,
block: suspend T.() -> R
): R? = with(throwableFlow) {
block(instance.value().getOrThrow())
}

suspend inline fun <reified C, R> Extension<*>.get(
throwableFlow: MutableSharedFlow<Throwable>,
block: C.() -> R
): R? = runCatching {
val client = instance.value.getOrThrow() as? C ?: return@runCatching null
crossinline block: suspend C.() -> R
): R? = with(throwableFlow) {
val client = instance.value().getOrThrow() as? C ?: return@with null
block(client)
}.getOrElse {
throwableFlow.emit(it.toAppException(this))
it.printStackTrace()
null
}

inline fun <reified T> Extension<*>.isClient() = instance.value.getOrNull() is T
suspend inline fun <reified T> Extension<*>.inject(crossinline block: suspend T.() -> Unit) {
instance.injectSuspended { (getOrNull() as? T)?.block() }
}

suspend inline fun <reified T> Extension<*>.isClient() = instance.value().getOrNull() is T

fun StateFlow<List<Extension<*>>?>.getExtension(id: String?) =
value?.find { it.metadata.id == id }
Original file line number Diff line number Diff line change
@@ -1,15 +1,19 @@
package dev.brahmkshatriya.echo.extensions.plugger
package dev.brahmkshatriya.echo.extensions

import dev.brahmkshatriya.echo.common.helpers.Injectable
import tel.jeelpa.plugger.ManifestParser
import tel.jeelpa.plugger.PluginLoader
import tel.jeelpa.plugger.PluginRepo
import tel.jeelpa.plugger.PluginSource
import tel.jeelpa.plugger.utils.mapState

data class LazyPluginRepoImpl<TSourceInput, TMetadata, TPlugin : Any>(
interface InjectablePluginRepo<T, R> : PluginRepo<T, Injectable<R>>

data class InjectablePluginRepoImpl<TSourceInput, TMetadata, TPlugin : Any>(
private val pluginSource: PluginSource<TSourceInput>,
private val manifestParser: ManifestParser<TSourceInput, TMetadata>,
private val pluginLoader: PluginLoader<TMetadata, TPlugin>
) : LazyPluginRepo<TMetadata, TPlugin> {
) : InjectablePluginRepo<TMetadata, TPlugin> {

override fun getAllPlugins() = pluginSource.getSourceFiles().mapState { files ->
files.map {
Expand All @@ -18,7 +22,7 @@ data class LazyPluginRepoImpl<TSourceInput, TMetadata, TPlugin : Any>(
}.mapState { metadata ->
metadata.map { resultMetadata ->
resultMetadata.mapCatching {
it to catchLazy { pluginLoader.loadPlugin(it) }
it to Injectable { pluginLoader.loadPlugin(it) }
}
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
package dev.brahmkshatriya.echo.extensions

import android.content.Context
import dev.brahmkshatriya.echo.common.clients.ExtensionClient
import dev.brahmkshatriya.echo.common.helpers.ExtensionType
import dev.brahmkshatriya.echo.common.helpers.Injectable
import dev.brahmkshatriya.echo.common.models.Metadata
import dev.brahmkshatriya.echo.utils.getSettings
import tel.jeelpa.plugger.utils.combineStates
import tel.jeelpa.plugger.utils.mapState

class InjectableRepoComposer<TPlugin : ExtensionClient>(
private val context: Context,
private val type: ExtensionType,
private vararg val repos: InjectablePluginRepo<Metadata, TPlugin>
) : InjectablePluginRepo<Metadata, TPlugin> {
override fun getAllPlugins() = repos.map { it.getAllPlugins() }
.reduce { a, b -> combineStates(a, b) { x, y -> x + y } }
.mapState { list ->
list.forEach { it.getOrNull()?.run { second.injected(first) } }
list.groupBy { it.getOrNull()?.first?.id }.map { entry ->
entry.value.minBy {
it.getOrNull()?.first?.importType?.ordinal ?: Int.MAX_VALUE
}
}
}

private fun Injectable<TPlugin>.injected(metadata: Metadata) = inject {
getOrNull()?.setSettings(getSettings(context, type, metadata))
}
}

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ import dev.brahmkshatriya.echo.ui.exception.AppException
import dev.brahmkshatriya.echo.utils.getFromCache
import dev.brahmkshatriya.echo.utils.getSettings
import dev.brahmkshatriya.echo.utils.saveToCache
import kotlinx.coroutines.runBlocking

class UnifiedExtension(
private val context: Context
Expand All @@ -49,7 +50,7 @@ class UnifiedExtension(
)

inline fun <reified C, T> Extension<*>.client(block: C.() -> T): T = run {
val client = instance.value.getOrThrow() as? C
val client = runBlocking { instance.value().getOrThrow() } as? C
?: throw ClientException.NotSupported("$name - ${C::class.java.name}")
runCatching { client.block() }
}.getOrElse { throw AppException.Other(it, this) }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,6 @@ import dev.brahmkshatriya.echo.offline.OfflineExtension
import dev.brahmkshatriya.echo.utils.future
import dev.brahmkshatriya.echo.utils.getFromCache
import dev.brahmkshatriya.echo.utils.saveToCache
import dev.brahmkshatriya.echo.utils.toJson
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.first
Expand Down Expand Up @@ -283,9 +282,9 @@ abstract class AndroidAutoCallback(
).build()
}

private fun Extension<*>.toMediaItem(context: Context) = browsableItem(
private suspend fun Extension<*>.toMediaItem(context: Context) = browsableItem(
"$ROOT/$id", name, context.getString(R.string.extension),
instance.value.isSuccess,
instance.value().isSuccess,
metadata.iconUrl?.toImageHolder()?.toUri()
)

Expand All @@ -296,10 +295,10 @@ abstract class AndroidAutoCallback(
@OptIn(UnstableApi::class)
val errorIo = LibraryResult.ofError<ImmutableList<MediaItem>>(SessionError.ERROR_IO)

inline fun <reified C> Extension<*>.get(
suspend inline fun <reified C> Extension<*>.get(
block: C.() -> List<MediaItem>
): LibraryResult<ImmutableList<MediaItem>> = runCatching {
val client = instance.value.getOrThrow() as? C ?: return@runCatching notSupported
val client = instance.value().getOrThrow() as? C ?: return@runCatching notSupported
LibraryResult.ofItemList(
client.block(),
MediaLibraryService.LibraryParams.Builder()
Expand Down
Loading

0 comments on commit a48484f

Please sign in to comment.