add support for installing external APK

This commit is contained in:
Aria Moradi
2021-09-09 05:22:23 +04:30
parent ae7d975a92
commit 090399f61d
8 changed files with 45 additions and 14 deletions

View File

@@ -1,8 +1,8 @@
# Server: v0.4.9-r882 + WebUI: r776 # Server: v0.4.9-r888 + WebUI: r777
## Tachidesk-Server ## Tachidesk-Server
### Public API ### Public API
#### Non-breaking changes #### Non-breaking changes
- N/A - (r888) add installing APK from external sources endpoint
#### Breaking changes #### Breaking changes
- (r877 #188 by @Syer10) `MangaDataClass.genre` changed type to `List<String>` - (r877 #188 by @Syer10) `MangaDataClass.genre` changed type to `List<String>`
@@ -11,7 +11,7 @@
- N/A - N/A
### Private API ### Private API
- N/A - (r887) the `run` task won't call `downloadWebUI` now
## Tachidesk-WebUI ## Tachidesk-WebUI
@@ -21,6 +21,7 @@
- (r774 #21 by @voltrare) `ReaderNavbar.jsx`: Swap close and retract Navbar buttons - (r774 #21 by @voltrare) `ReaderNavbar.jsx`: Swap close and retract Navbar buttons
- (r775 #23 by @voltrare) `yarn.lock`: Fixes version inconsistency after commit 9b866811b - (r775 #23 by @voltrare) `yarn.lock`: Fixes version inconsistency after commit 9b866811b
- (r776 #22 by @voltrare) add margin between Source and Extension cards, make the Search button look nicer - (r776 #22 by @voltrare) add margin between Source and Extension cards, make the Search button look nicer
- (r777) add support for installing external APK files
#### Bug fixes #### Bug fixes
- N/A - N/A

View File

@@ -14,7 +14,7 @@ const val MainClass = "suwayomi.tachidesk.MainKt"
// should be bumped with each stable release // should be bumped with each stable release
val tachideskVersion = System.getenv("ProductVersion") ?: "v0.4.9" val tachideskVersion = System.getenv("ProductVersion") ?: "v0.4.9"
val webUIRevisionTag = System.getenv("WebUIRevision") ?: "r776" val webUIRevisionTag = System.getenv("WebUIRevision") ?: "r777"
// counts commits on the the master branch // counts commits on the the master branch
val tachideskRevision = runCatching { val tachideskRevision = runCatching {

View File

@@ -26,6 +26,7 @@ object MangaAPI {
get("list", ExtensionController::list) get("list", ExtensionController::list)
get("install/:pkgName", ExtensionController::install) get("install/:pkgName", ExtensionController::install)
post("install", ExtensionController::installFile)
get("update/:pkgName", ExtensionController::update) get("update/:pkgName", ExtensionController::update)
get("uninstall/:pkgName", ExtensionController::uninstall) get("uninstall/:pkgName", ExtensionController::uninstall)

View File

@@ -33,6 +33,19 @@ object ExtensionController {
) )
} }
/** install the uploaded apk file */
fun installFile(ctx: Context) {
val uploadedFile = ctx.uploadedFile("file")!!
println(uploadedFile.filename)
ctx.json(
future {
Extension.installExternalExtension(uploadedFile.content)
}
)
}
/** update extension identified with "pkgName" */ /** update extension identified with "pkgName" */
fun update(ctx: Context) { fun update(ctx: Context) {
val pkgName = ctx.pathParam("pkgName") val pkgName = ctx.pathParam("pkgName")

View File

@@ -17,6 +17,7 @@ import mu.KotlinLogging
import okhttp3.Request import okhttp3.Request
import okio.buffer import okio.buffer
import okio.sink import okio.sink
import okio.source
import org.jetbrains.exposed.sql.deleteWhere import org.jetbrains.exposed.sql.deleteWhere
import org.jetbrains.exposed.sql.insert import org.jetbrains.exposed.sql.insert
import org.jetbrains.exposed.sql.select import org.jetbrains.exposed.sql.select
@@ -36,7 +37,6 @@ import suwayomi.tachidesk.manga.impl.util.PackageTools.dex2jar
import suwayomi.tachidesk.manga.impl.util.PackageTools.getPackageInfo import suwayomi.tachidesk.manga.impl.util.PackageTools.getPackageInfo
import suwayomi.tachidesk.manga.impl.util.PackageTools.getSignatureHash import suwayomi.tachidesk.manga.impl.util.PackageTools.getSignatureHash
import suwayomi.tachidesk.manga.impl.util.PackageTools.loadExtensionSources import suwayomi.tachidesk.manga.impl.util.PackageTools.loadExtensionSources
import suwayomi.tachidesk.manga.impl.util.PackageTools.trustedSignatures
import suwayomi.tachidesk.manga.impl.util.network.await import suwayomi.tachidesk.manga.impl.util.network.await
import suwayomi.tachidesk.manga.impl.util.storage.CachedImageResponse.getCachedImageResponse import suwayomi.tachidesk.manga.impl.util.storage.CachedImageResponse.getCachedImageResponse
import suwayomi.tachidesk.manga.model.table.ExtensionTable import suwayomi.tachidesk.manga.model.table.ExtensionTable
@@ -68,6 +68,22 @@ object Extension {
} }
} }
suspend fun installExternalExtension(inputStream: InputStream): Int {
return installAPK {
val apkName = "apkToSave.apk"
val savePath = "${applicationDirs.extensionsRoot}/$apkName"
// download apk file
val downloadedFile = File(savePath)
downloadedFile.sink().buffer().use { sink ->
inputStream.source().use { source ->
sink.writeAll(source)
sink.flush()
}
}
savePath
}
}
suspend fun installAPK(fetcher: suspend () -> String): Int { suspend fun installAPK(fetcher: suspend () -> String): Int {
val apkFilePath = fetcher() val apkFilePath = fetcher()
val apkName = File(apkFilePath).name val apkName = File(apkFilePath).name
@@ -103,12 +119,12 @@ object Extension {
val signatureHash = getSignatureHash(packageInfo) val signatureHash = getSignatureHash(packageInfo)
if (signatureHash == null) { // if (signatureHash == null) {
throw Exception("Package $pkgName isn't signed") // throw Exception("Package $pkgName isn't signed")
} else if (signatureHash !in trustedSignatures) { // } else if (signatureHash !in trustedSignatures) {
// TODO: allow trusting keys // // TODO: allow trusting keys
throw Exception("This apk is not a signed with the official tachiyomi signature") // throw Exception("This apk is not a signed with the official tachiyomi signature")
} // }
val isNsfw = packageInfo.applicationInfo.metaData.getString(METADATA_NSFW) == "1" val isNsfw = packageInfo.applicationInfo.metaData.getString(METADATA_NSFW) == "1"

View File

@@ -45,7 +45,7 @@ object PackageTools {
private const val officialSignature = "7ce04da7773d41b489f4693a366c36bcd0a11fc39b547168553c285bd7348e23" // inorichi's key private const val officialSignature = "7ce04da7773d41b489f4693a366c36bcd0a11fc39b547168553c285bd7348e23" // inorichi's key
private const val unofficialSignature = "64feb21075ba97ebc9cc981243645b331595c111cef1b0d084236a0403b00581" // ArMor's key private const val unofficialSignature = "64feb21075ba97ebc9cc981243645b331595c111cef1b0d084236a0403b00581" // ArMor's key
var trustedSignatures = mutableSetOf<String>() + officialSignature + unofficialSignature val trustedSignatures = mutableSetOf<String>() + officialSignature + unofficialSignature
/** /**
* Convert dex to jar, a wrapper for the dex2jar library * Convert dex to jar, a wrapper for the dex2jar library

View File

@@ -1,3 +1,3 @@
package suwayomi.tachidesk.manga.impl.util.lang package suwayomi.tachidesk.manga.impl.util.lang
fun List<String>.trimAll() = map { it.trim() } fun List<String>.trimAll() = map { it.trim() }

View File

@@ -41,4 +41,4 @@ data class PagedMangaListDataClass(
val hasNextPage: Boolean val hasNextPage: Boolean
) )
internal fun String?.toGenreList() = this?.split(",")?.trimAll().orEmpty() internal fun String?.toGenreList() = this?.split(",")?.trimAll().orEmpty()