mirror of
https://github.com/rebelonion/Dantotsu.git
synced 2026-01-25 03:31:03 +00:00
fix: novel extension installing
This commit is contained in:
@@ -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 {
|
||||
|
||||
/**
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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 -> ""
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user