mirror of
https://github.com/Suwayomi/Suwayomi-Server.git
synced 2026-07-13 15:54:35 -05:00
Improvements and fixes
This commit is contained in:
@@ -17,9 +17,6 @@ import io.github.oshai.kotlinlogging.KotlinLogging
|
||||
import net.dongliu.apk.parser.ApkFile
|
||||
import net.dongliu.apk.parser.bean.Icon
|
||||
import okhttp3.CacheControl
|
||||
import okio.buffer
|
||||
import okio.sink
|
||||
import okio.source
|
||||
import org.apache.commons.compress.archivers.zip.ZipFile
|
||||
import org.jetbrains.exposed.v1.core.eq
|
||||
import org.jetbrains.exposed.v1.jdbc.deleteWhere
|
||||
@@ -61,6 +58,8 @@ import kotlin.io.path.Path
|
||||
import kotlin.io.path.absolutePathString
|
||||
import kotlin.io.path.copyTo
|
||||
import kotlin.io.path.createDirectories
|
||||
import kotlin.io.path.createParentDirectories
|
||||
import kotlin.io.path.deleteExisting
|
||||
import kotlin.io.path.deleteIfExists
|
||||
import kotlin.io.path.deleteRecursively
|
||||
import kotlin.io.path.div
|
||||
@@ -92,7 +91,7 @@ object Extension {
|
||||
jarUrl != null -> {
|
||||
installJAR {
|
||||
val jarName = Uri.parse(jarUrl).lastPathSegment!!
|
||||
val jarSavePath = "${applicationDirs.extensionsRoot}/$jarName"
|
||||
val jarSavePath = Path(applicationDirs.tempRoot) / "extensions" / jarName
|
||||
// download jar file
|
||||
downloadExtension(jarUrl, jarSavePath)
|
||||
|
||||
@@ -103,7 +102,7 @@ object Extension {
|
||||
apkUrl != null -> {
|
||||
installAPK {
|
||||
val apkName = Uri.parse(apkUrl).lastPathSegment!!
|
||||
val apkSavePath = "${applicationDirs.extensionsRoot}/$apkName"
|
||||
val apkSavePath = Path(applicationDirs.tempRoot) / "extensions" / apkName
|
||||
// download apk file
|
||||
downloadExtension(apkUrl, apkSavePath)
|
||||
|
||||
@@ -122,20 +121,20 @@ object Extension {
|
||||
extensionName: String,
|
||||
): String {
|
||||
val copyToExtensionsRoot = {
|
||||
val rootPath = Path(applicationDirs.extensionsRoot)
|
||||
val rootPath = Path(applicationDirs.tempRoot) / "extensions"
|
||||
val downloadedFile = rootPath.resolve(extensionName).normalize()
|
||||
check(downloadedFile.startsWith(rootPath) && downloadedFile.parent == rootPath) {
|
||||
"File '$extensionName' is not a valid extension file"
|
||||
}
|
||||
logger.debug { "Saving jar at $extensionName" }
|
||||
// download jar file
|
||||
downloadedFile.outputStream().sink().buffer().use { sink ->
|
||||
inputStream.source().use { source ->
|
||||
sink.writeAll(source)
|
||||
sink.flush()
|
||||
downloadedFile.createParentDirectories()
|
||||
downloadedFile.outputStream().buffered().use { out ->
|
||||
inputStream.use {
|
||||
it.copyTo(out)
|
||||
}
|
||||
}
|
||||
downloadedFile.absolutePathString()
|
||||
downloadedFile
|
||||
}
|
||||
|
||||
return when {
|
||||
@@ -147,9 +146,9 @@ object Extension {
|
||||
|
||||
suspend fun installAPK(
|
||||
forceReinstall: Boolean = false,
|
||||
fetcher: suspend () -> String,
|
||||
fetcher: suspend () -> Path,
|
||||
): String {
|
||||
val apkFile = Path(fetcher())
|
||||
val apkFile = fetcher()
|
||||
|
||||
val packageInfo = getPackageInfo(apkFile)
|
||||
val pkgName = packageInfo.packageName
|
||||
@@ -161,14 +160,16 @@ object Extension {
|
||||
ExtensionTable.select(ExtensionTable.isInstalled).where { ExtensionTable.pkgName eq pkgName }.firstOrNull()
|
||||
}?.get(ExtensionTable.isInstalled) ?: false
|
||||
|
||||
val dirPathWithoutType = "${applicationDirs.extensionsRoot}/${apkFile.nameWithoutExtension}"
|
||||
val jarFile = Path("$dirPathWithoutType.jar")
|
||||
val extensionsRoot = Path(applicationDirs.extensionsRoot)
|
||||
val jarFile = extensionsRoot / (apkFile.nameWithoutExtension + ".jar")
|
||||
|
||||
if (isInstalled && forceReinstall) {
|
||||
uninstallExtension(pkgName)
|
||||
} else if (isInstalled) {
|
||||
apkFile.deleteExisting()
|
||||
return pkgName
|
||||
}
|
||||
|
||||
if (!isInstalled || forceReinstall) {
|
||||
if (!packageInfo.reqFeatures.orEmpty().any { it.name == EXTENSION_FEATURE }) {
|
||||
throw Exception("This apk is not a Tachiyomi extension")
|
||||
}
|
||||
@@ -224,7 +225,7 @@ object Extension {
|
||||
extractAndCacheApkIcon(apkFile, pkgName)
|
||||
|
||||
// clean up
|
||||
apkFile.deleteIfExists()
|
||||
apkFile.deleteExisting()
|
||||
|
||||
try {
|
||||
val extensionName =
|
||||
@@ -261,43 +262,62 @@ object Extension {
|
||||
}
|
||||
throw e
|
||||
}
|
||||
}
|
||||
return pkgName
|
||||
}
|
||||
|
||||
suspend fun installJAR(
|
||||
forceReinstall: Boolean = false,
|
||||
fetcher: suspend () -> String,
|
||||
fetcher: suspend () -> Path,
|
||||
): String {
|
||||
val jarFile = Path(fetcher())
|
||||
val tmpJarFile = fetcher()
|
||||
val jarFile = Path(applicationDirs.extensionsRoot) / tmpJarFile.name
|
||||
val tmpIcon = jarFile.parent / (jarFile.nameWithoutExtension + ".icon")
|
||||
|
||||
val jarZip =
|
||||
ZipFile
|
||||
.builder()
|
||||
.setPath(jarFile)
|
||||
.setPath(tmpJarFile)
|
||||
.get()
|
||||
|
||||
return jarZip.use { jarZip ->
|
||||
val manifest =
|
||||
val manifest: AndroidManifestParser.AndroidManifest
|
||||
jarZip.use { jarZip ->
|
||||
manifest =
|
||||
jarZip
|
||||
.getInputStream(jarZip.getEntry("AndroidManifest.xml"))
|
||||
.use {
|
||||
AndroidManifestParser.parse(it)
|
||||
}
|
||||
val pkgName = manifest.packageName
|
||||
try {
|
||||
ResourceArscIconParser.extractIcon(jarZip).use {
|
||||
tmpIcon.outputStream().use { out ->
|
||||
it.copyTo(out)
|
||||
}
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
logger.warn(e) { "Failed to extract icon from JAR ${manifest.packageName}" }
|
||||
}
|
||||
}
|
||||
|
||||
val pkgName: String = manifest.packageName
|
||||
|
||||
// check if we don't have the extension already installed
|
||||
// if it's installed and we want to update, it first has to be uninstalled
|
||||
val isInstalled =
|
||||
transaction {
|
||||
ExtensionTable.select(ExtensionTable.isInstalled).where { ExtensionTable.pkgName eq pkgName }.firstOrNull()
|
||||
ExtensionTable
|
||||
.select(ExtensionTable.isInstalled)
|
||||
.where { ExtensionTable.pkgName eq pkgName }
|
||||
.firstOrNull()
|
||||
}?.get(ExtensionTable.isInstalled) ?: false
|
||||
|
||||
if (isInstalled && forceReinstall) {
|
||||
uninstallExtension(pkgName)
|
||||
} else if (isInstalled) {
|
||||
tmpJarFile.deleteExisting()
|
||||
tmpIcon.deleteIfExists()
|
||||
return pkgName
|
||||
}
|
||||
|
||||
if (!isInstalled || forceReinstall) {
|
||||
if (!manifest.usesFeatures.any { it.name == EXTENSION_FEATURE }) {
|
||||
throw Exception("This apk is not a Tachiyomi extension")
|
||||
}
|
||||
@@ -311,6 +331,18 @@ object Extension {
|
||||
)
|
||||
}
|
||||
|
||||
if (tmpIcon.exists()) {
|
||||
try {
|
||||
cacheIcon(pkgName, tmpIcon.inputStream())
|
||||
} catch (e: Exception) {
|
||||
logger.warn(e) { "Failed to copy icon from JAR ${manifest.packageName}" }
|
||||
}
|
||||
}
|
||||
|
||||
tmpIcon.deleteIfExists()
|
||||
tmpJarFile.copyTo(jarFile)
|
||||
tmpJarFile.deleteExisting()
|
||||
|
||||
// TODO: allow trusting keys
|
||||
// val signatureHash = getSignatureHash(packageInfo)
|
||||
//
|
||||
@@ -348,8 +380,6 @@ object Extension {
|
||||
|
||||
logger.debug { "Main class for extension is $className" }
|
||||
|
||||
extractAndCacheJarIcon(jarZip, pkgName)
|
||||
|
||||
try {
|
||||
val extensionName =
|
||||
manifest.application.metaData.getString(METADATA_NAME)
|
||||
@@ -360,7 +390,7 @@ object Extension {
|
||||
manifest.application.metaData
|
||||
.getString(METADATA_EXTENSION_LIB)
|
||||
.takeUnless { it == "0" }
|
||||
?: manifest.versionName.substringBeforeLast('.')
|
||||
?: manifest.versionName!!.substringBeforeLast('.')
|
||||
|
||||
setupJar(
|
||||
jarFile,
|
||||
@@ -369,7 +399,7 @@ object Extension {
|
||||
extensionLibVersion,
|
||||
jarFile.name.removeSuffix(".jar") + ".apk",
|
||||
pkgName,
|
||||
manifest.versionName,
|
||||
manifest.versionName!!,
|
||||
manifest.versionCode!!,
|
||||
contentWarning,
|
||||
)
|
||||
@@ -384,9 +414,7 @@ object Extension {
|
||||
}
|
||||
throw e
|
||||
}
|
||||
}
|
||||
return@use pkgName
|
||||
}
|
||||
return pkgName
|
||||
}
|
||||
|
||||
private fun setupJar(
|
||||
@@ -499,7 +527,7 @@ object Extension {
|
||||
pkgName: String,
|
||||
inputStream: InputStream,
|
||||
) {
|
||||
val iconCacheDir = Path("${applicationDirs.extensionsRoot}/icon")
|
||||
val iconCacheDir = Path(applicationDirs.extensionsRoot) / "icon"
|
||||
iconCacheDir.createDirectories()
|
||||
clearCachedImage(iconCacheDir.absolutePathString(), pkgName)
|
||||
saveImage("$iconCacheDir/$pkgName", inputStream, null)
|
||||
@@ -517,7 +545,7 @@ object Extension {
|
||||
while (zipEntry != null) {
|
||||
if (zipEntry.name.startsWith("assets/") && !zipEntry.isDirectory) {
|
||||
val assetFile = assetsFolder / zipEntry.name
|
||||
assetFile.parent.createDirectories()
|
||||
assetFile.createParentDirectories()
|
||||
assetFile.outputStream().use { outputStream ->
|
||||
zipInputStream.copyTo(outputStream)
|
||||
}
|
||||
@@ -551,7 +579,7 @@ object Extension {
|
||||
|
||||
jarFile.deleteIfExists()
|
||||
tempJarFile.copyTo(jarFile)
|
||||
tempJarFile.deleteIfExists()
|
||||
tempJarFile.deleteExisting()
|
||||
|
||||
assetsFolder.deleteRecursively()
|
||||
}
|
||||
@@ -560,7 +588,7 @@ object Extension {
|
||||
|
||||
private suspend fun downloadExtension(
|
||||
url: String,
|
||||
savePath: String,
|
||||
savePath: Path,
|
||||
) {
|
||||
val response =
|
||||
network.client
|
||||
@@ -568,9 +596,9 @@ object Extension {
|
||||
GET(url, cache = CacheControl.FORCE_NETWORK),
|
||||
).await()
|
||||
|
||||
val downloadedFile = Path(savePath)
|
||||
savePath.createParentDirectories()
|
||||
response.body.byteStream().use {
|
||||
downloadedFile.outputStream().buffered().use { out ->
|
||||
savePath.outputStream().buffered().use { out ->
|
||||
it.copyTo(out)
|
||||
}
|
||||
}
|
||||
@@ -612,7 +640,7 @@ object Extension {
|
||||
// clear all loaded sources
|
||||
sources.forEach { GetSource.unregisterSource(it) }
|
||||
|
||||
jarPath.deleteIfExists()
|
||||
jarPath.deleteExisting()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user