From 760d1116a19821fc6717f3b6b214547a06fba3ba Mon Sep 17 00:00:00 2001 From: Aria Moradi Date: Sat, 3 Apr 2021 13:20:14 +0430 Subject: [PATCH] prepare to install apk from any source --- .../ir/armor/tachidesk/impl/Extension.kt | 71 +++++++++++++------ .../ir/armor/tachidesk/impl/ExtensionsList.kt | 4 +- .../model/database/ExtensionTable.kt | 18 +++-- .../model/dataclass/ExtensionDataClass.kt | 12 ++-- 4 files changed, 67 insertions(+), 38 deletions(-) diff --git a/server/src/main/kotlin/ir/armor/tachidesk/impl/Extension.kt b/server/src/main/kotlin/ir/armor/tachidesk/impl/Extension.kt index 46e815ea..01224532 100644 --- a/server/src/main/kotlin/ir/armor/tachidesk/impl/Extension.kt +++ b/server/src/main/kotlin/ir/armor/tachidesk/impl/Extension.kt @@ -7,6 +7,7 @@ package ir.armor.tachidesk.impl * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ +import android.net.Uri import com.googlecode.d2j.dex.Dex2jar import com.googlecode.d2j.reader.MultiDexFileReader import com.googlecode.dex2jar.tools.BaksmaliBaseDexExceptionHandler @@ -26,6 +27,7 @@ import mu.KotlinLogging import okhttp3.Request import okio.buffer import okio.sink +import org.jetbrains.exposed.sql.ResultRow import org.jetbrains.exposed.sql.deleteWhere import org.jetbrains.exposed.sql.insert import org.jetbrains.exposed.sql.select @@ -87,24 +89,51 @@ object Extension { return classToLoad.getDeclaredConstructor().newInstance() } + data class InstallableAPK( + val apkFilePath: String, + val pkgName: String + ) + suspend fun installExtension(pkgName: String): Int { logger.debug("Installing $pkgName") val extensionRecord = extensionTableAsDataClass().first { it.pkgName == pkgName } - val fileNameWithoutType = extensionRecord.apkName.substringBefore(".apk") - val dirPathWithoutType = "${ApplicationDirs.extensionsRoot}/$fileNameWithoutType" - // check if we don't have the dex file already downloaded - val jarPath = "${ApplicationDirs.extensionsRoot}/$fileNameWithoutType.jar" - if (!File(jarPath).exists()) { - val apkToDownload = ExtensionGithubApi.getApkUrl(extensionRecord) + return installAPK { + val apkURL = ExtensionGithubApi.getApkUrl(extensionRecord) + val apkName = Uri.parse(apkURL).lastPathSegment!! + val apkSavePath = "${ApplicationDirs.extensionsRoot}/$apkName" + // download apk file + downloadAPKFile(apkURL, apkSavePath) - val apkFilePath = "$dirPathWithoutType.apk" + apkSavePath + } + } + + suspend fun installAPK(fetcher: suspend () -> String): Int { + val apkFilePath = fetcher() + val apkName = Uri.parse(apkFilePath).lastPathSegment!! + + // TODO: handle the whole apk signature, and trusting bossiness + + val extensionRecord: ResultRow = transaction { + ExtensionTable.select { ExtensionTable.apkName eq apkName }.firstOrNull() + } ?: { + ExtensionTable.insert { + it[this.apkName] = apkName + } + ExtensionTable.select { ExtensionTable.apkName eq apkName }.firstOrNull()!! + }() + + val extensionId = extensionRecord[ExtensionTable.id] + + // check if we don't have the extension already installed + if (!extensionRecord[ExtensionTable.isInstalled]) { + val fileNameWithoutType = apkName.substringBefore(".apk") + + val dirPathWithoutType = "${ApplicationDirs.extensionsRoot}/$fileNameWithoutType" val jarFilePath = "$dirPathWithoutType.jar" val dexFilePath = "$dirPathWithoutType.dex" - // download apk file - downloadAPKFile(apkToDownload, apkFilePath) - val className: String = APKExtractor.extractDexAndReadClassname(apkFilePath, dexFilePath) logger.debug("Main class for extension is $className") @@ -117,18 +146,14 @@ object Extension { // update sources of the extension val instance = loadExtensionInstance(jarFilePath, className) - val extensionId = transaction { - ExtensionTable.select { ExtensionTable.pkgName eq extensionRecord.pkgName }.firstOrNull()!![ExtensionTable.id] - } - when (instance) { is HttpSource -> { // single source transaction { if (SourceTable.select { SourceTable.id eq instance.id }.count() == 0L) { SourceTable.insert { - it[this.id] = instance.id + it[id] = instance.id it[name] = instance.name - it[this.lang] = instance.lang + it[lang] = instance.lang it[extension] = extensionId } } @@ -141,9 +166,9 @@ object Extension { val httpSource = source as HttpSource if (SourceTable.select { SourceTable.id eq httpSource.id }.count() == 0L) { SourceTable.insert { - it[this.id] = httpSource.id + it[id] = httpSource.id it[name] = httpSource.name - it[this.lang] = httpSource.lang + it[lang] = httpSource.lang it[extension] = extensionId it[partOfFactorySource] = true } @@ -159,24 +184,24 @@ object Extension { // update extension info transaction { - ExtensionTable.update({ ExtensionTable.pkgName eq extensionRecord.pkgName }) { + ExtensionTable.update({ ExtensionTable.apkName eq apkName }) { it[isInstalled] = true it[classFQName] = className } } - return 201 // we downloaded successfully + return 201 // we installed successfully } else { - return 302 + return 302 // extension was already installed } } private val network: NetworkHelper by injectLazy() - private suspend fun downloadAPKFile(url: String, apkPath: String) { + private suspend fun downloadAPKFile(url: String, savePath: String) { val request = Request.Builder().url(url).build() val response = network.client.newCall(request).await() - val downloadedFile = File(apkPath) + val downloadedFile = File(savePath) downloadedFile.sink().buffer().use { sink -> response.body!!.source().use { source -> sink.writeAll(source) diff --git a/server/src/main/kotlin/ir/armor/tachidesk/impl/ExtensionsList.kt b/server/src/main/kotlin/ir/armor/tachidesk/impl/ExtensionsList.kt index 183a922f..a72b5d29 100644 --- a/server/src/main/kotlin/ir/armor/tachidesk/impl/ExtensionsList.kt +++ b/server/src/main/kotlin/ir/armor/tachidesk/impl/ExtensionsList.kt @@ -48,14 +48,14 @@ object ExtensionsList { fun extensionTableAsDataClass() = transaction { ExtensionTable.selectAll().map { ExtensionDataClass( + it[ExtensionTable.apkName], + getExtensionIconUrl(it[ExtensionTable.apkName]), it[ExtensionTable.name], it[ExtensionTable.pkgName], it[ExtensionTable.versionName], it[ExtensionTable.versionCode], it[ExtensionTable.lang], it[ExtensionTable.isNsfw], - it[ExtensionTable.apkName], - getExtensionIconUrl(it[ExtensionTable.apkName]), it[ExtensionTable.isInstalled], it[ExtensionTable.hasUpdate], it[ExtensionTable.isObsolete], diff --git a/server/src/main/kotlin/ir/armor/tachidesk/model/database/ExtensionTable.kt b/server/src/main/kotlin/ir/armor/tachidesk/model/database/ExtensionTable.kt index f5da6d69..bd870791 100644 --- a/server/src/main/kotlin/ir/armor/tachidesk/model/database/ExtensionTable.kt +++ b/server/src/main/kotlin/ir/armor/tachidesk/model/database/ExtensionTable.kt @@ -10,17 +10,21 @@ package ir.armor.tachidesk.model.database import org.jetbrains.exposed.dao.id.IntIdTable object ExtensionTable : IntIdTable() { - val name = varchar("name", 128) - val pkgName = varchar("pkg_name", 128) - val versionName = varchar("version_name", 16) - val versionCode = integer("version_code") - val lang = varchar("lang", 10) - val isNsfw = bool("is_nsfw") val apkName = varchar("apk_name", 1024) + + // default is the local source icon from tachiyomi val iconUrl = varchar("icon_url", 2048) + .default("https://raw.githubusercontent.com/tachiyomiorg/tachiyomi/64ba127e7d43b1d7e6d58a6f5c9b2bd5fe0543f7/app/src/main/res/mipmap-xxxhdpi/ic_local_source.webp") + + val name = varchar("name", 128).nullable().default(null) + val pkgName = varchar("pkg_name", 128).nullable().default(null) + val versionName = varchar("version_name", 16).nullable().default(null) + val versionCode = integer("version_code").default(0) + val lang = varchar("lang", 10).nullable().default(null) + val isNsfw = bool("is_nsfw").nullable().default(null) val isInstalled = bool("is_installed").default(false) val hasUpdate = bool("has_update").default(false) val isObsolete = bool("is_obsolete").default(false) - val classFQName = varchar("class_name", 256).default("") // fully qualified name + val classFQName = varchar("class_name", 1024).default("") // fully qualified name } diff --git a/server/src/main/kotlin/ir/armor/tachidesk/model/dataclass/ExtensionDataClass.kt b/server/src/main/kotlin/ir/armor/tachidesk/model/dataclass/ExtensionDataClass.kt index 41af6723..dfc19f5b 100644 --- a/server/src/main/kotlin/ir/armor/tachidesk/model/dataclass/ExtensionDataClass.kt +++ b/server/src/main/kotlin/ir/armor/tachidesk/model/dataclass/ExtensionDataClass.kt @@ -8,14 +8,14 @@ package ir.armor.tachidesk.model.dataclass * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ data class ExtensionDataClass( - val name: String, - val pkgName: String, - val versionName: String, - val versionCode: Int, - val lang: String, - val isNsfw: Boolean, val apkName: String, val iconUrl: String, + val name: String?, + val pkgName: String?, + val versionName: String?, + val versionCode: Int?, + val lang: String?, + val isNsfw: Boolean?, val installed: Boolean, val hasUpdate: Boolean, val obsolete: Boolean,