fix: novel extension installing

This commit is contained in:
rebelonion
2024-04-21 04:31:24 -05:00
parent 3622d91886
commit e475cc5c01
9 changed files with 193 additions and 621 deletions

View File

@@ -6,6 +6,8 @@ import android.content.Intent
import android.content.IntentFilter
import androidx.core.content.ContextCompat
import ani.dantotsu.media.MediaType
import ani.dantotsu.parsers.novel.NovelExtension
import ani.dantotsu.parsers.novel.NovelLoadResult
import ani.dantotsu.util.Logger
import eu.kanade.tachiyomi.extension.anime.model.AnimeExtension
import eu.kanade.tachiyomi.extension.anime.model.AnimeLoadResult
@@ -28,6 +30,7 @@ internal class ExtensionInstallReceiver : BroadcastReceiver() {
private var animeListener: AnimeListener? = null
private var mangaListener: MangaListener? = null
private var novelListener: NovelListener? = null
private var type: MediaType? = null
/**
@@ -50,6 +53,12 @@ internal class ExtensionInstallReceiver : BroadcastReceiver() {
return this
}
fun setNovelListener(listener: NovelListener): ExtensionInstallReceiver {
this.type = MediaType.NOVEL
novelListener = listener
return this
}
/**
* Called when one of the events of the [filter] is received. When the package is an extension,
* it's loaded in background and it notifies the [listener] when finished.
@@ -92,6 +101,16 @@ internal class ExtensionInstallReceiver : BroadcastReceiver() {
}
}
MediaType.NOVEL -> {
when (val result = getNovelExtensionFromIntent(context, intent)) {
is NovelLoadResult.Success -> novelListener?.onExtensionInstalled(
result.extension
)
else -> {}
}
}
else -> {}
}
}
@@ -120,6 +139,16 @@ internal class ExtensionInstallReceiver : BroadcastReceiver() {
}
}
MediaType.NOVEL -> {
when (val result = getNovelExtensionFromIntent(context, intent)) {
is NovelLoadResult.Success -> novelListener?.onExtensionUpdated(
result.extension
)
else -> {}
}
}
else -> {}
}
}
@@ -139,6 +168,10 @@ internal class ExtensionInstallReceiver : BroadcastReceiver() {
mangaListener?.onPackageUninstalled(pkgName)
}
MediaType.NOVEL -> {
novelListener?.onPackageUninstalled(pkgName)
}
else -> {}
}
}
@@ -188,6 +221,23 @@ internal class ExtensionInstallReceiver : BroadcastReceiver() {
}.await()
}
@OptIn(DelicateCoroutinesApi::class)
private suspend fun getNovelExtensionFromIntent(
context: Context,
intent: Intent?
): NovelLoadResult {
val pkgName = getPackageNameFromIntent(intent)
if (pkgName == null) {
Logger.log("Package name not found")
return NovelLoadResult.Error(Exception("Package name not found"))
}
return GlobalScope.async(Dispatchers.Default, CoroutineStart.DEFAULT) {
ExtensionLoader.loadNovelExtensionFromPkgName(
context,
pkgName,
)
}.await()
}
/**
* Listener that receives extension installation events.
@@ -206,6 +256,12 @@ internal class ExtensionInstallReceiver : BroadcastReceiver() {
fun onPackageUninstalled(pkgName: String)
}
interface NovelListener {
fun onExtensionInstalled(extension: NovelExtension.Installed)
fun onExtensionUpdated(extension: NovelExtension.Installed)
fun onPackageUninstalled(pkgName: String)
}
companion object {
/**

View File

@@ -87,6 +87,8 @@ class ExtensionInstaller(private val context: Context) {
downloadUri.lastPathSegment
)
.setDescription(type.asText())
.setAllowedNetworkTypes(DownloadManager.Request.NETWORK_WIFI or DownloadManager.Request.NETWORK_MOBILE)
.setAllowedOverRoaming(true)
.setNotificationVisibility(DownloadManager.Request.VISIBILITY_VISIBLE_NOTIFY_COMPLETED)
val id = downloadManager.enqueue(request)

View File

@@ -6,6 +6,9 @@ import android.content.pm.PackageManager
import android.os.Build
import androidx.core.content.pm.PackageInfoCompat
import ani.dantotsu.media.MediaType
import ani.dantotsu.parsers.NovelInterface
import ani.dantotsu.parsers.novel.NovelExtension
import ani.dantotsu.parsers.novel.NovelLoadResult
import ani.dantotsu.util.Logger
import dalvik.system.PathClassLoader
import eu.kanade.domain.source.service.SourcePreferences
@@ -24,6 +27,7 @@ import eu.kanade.tachiyomi.util.system.getApplicationIcon
import kotlinx.coroutines.async
import kotlinx.coroutines.runBlocking
import uy.kohesive.injekt.injectLazy
import java.util.Locale
/**
* Class that handles the loading of the extensions. Supports two kinds of extensions:
@@ -77,6 +81,10 @@ internal object ExtensionLoader {
private const val officialSignatureManga =
"7ce04da7773d41b489f4693a366c36bcd0a11fc39b547168553c285bd7348e23"
//dan's key
private const val officialSignature =
"a3061edb369278749b8e8de810d440d38e96417bbd67bbdfc5d9d9ed475ce4a5"
/**
* List of the trusted signatures.
*/
@@ -133,6 +141,28 @@ internal object ExtensionLoader {
}
}
fun loadNovelExtensions(context: Context): List<NovelLoadResult> {
val pkgManager = context.packageManager
val installedPkgs = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
pkgManager.getInstalledPackages(PackageManager.PackageInfoFlags.of(PACKAGE_FLAGS.toLong()))
} else {
pkgManager.getInstalledPackages(PACKAGE_FLAGS)
}
val extPkgs = installedPkgs.filter { isPackageAnExtension(MediaType.NOVEL, it) }
if (extPkgs.isEmpty()) return emptyList()
// Load each extension concurrently and wait for completion
return runBlocking {
val deferred = extPkgs.map {
async { loadNovelExtension(context, it.packageName, it) }
}
deferred.map { it.await() }
}
}
/**
* Attempts to load an extension from the given package name. It checks if the extension
* contains the required feature flag before trying to load it.
@@ -167,6 +197,21 @@ internal object ExtensionLoader {
return loadMangaExtension(context, pkgName, pkgInfo)
}
fun loadNovelExtensionFromPkgName(context: Context, pkgName: String): NovelLoadResult {
val pkgInfo = try {
context.packageManager.getPackageInfo(pkgName, PACKAGE_FLAGS)
} catch (error: PackageManager.NameNotFoundException) {
// Unlikely, but the package may have been uninstalled at this point
Logger.log(error)
return NovelLoadResult.Error(error)
}
if (!isPackageAnExtension(MediaType.NOVEL, pkgInfo)) {
Logger.log("Tried to load a package that wasn't a extension ($pkgName)")
return NovelLoadResult.Error(Exception("Tried to load a package that wasn't a extension ($pkgName)"))
}
return loadNovelExtension(context, pkgName, pkgInfo)
}
/**
* Loads an extension given its package name.
*
@@ -400,17 +445,75 @@ internal object ExtensionLoader {
return MangaLoadResult.Success(extension)
}
private fun loadNovelExtension(
context: Context,
pkgName: String,
pkgInfo: PackageInfo
): NovelLoadResult {
val pkgManager = context.packageManager
val appInfo = try {
pkgManager.getApplicationInfo(pkgName, PackageManager.GET_META_DATA)
} catch (error: PackageManager.NameNotFoundException) {
// Unlikely, but the package may have been uninstalled at this point
Logger.log(error)
return NovelLoadResult.Error(error)
}
val extName =
pkgManager.getApplicationLabel(appInfo).toString().substringAfter("Tachiyomi: ")
val versionName = pkgInfo.versionName
val versionCode = PackageInfoCompat.getLongVersionCode(pkgInfo)
if (versionName.isNullOrEmpty()) {
Logger.log("Missing versionName for extension $extName")
return NovelLoadResult.Error(Exception("Missing versionName for extension $extName"))
}
val signatureHash = getSignatureHash(pkgInfo)
val classLoader = PathClassLoader(appInfo.sourceDir, null, context.classLoader)
val novelInterfaceInstance = try {
val className = appInfo.loadLabel(context.packageManager).toString()
val extensionClassName =
"some.random.novelextensions.${className.lowercase(Locale.getDefault())}.$className"
val loadedClass = classLoader.loadClass(extensionClassName)
val instance = loadedClass.getDeclaredConstructor().newInstance()
instance as? NovelInterface
} catch (e: Throwable) {
Logger.log("Extension load error: $extName")
return NovelLoadResult.Error(e as Exception)
}
val extension = NovelExtension.Installed(
name = extName,
pkgName = pkgName,
versionName = versionName,
versionCode = versionCode,
sources = listOfNotNull(novelInterfaceInstance),
isUnofficial = signatureHash != officialSignatureManga,
icon = context.getApplicationIcon(pkgName),
)
return NovelLoadResult.Success(extension)
}
/**
* Returns true if the given package is an extension.
*
* @param pkgInfo The package info of the application.
*/
private fun isPackageAnExtension(type: MediaType, pkgInfo: PackageInfo): Boolean {
return pkgInfo.reqFeatures.orEmpty().any {
it.name == when (type) {
MediaType.ANIME -> ANIME_PACKAGE
MediaType.MANGA -> MANGA_PACKAGE
else -> ""
return if (type == MediaType.NOVEL) {
pkgInfo.packageName.startsWith("some.random")
} else {
pkgInfo.reqFeatures.orEmpty().any {
it.name == when (type) {
MediaType.ANIME -> ANIME_PACKAGE
MediaType.MANGA -> MANGA_PACKAGE
else -> ""
}
}
}
}