beter handling of uninstalling Extensions

This commit is contained in:
Aria Moradi
2021-09-11 16:26:04 +04:30
parent 43e0763fef
commit 4498e9d444
2 changed files with 33 additions and 12 deletions

View File

@@ -28,6 +28,8 @@ import org.kodein.di.conf.global
import org.kodein.di.instance import org.kodein.di.instance
import suwayomi.tachidesk.manga.impl.extension.ExtensionsList.extensionTableAsDataClass import suwayomi.tachidesk.manga.impl.extension.ExtensionsList.extensionTableAsDataClass
import suwayomi.tachidesk.manga.impl.extension.github.ExtensionGithubApi import suwayomi.tachidesk.manga.impl.extension.github.ExtensionGithubApi
import suwayomi.tachidesk.manga.impl.util.GetHttpSource
import suwayomi.tachidesk.manga.impl.util.PackageTools
import suwayomi.tachidesk.manga.impl.util.PackageTools.EXTENSION_FEATURE import suwayomi.tachidesk.manga.impl.util.PackageTools.EXTENSION_FEATURE
import suwayomi.tachidesk.manga.impl.util.PackageTools.LIB_VERSION_MAX import suwayomi.tachidesk.manga.impl.util.PackageTools.LIB_VERSION_MAX
import suwayomi.tachidesk.manga.impl.util.PackageTools.LIB_VERSION_MIN import suwayomi.tachidesk.manga.impl.util.PackageTools.LIB_VERSION_MIN
@@ -69,7 +71,7 @@ object Extension {
} }
suspend fun installExternalExtension(inputStream: InputStream, apkName: String): Int { suspend fun installExternalExtension(inputStream: InputStream, apkName: String): Int {
return installAPK { return installAPK(true) {
val savePath = "${applicationDirs.extensionsRoot}/$apkName" val savePath = "${applicationDirs.extensionsRoot}/$apkName"
logger.debug { "Saving apk at $apkName" } logger.debug { "Saving apk at $apkName" }
// download apk file // download apk file
@@ -84,7 +86,7 @@ object Extension {
} }
} }
suspend fun installAPK(fetcher: suspend () -> String): Int { suspend fun installAPK(forceReinstall: Boolean = false, fetcher: suspend () -> String): Int {
val apkFilePath = fetcher() val apkFilePath = fetcher()
val apkName = File(apkFilePath).name val apkName = File(apkFilePath).name
@@ -94,16 +96,19 @@ object Extension {
ExtensionTable.select { ExtensionTable.apkName eq apkName }.firstOrNull() ExtensionTable.select { ExtensionTable.apkName eq apkName }.firstOrNull()
}?.get(ExtensionTable.isInstalled) ?: false }?.get(ExtensionTable.isInstalled) ?: false
if (!isInstalled) { val fileNameWithoutType = apkName.substringBefore(".apk")
val fileNameWithoutType = apkName.substringBefore(".apk")
val dirPathWithoutType = "${applicationDirs.extensionsRoot}/$fileNameWithoutType" val dirPathWithoutType = "${applicationDirs.extensionsRoot}/$fileNameWithoutType"
val jarFilePath = "$dirPathWithoutType.jar" val jarFilePath = "$dirPathWithoutType.jar"
val dexFilePath = "$dirPathWithoutType.dex" val dexFilePath = "$dirPathWithoutType.dex"
val packageInfo = getPackageInfo(apkFilePath) val packageInfo = getPackageInfo(apkFilePath)
val pkgName = packageInfo.packageName val pkgName = packageInfo.packageName
if (isInstalled && forceReinstall) {
uninstallExtension(pkgName)
}
if (!isInstalled || forceReinstall) {
if (!packageInfo.reqFeatures.orEmpty().any { it.name == EXTENSION_FEATURE }) { if (!packageInfo.reqFeatures.orEmpty().any { it.name == EXTENSION_FEATURE }) {
throw Exception("This apk is not a Tachiyomi extension") throw Exception("This apk is not a Tachiyomi extension")
} }
@@ -136,7 +141,7 @@ object Extension {
dex2jar(apkFilePath, jarFilePath, fileNameWithoutType) dex2jar(apkFilePath, jarFilePath, fileNameWithoutType)
// clean up // clean up
// File(apkFilePath).delete() File(apkFilePath).delete()
File(dexFilePath).delete() File(dexFilePath).delete()
// collect sources from the extension // collect sources from the extension
@@ -217,19 +222,30 @@ object Extension {
val extensionRecord = transaction { ExtensionTable.select { ExtensionTable.pkgName eq pkgName }.first() } val extensionRecord = transaction { ExtensionTable.select { ExtensionTable.pkgName eq pkgName }.first() }
val fileNameWithoutType = extensionRecord[ExtensionTable.apkName].substringBefore(".apk") val fileNameWithoutType = extensionRecord[ExtensionTable.apkName].substringBefore(".apk")
val jarPath = "${applicationDirs.extensionsRoot}/$fileNameWithoutType.jar" val jarPath = "${applicationDirs.extensionsRoot}/$fileNameWithoutType.jar"
transaction { val sources = transaction {
val extensionId = extensionRecord[ExtensionTable.id].value val extensionId = extensionRecord[ExtensionTable.id].value
val sources = SourceTable.select { SourceTable.extension eq extensionId }.map { it[SourceTable.id].value }
SourceTable.deleteWhere { SourceTable.extension eq extensionId } SourceTable.deleteWhere { SourceTable.extension eq extensionId }
if (extensionRecord[ExtensionTable.isObsolete]) if (extensionRecord[ExtensionTable.isObsolete])
ExtensionTable.deleteWhere { ExtensionTable.pkgName eq pkgName } ExtensionTable.deleteWhere { ExtensionTable.pkgName eq pkgName }
else else
ExtensionTable.update({ ExtensionTable.pkgName eq pkgName }) { ExtensionTable.update({ ExtensionTable.pkgName eq pkgName }) {
it[isInstalled] = false it[isInstalled] = false
} }
sources
} }
if (File(jarPath).exists()) { if (File(jarPath).exists()) {
// free up the file descriptor if exists
PackageTools.jarLoaderMap.remove(jarPath)?.close()
// clear all loaded sources
sources.forEach { GetHttpSource.invalidateSourceCache(it) }
File(jarPath).delete() File(jarPath).delete()
} }
} }

View File

@@ -136,14 +136,19 @@ object PackageTools {
} }
} }
val jarLoaderMap = mutableMapOf<String, URLClassLoader>()
/** /**
* loads the extension main class called [className] from the jar located at [jarPath] * loads the extension main class called [className] from the jar located at [jarPath]
* It may return an instance of HttpSource or SourceFactory depending on the extension. * It may return an instance of HttpSource or SourceFactory depending on the extension.
*/ */
fun loadExtensionSources(jarPath: String, className: String): Any { fun loadExtensionSources(jarPath: String, className: String): Any {
logger.debug { "loading jar with path: $jarPath" } logger.debug { "loading jar with path: $jarPath" }
val classLoader = URLClassLoader(arrayOf<URL>(URL("file:$jarPath"))) val classLoader = jarLoaderMap[jarPath] ?: URLClassLoader(arrayOf<URL>(URL("file:$jarPath")))
val classToLoad = Class.forName(className, false, classLoader) val classToLoad = Class.forName(className, false, classLoader)
jarLoaderMap[jarPath] = classLoader
return classToLoad.getDeclaredConstructor().newInstance() return classToLoad.getDeclaredConstructor().newInstance()
} }
} }