refactor & support for extension update: Backend

This commit is contained in:
Aria Moradi 2021-03-29 00:35:21 +04:30
parent b1b1abad1d
commit 077bbc3c38
34 changed files with 244 additions and 252 deletions

View File

@ -11,13 +11,14 @@ package eu.kanade.tachiyomi.extension.api
// import eu.kanade.tachiyomi.data.preference.PreferencesHelper // import eu.kanade.tachiyomi.data.preference.PreferencesHelper
import eu.kanade.tachiyomi.extension.model.Extension import eu.kanade.tachiyomi.extension.model.Extension
import eu.kanade.tachiyomi.extension.util.ExtensionLoader import eu.kanade.tachiyomi.extension.util.ExtensionLoader
import ir.armor.tachidesk.database.dataclass.ExtensionDataClass import ir.armor.tachidesk.model.dataclass.ExtensionDataClass
// import kotlinx.coroutines.Dispatchers // import kotlinx.coroutines.Dispatchers
// import kotlinx.coroutines.withContext // import kotlinx.coroutines.withContext
import kotlinx.serialization.json.JsonArray import kotlinx.serialization.json.JsonArray
import kotlinx.serialization.json.int import kotlinx.serialization.json.int
import kotlinx.serialization.json.jsonObject import kotlinx.serialization.json.jsonObject
import kotlinx.serialization.json.jsonPrimitive import kotlinx.serialization.json.jsonPrimitive
// import uy.kohesive.injekt.injectLazy // import uy.kohesive.injekt.injectLazy
internal class ExtensionGithubApi { internal class ExtensionGithubApi {
@ -84,7 +85,9 @@ internal class ExtensionGithubApi {
} }
companion object { companion object {
const val BASE_URL = "https://raw.githubusercontent.com/" // const val BASE_URL = "https://raw.githubusercontent.com"
const val REPO_URL_PREFIX = "${BASE_URL}inorichi/tachiyomi-extensions/repo" // const val REPO_URL_PREFIX = "${BASE_URL}/tachiyomiorg/tachiyomi-extensions/repo"
const val BASE_URL = "http://127.0.0.1:8000"
const val REPO_URL_PREFIX = "$BASE_URL/repo"
} }
} }

View File

@ -23,7 +23,7 @@ interface ExtensionGithubService {
.addNetworkInterceptor { chain -> .addNetworkInterceptor { chain ->
val originalResponse = chain.proceed(chain.request()) val originalResponse = chain.proceed(chain.request())
originalResponse.newBuilder() originalResponse.newBuilder()
.header("Content-Encoding", "gzip") // .header("Content-Encoding", "gzip")
.header("Content-Type", "application/json") .header("Content-Type", "application/json")
.build() .build()
} }
@ -41,6 +41,7 @@ interface ExtensionGithubService {
} }
} }
@GET("${ExtensionGithubApi.REPO_URL_PREFIX}/index.json.gz") // @GET("${ExtensionGithubApi.REPO_URL_PREFIX}/index.json.gz")
@GET("${ExtensionGithubApi.REPO_URL_PREFIX}/index.json")
suspend fun getRepo(): JsonArray suspend fun getRepo(): JsonArray
} }

View File

@ -1,28 +0,0 @@
package ir.armor.tachidesk.database.entity
/*
* Copyright (C) Contributors to the Suwayomi project
*
* This Source Code Form is subject to the terms of the Mozilla Public
* 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 ir.armor.tachidesk.database.table.ExtensionTable
import org.jetbrains.exposed.dao.IntEntity
import org.jetbrains.exposed.dao.IntEntityClass
import org.jetbrains.exposed.dao.id.EntityID
class ExtensionEntity(id: EntityID<Int>) : IntEntity(id) {
companion object : IntEntityClass<ExtensionEntity>(ExtensionTable)
var name by ExtensionTable.name
var pkgName by ExtensionTable.pkgName
var versionName by ExtensionTable.versionName
var versionCode by ExtensionTable.versionCode
var lang by ExtensionTable.lang
var isNsfw by ExtensionTable.isNsfw
var apkName by ExtensionTable.apkName
var iconUrl by ExtensionTable.iconUrl
var installed by ExtensionTable.installed
var classFQName by ExtensionTable.classFQName
}

View File

@ -1,30 +0,0 @@
package ir.armor.tachidesk.database.entity
/*
* Copyright (C) Contributors to the Suwayomi project
*
* This Source Code Form is subject to the terms of the Mozilla Public
* 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 ir.armor.tachidesk.database.table.MangaTable
import org.jetbrains.exposed.dao.IntEntity
import org.jetbrains.exposed.dao.IntEntityClass
import org.jetbrains.exposed.dao.id.EntityID
class MangaEntity(id: EntityID<Int>) : IntEntity(id) {
companion object : IntEntityClass<MangaEntity>(MangaTable)
var url by MangaTable.url
var title by MangaTable.title
var initialized by MangaTable.initialized
var artist by MangaTable.artist
var author by MangaTable.author
var description by MangaTable.description
var genre by MangaTable.genre
var status by MangaTable.status
var thumbnail_url by MangaTable.thumbnail_url
var sourceReference by MangaEntity referencedOn MangaTable.sourceReference
}

View File

@ -1,24 +0,0 @@
package ir.armor.tachidesk.database.entity
/*
* Copyright (C) Contributors to the Suwayomi project
*
* This Source Code Form is subject to the terms of the Mozilla Public
* 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 ir.armor.tachidesk.database.table.SourceTable
import org.jetbrains.exposed.dao.EntityClass
import org.jetbrains.exposed.dao.LongEntity
import org.jetbrains.exposed.dao.id.EntityID
class SourceEntity(id: EntityID<Long>) : LongEntity(id) {
companion object : EntityClass<Long, SourceEntity>(SourceTable, null)
var sourceId by SourceTable.id
var name by SourceTable.name
var lang by SourceTable.lang
var extension by ExtensionEntity referencedOn SourceTable.extension
var partOfFactorySource by SourceTable.partOfFactorySource
var positionInFactorySource by SourceTable.positionInFactorySource
}

View File

@ -7,10 +7,10 @@ package ir.armor.tachidesk.impl
* License, v. 2.0. If a copy of the MPL was not distributed with this * 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/. */ * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
import ir.armor.tachidesk.database.dataclass.CategoryDataClass import ir.armor.tachidesk.model.database.CategoryMangaTable
import ir.armor.tachidesk.database.table.CategoryMangaTable import ir.armor.tachidesk.model.database.CategoryTable
import ir.armor.tachidesk.database.table.CategoryTable import ir.armor.tachidesk.model.database.toDataClass
import ir.armor.tachidesk.database.table.toDataClass import ir.armor.tachidesk.model.dataclass.CategoryDataClass
import org.jetbrains.exposed.sql.SortOrder import org.jetbrains.exposed.sql.SortOrder
import org.jetbrains.exposed.sql.deleteWhere import org.jetbrains.exposed.sql.deleteWhere
import org.jetbrains.exposed.sql.insert import org.jetbrains.exposed.sql.insert

View File

@ -7,12 +7,12 @@ package ir.armor.tachidesk.impl
* License, v. 2.0. If a copy of the MPL was not distributed with this * 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/. */ * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
import ir.armor.tachidesk.database.dataclass.CategoryDataClass import ir.armor.tachidesk.model.database.CategoryMangaTable
import ir.armor.tachidesk.database.dataclass.MangaDataClass import ir.armor.tachidesk.model.database.CategoryTable
import ir.armor.tachidesk.database.table.CategoryMangaTable import ir.armor.tachidesk.model.database.MangaTable
import ir.armor.tachidesk.database.table.CategoryTable import ir.armor.tachidesk.model.database.toDataClass
import ir.armor.tachidesk.database.table.MangaTable import ir.armor.tachidesk.model.dataclass.CategoryDataClass
import ir.armor.tachidesk.database.table.toDataClass import ir.armor.tachidesk.model.dataclass.MangaDataClass
import org.jetbrains.exposed.sql.SortOrder import org.jetbrains.exposed.sql.SortOrder
import org.jetbrains.exposed.sql.and import org.jetbrains.exposed.sql.and
import org.jetbrains.exposed.sql.deleteWhere import org.jetbrains.exposed.sql.deleteWhere

View File

@ -9,10 +9,10 @@ package ir.armor.tachidesk.impl
import eu.kanade.tachiyomi.source.model.SChapter import eu.kanade.tachiyomi.source.model.SChapter
import eu.kanade.tachiyomi.source.model.SManga import eu.kanade.tachiyomi.source.model.SManga
import ir.armor.tachidesk.database.dataclass.ChapterDataClass import ir.armor.tachidesk.model.database.ChapterTable
import ir.armor.tachidesk.database.table.ChapterTable import ir.armor.tachidesk.model.database.MangaTable
import ir.armor.tachidesk.database.table.MangaTable import ir.armor.tachidesk.model.database.PageTable
import ir.armor.tachidesk.database.table.PageTable import ir.armor.tachidesk.model.dataclass.ChapterDataClass
import org.jetbrains.exposed.sql.and import org.jetbrains.exposed.sql.and
import org.jetbrains.exposed.sql.insert import org.jetbrains.exposed.sql.insert
import org.jetbrains.exposed.sql.insertAndGetId import org.jetbrains.exposed.sql.insertAndGetId

View File

@ -15,9 +15,9 @@ import eu.kanade.tachiyomi.network.GET
import eu.kanade.tachiyomi.network.NetworkHelper import eu.kanade.tachiyomi.network.NetworkHelper
import eu.kanade.tachiyomi.source.SourceFactory import eu.kanade.tachiyomi.source.SourceFactory
import eu.kanade.tachiyomi.source.online.HttpSource import eu.kanade.tachiyomi.source.online.HttpSource
import ir.armor.tachidesk.database.table.ExtensionTable
import ir.armor.tachidesk.database.table.SourceTable
import ir.armor.tachidesk.impl.util.APKExtractor import ir.armor.tachidesk.impl.util.APKExtractor
import ir.armor.tachidesk.model.database.ExtensionTable
import ir.armor.tachidesk.model.database.SourceTable
import ir.armor.tachidesk.server.applicationDirs import ir.armor.tachidesk.server.applicationDirs
import kotlinx.coroutines.runBlocking import kotlinx.coroutines.runBlocking
import mu.KotlinLogging import mu.KotlinLogging
@ -71,10 +71,10 @@ private fun dex2jar(dexFile: String, jarFile: String, fileNameWithoutType: Strin
} }
} }
fun installAPK(apkName: String): Int { fun installExtension(pkgName: String): Int {
logger.debug("Installing $apkName") logger.debug("Installing $pkgName")
val extensionRecord = getExtensionList(true).first { it.apkName == apkName } val extensionRecord = extensionTableAsDataClass().first { it.pkgName == pkgName }
val fileNameWithoutType = apkName.substringBefore(".apk") val fileNameWithoutType = extensionRecord.apkName.substringBefore(".apk")
val dirPathWithoutType = "${applicationDirs.extensionsRoot}/$fileNameWithoutType" val dirPathWithoutType = "${applicationDirs.extensionsRoot}/$fileNameWithoutType"
// check if we don't have the dex file already downloaded // check if we don't have the dex file already downloaded
@ -145,7 +145,7 @@ fun installAPK(apkName: String): Int {
// update extension info // update extension info
transaction { transaction {
ExtensionTable.update({ ExtensionTable.name eq extensionRecord.name }) { ExtensionTable.update({ ExtensionTable.name eq extensionRecord.name }) {
it[installed] = true it[isInstalled] = true
it[classFQName] = className it[classFQName] = className
} }
} }
@ -168,18 +168,18 @@ private fun downloadAPKFile(url: String, apkPath: String) {
sink.close() sink.close()
} }
fun removeExtension(apkName: String) { fun removeExtension(pkgName: String) {
logger.debug("Uninstalling $apkName") logger.debug("Uninstalling $pkgName")
val extensionRecord = getExtensionList(true).first { it.apkName == apkName } val extensionRecord = extensionTableAsDataClass().first { it.pkgName == pkgName }
val fileNameWithoutType = apkName.substringBefore(".apk") val fileNameWithoutType = extensionRecord.apkName.substringBefore(".apk")
val jarPath = "${applicationDirs.extensionsRoot}/$fileNameWithoutType.jar" val jarPath = "${applicationDirs.extensionsRoot}/$fileNameWithoutType.jar"
transaction { transaction {
val extensionId = ExtensionTable.select { ExtensionTable.name eq extensionRecord.name }.first()[ExtensionTable.id] val extensionId = ExtensionTable.select { ExtensionTable.name eq extensionRecord.name }.first()[ExtensionTable.id]
SourceTable.deleteWhere { SourceTable.extension eq extensionId } SourceTable.deleteWhere { SourceTable.extension eq extensionId }
ExtensionTable.update({ ExtensionTable.name eq extensionRecord.name }) { ExtensionTable.update({ ExtensionTable.name eq extensionRecord.name }) {
it[ExtensionTable.installed] = false it[isInstalled] = false
} }
} }
@ -188,6 +188,24 @@ fun removeExtension(apkName: String) {
} }
} }
fun updateExtension(pkgName: String): Int {
val targetExtension = ExtensionListData.updateMap.remove(pkgName)!!
removeExtension(pkgName)
transaction {
ExtensionTable.update({ ExtensionTable.pkgName eq pkgName }) {
it[name] = targetExtension.name
it[versionName] = targetExtension.versionName
it[versionCode] = targetExtension.versionCode
it[lang] = targetExtension.lang
it[isNsfw] = targetExtension.isNsfw
it[apkName] = targetExtension.apkName
it[iconUrl] = targetExtension.iconUrl
it[hasUpdate] = false
}
}
return installExtension(pkgName)
}
val network: NetworkHelper by injectLazy() val network: NetworkHelper by injectLazy()
fun getExtensionIcon(apkName: String): Pair<InputStream, String> { fun getExtensionIcon(apkName: String): Pair<InputStream, String> {

View File

@ -9,86 +9,122 @@ package ir.armor.tachidesk.impl
import eu.kanade.tachiyomi.extension.api.ExtensionGithubApi import eu.kanade.tachiyomi.extension.api.ExtensionGithubApi
import eu.kanade.tachiyomi.extension.model.Extension import eu.kanade.tachiyomi.extension.model.Extension
import ir.armor.tachidesk.database.dataclass.ExtensionDataClass import ir.armor.tachidesk.model.database.ExtensionTable
import ir.armor.tachidesk.database.table.ExtensionTable import ir.armor.tachidesk.model.dataclass.ExtensionDataClass
import kotlinx.coroutines.runBlocking import kotlinx.coroutines.runBlocking
import mu.KotlinLogging import mu.KotlinLogging
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
import org.jetbrains.exposed.sql.selectAll import org.jetbrains.exposed.sql.selectAll
import org.jetbrains.exposed.sql.transactions.transaction import org.jetbrains.exposed.sql.transactions.transaction
import org.jetbrains.exposed.sql.update import org.jetbrains.exposed.sql.update
import java.util.concurrent.ConcurrentHashMap
private val logger = KotlinLogging.logger {} private val logger = KotlinLogging.logger {}
private object Data { object ExtensionListData {
var lastExtensionCheck: Long = 0 var lastUpdateCheck: Long = 0
var updateMap = ConcurrentHashMap<String, Extension.Available>()
} }
private fun extensionDatabaseIsEmtpy(): Boolean { fun getExtensionList(): List<ExtensionDataClass> {
return transaction {
return@transaction ExtensionTable.selectAll().count() == 0L
}
}
fun getExtensionList(offline: Boolean = false): List<ExtensionDataClass> {
// update if 60 seconds has passed or requested offline and database is empty // update if 60 seconds has passed or requested offline and database is empty
if (Data.lastExtensionCheck + 60 * 1000 < System.currentTimeMillis() || (offline && extensionDatabaseIsEmtpy())) { if (ExtensionListData.lastUpdateCheck + 60 * 1000 < System.currentTimeMillis()) {
logger.debug("Getting extensions list from the internet") logger.debug("Getting extensions list from the internet")
Data.lastExtensionCheck = System.currentTimeMillis() ExtensionListData.lastUpdateCheck = System.currentTimeMillis()
var foundExtensions: List<Extension.Available>
runBlocking { runBlocking {
val api = ExtensionGithubApi() val foundExtensions = ExtensionGithubApi().findExtensions()
foundExtensions = api.findExtensions() updateExtensionDatabase(foundExtensions)
transaction {
foundExtensions.forEach { foundExtension ->
val extensionRecord = ExtensionTable.select { ExtensionTable.name eq foundExtension.name }.firstOrNull()
if (extensionRecord != null) {
// update the record
ExtensionTable.update({ ExtensionTable.name eq foundExtension.name }) {
it[name] = foundExtension.name
it[pkgName] = foundExtension.pkgName
it[versionName] = foundExtension.versionName
it[versionCode] = foundExtension.versionCode
it[lang] = foundExtension.lang
it[isNsfw] = foundExtension.isNsfw
it[apkName] = foundExtension.apkName
it[iconUrl] = foundExtension.iconUrl
}
} else {
// insert new record
ExtensionTable.insert {
it[name] = foundExtension.name
it[pkgName] = foundExtension.pkgName
it[versionName] = foundExtension.versionName
it[versionCode] = foundExtension.versionCode
it[lang] = foundExtension.lang
it[isNsfw] = foundExtension.isNsfw
it[apkName] = foundExtension.apkName
it[iconUrl] = foundExtension.iconUrl
}
}
}
}
} }
} else { } else {
logger.debug("used cached extension list") logger.debug("used cached extension list")
} }
return transaction { return extensionTableAsDataClass()
return@transaction ExtensionTable.selectAll().map { }
ExtensionDataClass(
it[ExtensionTable.name], fun extensionTableAsDataClass() = transaction {
it[ExtensionTable.pkgName], return@transaction ExtensionTable.selectAll().map {
it[ExtensionTable.versionName], ExtensionDataClass(
it[ExtensionTable.versionCode], it[ExtensionTable.name],
it[ExtensionTable.lang], it[ExtensionTable.pkgName],
it[ExtensionTable.isNsfw], it[ExtensionTable.versionName],
it[ExtensionTable.apkName], it[ExtensionTable.versionCode],
getExtensionIconUrl(it[ExtensionTable.apkName]), it[ExtensionTable.lang],
it[ExtensionTable.installed], it[ExtensionTable.isNsfw],
it[ExtensionTable.classFQName] it[ExtensionTable.apkName],
) getExtensionIconUrl(it[ExtensionTable.apkName]),
it[ExtensionTable.isInstalled],
it[ExtensionTable.hasUpdate],
it[ExtensionTable.isObsolete],
)
}
}
private fun updateExtensionDatabase(foundExtensions: List<Extension.Available>) {
transaction {
foundExtensions.forEach { foundExtension ->
val extensionRecord = ExtensionTable.select { ExtensionTable.pkgName eq foundExtension.pkgName }.firstOrNull()
if (extensionRecord != null) {
if (extensionRecord[ExtensionTable.isInstalled]) {
if (foundExtension.versionCode > extensionRecord[ExtensionTable.versionCode]) {
// there is an update
ExtensionTable.update({ ExtensionTable.pkgName eq foundExtension.pkgName }) {
it[hasUpdate] = true
}
ExtensionListData.updateMap.putIfAbsent(foundExtension.pkgName, foundExtension)
} else if (foundExtension.versionCode < extensionRecord[ExtensionTable.versionCode]) {
// some how the user installed an invalid version
ExtensionTable.update({ ExtensionTable.pkgName eq foundExtension.pkgName }) {
it[isObsolete] = true
}
} else {
// the two are equal
// NOOP
}
} else {
// extension is not installed so we can overwrite the data without a care
ExtensionTable.update({ ExtensionTable.pkgName eq foundExtension.pkgName }) {
it[name] = foundExtension.name
it[versionName] = foundExtension.versionName
it[versionCode] = foundExtension.versionCode
it[lang] = foundExtension.lang
it[isNsfw] = foundExtension.isNsfw
it[apkName] = foundExtension.apkName
it[iconUrl] = foundExtension.iconUrl
}
}
} else {
// insert new record
ExtensionTable.insert {
it[name] = foundExtension.name
it[pkgName] = foundExtension.pkgName
it[versionName] = foundExtension.versionName
it[versionCode] = foundExtension.versionCode
it[lang] = foundExtension.lang
it[isNsfw] = foundExtension.isNsfw
it[apkName] = foundExtension.apkName
it[iconUrl] = foundExtension.iconUrl
}
}
}
// deal with obsolete extensions
ExtensionTable.selectAll().forEach { extensionRecord ->
val foundExtension = foundExtensions.find { it.pkgName == extensionRecord[ExtensionTable.pkgName] }
if (foundExtension == null) {
// this extensions is obsolete
if (extensionRecord[ExtensionTable.isInstalled]) {
// is installed so we should mark it as obsolete
ExtensionTable.update({ ExtensionTable.pkgName eq extensionRecord[ExtensionTable.pkgName] }) {
it[isObsolete] = true
}
} else {
// is not installed so we can remove the record without a care
ExtensionTable.deleteWhere { ExtensionTable.pkgName eq extensionRecord[ExtensionTable.pkgName] }
}
}
} }
} }
} }

View File

@ -7,10 +7,10 @@ package ir.armor.tachidesk.impl
* License, v. 2.0. If a copy of the MPL was not distributed with this * 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/. */ * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
import ir.armor.tachidesk.database.dataclass.MangaDataClass import ir.armor.tachidesk.model.database.CategoryMangaTable
import ir.armor.tachidesk.database.table.CategoryMangaTable import ir.armor.tachidesk.model.database.MangaTable
import ir.armor.tachidesk.database.table.MangaTable import ir.armor.tachidesk.model.database.toDataClass
import ir.armor.tachidesk.database.table.toDataClass import ir.armor.tachidesk.model.dataclass.MangaDataClass
import org.jetbrains.exposed.sql.and import org.jetbrains.exposed.sql.and
import org.jetbrains.exposed.sql.deleteWhere import org.jetbrains.exposed.sql.deleteWhere
import org.jetbrains.exposed.sql.select import org.jetbrains.exposed.sql.select

View File

@ -9,9 +9,9 @@ package ir.armor.tachidesk.impl
import eu.kanade.tachiyomi.network.GET import eu.kanade.tachiyomi.network.GET
import eu.kanade.tachiyomi.source.model.SManga import eu.kanade.tachiyomi.source.model.SManga
import ir.armor.tachidesk.database.dataclass.MangaDataClass import ir.armor.tachidesk.model.database.MangaStatus
import ir.armor.tachidesk.database.table.MangaStatus import ir.armor.tachidesk.model.database.MangaTable
import ir.armor.tachidesk.database.table.MangaTable import ir.armor.tachidesk.model.dataclass.MangaDataClass
import ir.armor.tachidesk.server.applicationDirs import ir.armor.tachidesk.server.applicationDirs
import org.jetbrains.exposed.sql.select import org.jetbrains.exposed.sql.select
import org.jetbrains.exposed.sql.transactions.transaction import org.jetbrains.exposed.sql.transactions.transaction

View File

@ -8,10 +8,10 @@ package ir.armor.tachidesk.impl
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */ * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
import eu.kanade.tachiyomi.source.model.MangasPage import eu.kanade.tachiyomi.source.model.MangasPage
import ir.armor.tachidesk.database.dataclass.MangaDataClass import ir.armor.tachidesk.model.database.MangaStatus
import ir.armor.tachidesk.database.dataclass.PagedMangaListDataClass import ir.armor.tachidesk.model.database.MangaTable
import ir.armor.tachidesk.database.table.MangaStatus import ir.armor.tachidesk.model.dataclass.MangaDataClass
import ir.armor.tachidesk.database.table.MangaTable import ir.armor.tachidesk.model.dataclass.PagedMangaListDataClass
import org.jetbrains.exposed.sql.insertAndGetId import org.jetbrains.exposed.sql.insertAndGetId
import org.jetbrains.exposed.sql.select import org.jetbrains.exposed.sql.select
import org.jetbrains.exposed.sql.transactions.transaction import org.jetbrains.exposed.sql.transactions.transaction

View File

@ -9,10 +9,10 @@ package ir.armor.tachidesk.impl
import eu.kanade.tachiyomi.source.model.Page import eu.kanade.tachiyomi.source.model.Page
import eu.kanade.tachiyomi.source.online.HttpSource import eu.kanade.tachiyomi.source.online.HttpSource
import ir.armor.tachidesk.database.table.ChapterTable import ir.armor.tachidesk.model.database.ChapterTable
import ir.armor.tachidesk.database.table.MangaTable import ir.armor.tachidesk.model.database.MangaTable
import ir.armor.tachidesk.database.table.PageTable import ir.armor.tachidesk.model.database.PageTable
import ir.armor.tachidesk.database.table.SourceTable import ir.armor.tachidesk.model.database.SourceTable
import ir.armor.tachidesk.server.applicationDirs import ir.armor.tachidesk.server.applicationDirs
import org.jetbrains.exposed.sql.and import org.jetbrains.exposed.sql.and
import org.jetbrains.exposed.sql.select import org.jetbrains.exposed.sql.select

View File

@ -7,7 +7,7 @@ package ir.armor.tachidesk.impl
* License, v. 2.0. If a copy of the MPL was not distributed with this * 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/. */ * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
import ir.armor.tachidesk.database.dataclass.PagedMangaListDataClass import ir.armor.tachidesk.model.dataclass.PagedMangaListDataClass
fun sourceFilters(sourceId: Long) { fun sourceFilters(sourceId: Long) {
val source = getHttpSource(sourceId) val source = getHttpSource(sourceId)

View File

@ -9,17 +9,14 @@ package ir.armor.tachidesk.impl
import eu.kanade.tachiyomi.source.SourceFactory import eu.kanade.tachiyomi.source.SourceFactory
import eu.kanade.tachiyomi.source.online.HttpSource import eu.kanade.tachiyomi.source.online.HttpSource
import ir.armor.tachidesk.database.dataclass.SourceDataClass import ir.armor.tachidesk.model.database.ExtensionTable
import ir.armor.tachidesk.database.entity.ExtensionEntity import ir.armor.tachidesk.model.database.SourceTable
import ir.armor.tachidesk.database.entity.SourceEntity import ir.armor.tachidesk.model.dataclass.SourceDataClass
import ir.armor.tachidesk.database.table.ExtensionTable
import ir.armor.tachidesk.database.table.SourceTable
import ir.armor.tachidesk.server.applicationDirs import ir.armor.tachidesk.server.applicationDirs
import mu.KotlinLogging import mu.KotlinLogging
import org.jetbrains.exposed.sql.select import org.jetbrains.exposed.sql.select
import org.jetbrains.exposed.sql.selectAll import org.jetbrains.exposed.sql.selectAll
import org.jetbrains.exposed.sql.transactions.transaction import org.jetbrains.exposed.sql.transactions.transaction
import java.lang.NullPointerException
import java.net.URL import java.net.URL
import java.net.URLClassLoader import java.net.URLClassLoader
@ -30,8 +27,8 @@ private val extensionCache = mutableListOf<Pair<String, Any>>()
fun getHttpSource(sourceId: Long): HttpSource { fun getHttpSource(sourceId: Long): HttpSource {
val sourceRecord = transaction { val sourceRecord = transaction {
SourceEntity.findById(sourceId) SourceTable.select { SourceTable.id eq sourceId }.firstOrNull()!!
} ?: throw NullPointerException("Source with id $sourceId is not installed") }
val cachedResult: Pair<Long, HttpSource>? = sourceCache.firstOrNull { it.first == sourceId } val cachedResult: Pair<Long, HttpSource>? = sourceCache.firstOrNull { it.first == sourceId }
if (cachedResult != null) { if (cachedResult != null) {
@ -40,10 +37,10 @@ fun getHttpSource(sourceId: Long): HttpSource {
} }
val result: HttpSource = transaction { val result: HttpSource = transaction {
val extensionId = sourceRecord.extension.id.value val extensionId = sourceRecord[SourceTable.extension]
val extensionRecord = ExtensionEntity.findById(extensionId)!! val extensionRecord = ExtensionTable.select { ExtensionTable.id eq extensionId }.firstOrNull()!!
val apkName = extensionRecord.apkName val apkName = extensionRecord[ExtensionTable.apkName]
val className = extensionRecord.classFQName val className = extensionRecord[ExtensionTable.classFQName]
val jarName = apkName.substringBefore(".apk") + ".jar" val jarName = apkName.substringBefore(".apk") + ".jar"
val jarPath = "${applicationDirs.extensionsRoot}/$jarName" val jarPath = "${applicationDirs.extensionsRoot}/$jarName"
@ -60,13 +57,15 @@ fun getHttpSource(sourceId: Long): HttpSource {
val classToLoad = Class.forName(className, true, child) val classToLoad = Class.forName(className, true, child)
classToLoad.newInstance() classToLoad.newInstance()
} }
if (sourceRecord.partOfFactorySource) { if (sourceRecord[SourceTable.partOfFactorySource]) {
val positionInFactorySource = sourceRecord[SourceTable.positionInFactorySource]!!
return@transaction if (usedCached) { return@transaction if (usedCached) {
(instance as List<HttpSource>)[sourceRecord.positionInFactorySource!!] @Suppress("UNCHECKED_CAST")
(instance as List<HttpSource>)[positionInFactorySource]
} else { } else {
val list = (instance as SourceFactory).createSources() val list = (instance as SourceFactory).createSources()
extensionCache.add(Pair(jarPath, list)) extensionCache.add(Pair(jarPath, list))
list[sourceRecord.positionInFactorySource!!] as HttpSource list[positionInFactorySource] as HttpSource
} }
} else { } else {
if (!usedCached) if (!usedCached)

View File

@ -1,4 +1,4 @@
package ir.armor.tachidesk.database.table package ir.armor.tachidesk.model.database
/* /*
* Copyright (C) Contributors to the Suwayomi project * Copyright (C) Contributors to the Suwayomi project

View File

@ -1,4 +1,4 @@
package ir.armor.tachidesk.database.table package ir.armor.tachidesk.model.database
/* /*
* Copyright (C) Contributors to the Suwayomi project * Copyright (C) Contributors to the Suwayomi project
@ -7,7 +7,7 @@ package ir.armor.tachidesk.database.table
* License, v. 2.0. If a copy of the MPL was not distributed with this * 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/. */ * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
import ir.armor.tachidesk.database.dataclass.CategoryDataClass import ir.armor.tachidesk.model.dataclass.CategoryDataClass
import org.jetbrains.exposed.dao.id.IntIdTable import org.jetbrains.exposed.dao.id.IntIdTable
import org.jetbrains.exposed.sql.ResultRow import org.jetbrains.exposed.sql.ResultRow

View File

@ -1,4 +1,4 @@
package ir.armor.tachidesk.database.table package ir.armor.tachidesk.model.database
/* /*
* Copyright (C) Contributors to the Suwayomi project * Copyright (C) Contributors to the Suwayomi project

View File

@ -1,4 +1,4 @@
package ir.armor.tachidesk.database.table package ir.armor.tachidesk.model.database
/* /*
* Copyright (C) Contributors to the Suwayomi project * Copyright (C) Contributors to the Suwayomi project
@ -19,6 +19,8 @@ object ExtensionTable : IntIdTable() {
val apkName = varchar("apk_name", 1024) val apkName = varchar("apk_name", 1024)
val iconUrl = varchar("icon_url", 2048) val iconUrl = varchar("icon_url", 2048)
val installed = bool("installed").default(false) 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", 256).default("") // fully qualified name
} }

View File

@ -1,4 +1,4 @@
package ir.armor.tachidesk.database.table package ir.armor.tachidesk.model.database
/* /*
* Copyright (C) Contributors to the Suwayomi project * Copyright (C) Contributors to the Suwayomi project
@ -8,8 +8,8 @@ package ir.armor.tachidesk.database.table
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */ * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
import eu.kanade.tachiyomi.source.model.SManga import eu.kanade.tachiyomi.source.model.SManga
import ir.armor.tachidesk.database.dataclass.MangaDataClass
import ir.armor.tachidesk.impl.proxyThumbnailUrl import ir.armor.tachidesk.impl.proxyThumbnailUrl
import ir.armor.tachidesk.model.dataclass.MangaDataClass
import org.jetbrains.exposed.dao.id.IntIdTable import org.jetbrains.exposed.dao.id.IntIdTable
import org.jetbrains.exposed.sql.ResultRow import org.jetbrains.exposed.sql.ResultRow

View File

@ -1,4 +1,4 @@
package ir.armor.tachidesk.database.table package ir.armor.tachidesk.model.database
/* /*
* Copyright (C) Contributors to the Suwayomi project * Copyright (C) Contributors to the Suwayomi project

View File

@ -1,4 +1,4 @@
package ir.armor.tachidesk.database.table package ir.armor.tachidesk.model.database
/* /*
* Copyright (C) Contributors to the Suwayomi project * Copyright (C) Contributors to the Suwayomi project

View File

@ -1,4 +1,4 @@
package ir.armor.tachidesk.database.dataclass package ir.armor.tachidesk.model.dataclass
/* /*
* Copyright (C) Contributors to the Suwayomi project * Copyright (C) Contributors to the Suwayomi project

View File

@ -1,4 +1,4 @@
package ir.armor.tachidesk.database.dataclass package ir.armor.tachidesk.model.dataclass
/* /*
* Copyright (C) Contributors to the Suwayomi project * Copyright (C) Contributors to the Suwayomi project

View File

@ -1,4 +1,4 @@
package ir.armor.tachidesk.database package ir.armor.tachidesk.model.dataclass
/* /*
* Copyright (C) Contributors to the Suwayomi project * Copyright (C) Contributors to the Suwayomi project
@ -7,13 +7,13 @@ package ir.armor.tachidesk.database
* License, v. 2.0. If a copy of the MPL was not distributed with this * 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/. */ * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
import ir.armor.tachidesk.database.table.CategoryMangaTable import ir.armor.tachidesk.model.database.CategoryMangaTable
import ir.armor.tachidesk.database.table.CategoryTable import ir.armor.tachidesk.model.database.CategoryTable
import ir.armor.tachidesk.database.table.ChapterTable import ir.armor.tachidesk.model.database.ChapterTable
import ir.armor.tachidesk.database.table.ExtensionTable import ir.armor.tachidesk.model.database.ExtensionTable
import ir.armor.tachidesk.database.table.MangaTable import ir.armor.tachidesk.model.database.MangaTable
import ir.armor.tachidesk.database.table.PageTable import ir.armor.tachidesk.model.database.PageTable
import ir.armor.tachidesk.database.table.SourceTable import ir.armor.tachidesk.model.database.SourceTable
import ir.armor.tachidesk.server.applicationDirs import ir.armor.tachidesk.server.applicationDirs
import org.jetbrains.exposed.sql.Database import org.jetbrains.exposed.sql.Database
import org.jetbrains.exposed.sql.SchemaUtils import org.jetbrains.exposed.sql.SchemaUtils

View File

@ -1,4 +1,4 @@
package ir.armor.tachidesk.database.dataclass package ir.armor.tachidesk.model.dataclass
/* /*
* Copyright (C) Contributors to the Suwayomi project * Copyright (C) Contributors to the Suwayomi project
@ -16,6 +16,7 @@ data class ExtensionDataClass(
val isNsfw: Boolean, val isNsfw: Boolean,
val apkName: String, val apkName: String,
val iconUrl: String, val iconUrl: String,
val installed: Boolean, val isInstalled: Boolean,
val classFQName: String, val hasUpdate: Boolean,
val isObsolete: Boolean,
) )

View File

@ -1,4 +1,4 @@
package ir.armor.tachidesk.database.dataclass package ir.armor.tachidesk.model.dataclass
/* /*
* Copyright (C) Contributors to the Suwayomi project * Copyright (C) Contributors to the Suwayomi project
@ -7,7 +7,7 @@ package ir.armor.tachidesk.database.dataclass
* License, v. 2.0. If a copy of the MPL was not distributed with this * 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/. */ * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
import ir.armor.tachidesk.database.table.MangaStatus import ir.armor.tachidesk.model.database.MangaStatus
data class MangaDataClass( data class MangaDataClass(
val id: Int, val id: Int,

View File

@ -1,4 +1,4 @@
package ir.armor.tachidesk.database.dataclass package ir.armor.tachidesk.model.dataclass
/* /*
* Copyright (C) Contributors to the Suwayomi project * Copyright (C) Contributors to the Suwayomi project

View File

@ -1,4 +1,4 @@
package ir.armor.tachidesk.database.dataclass package ir.armor.tachidesk.model.dataclass
/* /*
* Copyright (C) Contributors to the Suwayomi project * Copyright (C) Contributors to the Suwayomi project

View File

@ -19,7 +19,7 @@ import ir.armor.tachidesk.impl.getPageImage
import ir.armor.tachidesk.impl.getSource import ir.armor.tachidesk.impl.getSource
import ir.armor.tachidesk.impl.getSourceList import ir.armor.tachidesk.impl.getSourceList
import ir.armor.tachidesk.impl.getThumbnail import ir.armor.tachidesk.impl.getThumbnail
import ir.armor.tachidesk.impl.installAPK import ir.armor.tachidesk.impl.installExtension
import ir.armor.tachidesk.impl.removeCategory import ir.armor.tachidesk.impl.removeCategory
import ir.armor.tachidesk.impl.removeExtension import ir.armor.tachidesk.impl.removeExtension
import ir.armor.tachidesk.impl.removeMangaFromCategory import ir.armor.tachidesk.impl.removeMangaFromCategory
@ -29,6 +29,7 @@ import ir.armor.tachidesk.impl.sourceFilters
import ir.armor.tachidesk.impl.sourceGlobalSearch import ir.armor.tachidesk.impl.sourceGlobalSearch
import ir.armor.tachidesk.impl.sourceSearch import ir.armor.tachidesk.impl.sourceSearch
import ir.armor.tachidesk.impl.updateCategory import ir.armor.tachidesk.impl.updateCategory
import ir.armor.tachidesk.impl.updateExtension
import ir.armor.tachidesk.server.util.openInBrowser import ir.armor.tachidesk.server.util.openInBrowser
import mu.KotlinLogging import mu.KotlinLogging
@ -69,18 +70,26 @@ fun javalinSetup() {
ctx.json(getExtensionList()) ctx.json(getExtensionList())
} }
app.get("/api/v1/extension/install/:apkName") { ctx -> app.get("/api/v1/extension/install/:pkgName") { ctx ->
val apkName = ctx.pathParam("apkName") val pkgName = ctx.pathParam("pkgName")
ctx.status( ctx.status(
installAPK(apkName) installExtension(pkgName)
) )
} }
app.get("/api/v1/extension/uninstall/:apkName") { ctx -> app.get("/api/v1/extension/update/:pkgName") { ctx ->
val apkName = ctx.pathParam("apkName") val pkgName = ctx.pathParam("pkgName")
removeExtension(apkName) ctx.status(
updateExtension(pkgName)
)
}
app.get("/api/v1/extension/uninstall/:pkgName") { ctx ->
val pkgName = ctx.pathParam("pkgName")
removeExtension(pkgName)
ctx.status(200) ctx.status(200)
} }

View File

@ -10,7 +10,7 @@ package ir.armor.tachidesk.server
import ch.qos.logback.classic.Level import ch.qos.logback.classic.Level
import eu.kanade.tachiyomi.App import eu.kanade.tachiyomi.App
import ir.armor.tachidesk.Main import ir.armor.tachidesk.Main
import ir.armor.tachidesk.database.makeDataBaseTables import ir.armor.tachidesk.model.dataclass.makeDataBaseTables
import ir.armor.tachidesk.server.util.systemTray import ir.armor.tachidesk.server.util.systemTray
import mu.KotlinLogging import mu.KotlinLogging
import net.harawata.appdirs.AppDirsFactory import net.harawata.appdirs.AppDirsFactory
@ -40,7 +40,7 @@ val androidCompat by lazy { AndroidCompat() }
fun applicationSetup() { fun applicationSetup() {
// register server config // register server config
GlobalConfigManager.registerModule( GlobalConfigManager.registerModule(
ServerConfig.register(GlobalConfigManager.config) ServerConfig.register(GlobalConfigManager.config)
) )
// set application wide logging level // set application wide logging level
@ -50,10 +50,10 @@ fun applicationSetup() {
// make dirs we need // make dirs we need
listOf( listOf(
applicationDirs.dataRoot, applicationDirs.dataRoot,
applicationDirs.extensionsRoot, applicationDirs.extensionsRoot,
"${applicationDirs.extensionsRoot}/icon", "${applicationDirs.extensionsRoot}/icon",
applicationDirs.thumbnailsRoot applicationDirs.thumbnailsRoot
).forEach { ).forEach {
File(it).mkdirs() File(it).mkdirs()
} }
@ -99,5 +99,6 @@ fun applicationSetup() {
System.getProperties()["proxySet"] = "true" System.getProperties()["proxySet"] = "true"
System.getProperties()["socksProxyHost"] = serverConfig.socksProxyHost System.getProperties()["socksProxyHost"] = serverConfig.socksProxyHost
System.getProperties()["socksProxyPort"] = serverConfig.socksProxyPort System.getProperties()["socksProxyPort"] = serverConfig.socksProxyPort
logger.info("Socks Proxy is enabled to ${serverConfig.socksProxyHost}:${serverConfig.socksProxyPort}")
} }
} }

View File

@ -49,7 +49,7 @@ interface IProps {
export default function ExtensionCard(props: IProps) { export default function ExtensionCard(props: IProps) {
const { const {
extension: { extension: {
name, lang, versionName, installed, apkName, iconUrl, name, lang, versionName, installed, pkgName, iconUrl,
}, },
notifyInstall, notifyInstall,
} = props; } = props;
@ -62,7 +62,7 @@ export default function ExtensionCard(props: IProps) {
function install() { function install() {
setInstalledState('installing'); setInstalledState('installing');
client.get(`/api/v1/extension/install/${apkName}`) client.get(`/api/v1/extension/install/${pkgName}`)
.then(() => { .then(() => {
setInstalledState('uninstall'); setInstalledState('uninstall');
notifyInstall(); notifyInstall();
@ -71,7 +71,7 @@ export default function ExtensionCard(props: IProps) {
function uninstall() { function uninstall() {
setInstalledState('uninstalling'); setInstalledState('uninstalling');
client.get(`/api/v1/extension/uninstall/${apkName}`) client.get(`/api/v1/extension/uninstall/${pkgName}`)
.then(() => { .then(() => {
// setInstalledState('install'); // setInstalledState('install');
notifyInstall(); notifyInstall();

View File

@ -7,12 +7,16 @@
interface IExtension { interface IExtension {
name: string name: string
lang: string
versionName: string
iconUrl: string
installed: boolean
apkName: string
pkgName: string pkgName: string
versionName: string
versionCode: number
lang: string
isNsfw: boolean
apkName: string
iconUrl: string
isInstalled: boolean
hasUpdate: boolean
isObsolete: boolean
} }
interface ISource { interface ISource {