From 4498e9d4447a86cc78efabb265ebbe3e41579848 Mon Sep 17 00:00:00 2001 From: Aria Moradi Date: Sat, 11 Sep 2021 16:26:04 +0430 Subject: [PATCH] beter handling of uninstalling Extensions --- .../manga/impl/extension/Extension.kt | 38 +++++++++++++------ .../tachidesk/manga/impl/util/PackageTools.kt | 7 +++- 2 files changed, 33 insertions(+), 12 deletions(-) diff --git a/server/src/main/kotlin/suwayomi/tachidesk/manga/impl/extension/Extension.kt b/server/src/main/kotlin/suwayomi/tachidesk/manga/impl/extension/Extension.kt index 7dd481a4..c675456c 100644 --- a/server/src/main/kotlin/suwayomi/tachidesk/manga/impl/extension/Extension.kt +++ b/server/src/main/kotlin/suwayomi/tachidesk/manga/impl/extension/Extension.kt @@ -28,6 +28,8 @@ import org.kodein.di.conf.global import org.kodein.di.instance import suwayomi.tachidesk.manga.impl.extension.ExtensionsList.extensionTableAsDataClass 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.LIB_VERSION_MAX import suwayomi.tachidesk.manga.impl.util.PackageTools.LIB_VERSION_MIN @@ -69,7 +71,7 @@ object Extension { } suspend fun installExternalExtension(inputStream: InputStream, apkName: String): Int { - return installAPK { + return installAPK(true) { val savePath = "${applicationDirs.extensionsRoot}/$apkName" logger.debug { "Saving apk at $apkName" } // 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 apkName = File(apkFilePath).name @@ -94,16 +96,19 @@ object Extension { ExtensionTable.select { ExtensionTable.apkName eq apkName }.firstOrNull() }?.get(ExtensionTable.isInstalled) ?: false - if (!isInstalled) { - val fileNameWithoutType = apkName.substringBefore(".apk") + val fileNameWithoutType = apkName.substringBefore(".apk") - val dirPathWithoutType = "${applicationDirs.extensionsRoot}/$fileNameWithoutType" - val jarFilePath = "$dirPathWithoutType.jar" - val dexFilePath = "$dirPathWithoutType.dex" + val dirPathWithoutType = "${applicationDirs.extensionsRoot}/$fileNameWithoutType" + val jarFilePath = "$dirPathWithoutType.jar" + val dexFilePath = "$dirPathWithoutType.dex" - val packageInfo = getPackageInfo(apkFilePath) - val pkgName = packageInfo.packageName + val packageInfo = getPackageInfo(apkFilePath) + val pkgName = packageInfo.packageName + if (isInstalled && forceReinstall) { + uninstallExtension(pkgName) + } + if (!isInstalled || forceReinstall) { if (!packageInfo.reqFeatures.orEmpty().any { it.name == EXTENSION_FEATURE }) { throw Exception("This apk is not a Tachiyomi extension") } @@ -136,7 +141,7 @@ object Extension { dex2jar(apkFilePath, jarFilePath, fileNameWithoutType) // clean up -// File(apkFilePath).delete() + File(apkFilePath).delete() File(dexFilePath).delete() // collect sources from the extension @@ -217,19 +222,30 @@ object Extension { val extensionRecord = transaction { ExtensionTable.select { ExtensionTable.pkgName eq pkgName }.first() } val fileNameWithoutType = extensionRecord[ExtensionTable.apkName].substringBefore(".apk") val jarPath = "${applicationDirs.extensionsRoot}/$fileNameWithoutType.jar" - transaction { + val sources = transaction { 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 } + if (extensionRecord[ExtensionTable.isObsolete]) ExtensionTable.deleteWhere { ExtensionTable.pkgName eq pkgName } else ExtensionTable.update({ ExtensionTable.pkgName eq pkgName }) { it[isInstalled] = false } + + sources } 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() } } diff --git a/server/src/main/kotlin/suwayomi/tachidesk/manga/impl/util/PackageTools.kt b/server/src/main/kotlin/suwayomi/tachidesk/manga/impl/util/PackageTools.kt index 63708ab2..1da66e38 100644 --- a/server/src/main/kotlin/suwayomi/tachidesk/manga/impl/util/PackageTools.kt +++ b/server/src/main/kotlin/suwayomi/tachidesk/manga/impl/util/PackageTools.kt @@ -136,14 +136,19 @@ object PackageTools { } } + val jarLoaderMap = mutableMapOf() + /** * 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. */ fun loadExtensionSources(jarPath: String, className: String): Any { logger.debug { "loading jar with path: $jarPath" } - val classLoader = URLClassLoader(arrayOf(URL("file:$jarPath"))) + val classLoader = jarLoaderMap[jarPath] ?: URLClassLoader(arrayOf(URL("file:$jarPath"))) val classToLoad = Class.forName(className, false, classLoader) + + jarLoaderMap[jarPath] = classLoader + return classToLoad.getDeclaredConstructor().newInstance() } }