Merge pull request #53 from Syer10/testing_suit

Add initial testing suit
This commit is contained in:
Aria Moradi 2021-04-04 03:14:27 +04:30 committed by GitHub
commit 975a3b1828
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
14 changed files with 300 additions and 39 deletions

2
.gitignore vendored
View File

@ -7,3 +7,5 @@ gradle.properties
build build
server/src/main/resources/react server/src/main/resources/react
server/tmp/
server/tachiserver-data/

View File

@ -18,8 +18,6 @@ import java.io.File
* Manages app config. * Manages app config.
*/ */
open class ConfigManager { open class ConfigManager {
private val dataRoot by lazy { AppDirsFactory.getInstance().getUserDataDir("Tachidesk", null, null)!! }
private val generatedModules = mutableMapOf<Class<out ConfigModule>, ConfigModule>() private val generatedModules = mutableMapOf<Class<out ConfigModule>, ConfigModule>()
val config by lazy { loadConfigs() } val config by lazy { loadConfigs() }
@ -27,8 +25,6 @@ open class ConfigManager {
val loadedModules: Map<Class<out ConfigModule>, ConfigModule> val loadedModules: Map<Class<out ConfigModule>, ConfigModule>
get() = generatedModules get() = generatedModules
open val appConfigFile: String = "$dataRoot/server.conf"
val logger = KotlinLogging.logger {} val logger = KotlinLogging.logger {}
/** /**
@ -51,8 +47,8 @@ open class ConfigManager {
//Load user config //Load user config
val userConfig = val userConfig =
File(appConfigFile).let{ File(System.getProperty("ir.armor.tachidesk.rootDir"), "server.conf").let {
ConfigFactory.parseFile(it) ConfigFactory.parseFile(it)
} }
val config = ConfigFactory.empty() val config = ConfigFactory.empty()
@ -69,7 +65,7 @@ open class ConfigManager {
} }
fun registerModule(module: ConfigModule) { fun registerModule(module: ConfigModule) {
generatedModules.put(module.javaClass, module) generatedModules[module.javaClass] = module
} }
fun registerModules(vararg modules: ConfigModule) { fun registerModules(vararg modules: ConfigModule) {

View File

@ -53,6 +53,7 @@ configure(projects) {
val coroutinesVersion = "1.4.2" val coroutinesVersion = "1.4.2"
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:$coroutinesVersion") implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:$coroutinesVersion")
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-jdk8:$coroutinesVersion") implementation("org.jetbrains.kotlinx:kotlinx-coroutines-jdk8:$coroutinesVersion")
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-test:$coroutinesVersion")
// Dependency Injection // Dependency Injection

View File

@ -80,6 +80,9 @@ dependencies {
// uncomment to test extensions directly // uncomment to test extensions directly
// implementation(fileTree("lib/")) // implementation(fileTree("lib/"))
// Testing
testImplementation(kotlin("test-junit5"))
} }
val name = "ir.armor.tachidesk.Main" val name = "ir.armor.tachidesk.Main"
@ -137,6 +140,9 @@ tasks {
) )
} }
} }
test {
useJUnit()
}
} }
launch4j { //used for windows launch4j { //used for windows

View File

@ -39,12 +39,17 @@ import org.jetbrains.exposed.sql.insert
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
import org.jetbrains.exposed.sql.update import org.jetbrains.exposed.sql.update
import org.kodein.di.DI
import org.kodein.di.conf.global
import org.kodein.di.instance
import uy.kohesive.injekt.injectLazy import uy.kohesive.injekt.injectLazy
import java.io.File import java.io.File
import java.io.InputStream import java.io.InputStream
object Extension { object Extension {
private val logger = KotlinLogging.logger {} private val logger = KotlinLogging.logger {}
private val applicationDirs by DI.global.instance<ApplicationDirs>()
data class InstallableAPK( data class InstallableAPK(
val apkFilePath: String, val apkFilePath: String,
@ -58,7 +63,7 @@ object Extension {
return installAPK { return installAPK {
val apkURL = ExtensionGithubApi.getApkUrl(extensionRecord) val apkURL = ExtensionGithubApi.getApkUrl(extensionRecord)
val apkName = Uri.parse(apkURL).lastPathSegment!! val apkName = Uri.parse(apkURL).lastPathSegment!!
val apkSavePath = "${ApplicationDirs.extensionsRoot}/$apkName" val apkSavePath = "${applicationDirs.extensionsRoot}/$apkName"
// download apk file // download apk file
downloadAPKFile(apkURL, apkSavePath) downloadAPKFile(apkURL, apkSavePath)
@ -79,7 +84,7 @@ object Extension {
if (!isInstalled) { if (!isInstalled) {
val fileNameWithoutType = apkName.substringBefore(".apk") val fileNameWithoutType = apkName.substringBefore(".apk")
val dirPathWithoutType = "${ApplicationDirs.extensionsRoot}/$fileNameWithoutType" val dirPathWithoutType = "${applicationDirs.extensionsRoot}/$fileNameWithoutType"
val jarFilePath = "$dirPathWithoutType.jar" val jarFilePath = "$dirPathWithoutType.jar"
val dexFilePath = "$dirPathWithoutType.dex" val dexFilePath = "$dirPathWithoutType.dex"
@ -193,7 +198,7 @@ object Extension {
val extensionRecord = transaction { ExtensionTable.select { ExtensionTable.pkgName eq pkgName }.firstOrNull()!! } val extensionRecord = transaction { ExtensionTable.select { ExtensionTable.pkgName eq pkgName }.firstOrNull()!! }
val fileNameWithoutType = extensionRecord[ExtensionTable.apkName].substringBefore(".apk") val fileNameWithoutType = extensionRecord[ExtensionTable.apkName].substringBefore(".apk")
val jarPath = "${ApplicationDirs.extensionsRoot}/$fileNameWithoutType.jar" val jarPath = "${applicationDirs.extensionsRoot}/$fileNameWithoutType.jar"
transaction { transaction {
val extensionId = extensionRecord[ExtensionTable.id].value val extensionId = extensionRecord[ExtensionTable.id].value
@ -232,7 +237,7 @@ object Extension {
suspend fun getExtensionIcon(apkName: String): Pair<InputStream, String> { suspend fun getExtensionIcon(apkName: String): Pair<InputStream, String> {
val iconUrl = transaction { ExtensionTable.select { ExtensionTable.apkName eq apkName }.firstOrNull()!! }[ExtensionTable.iconUrl] val iconUrl = transaction { ExtensionTable.select { ExtensionTable.apkName eq apkName }.firstOrNull()!! }[ExtensionTable.iconUrl]
val saveDir = "${ApplicationDirs.extensionsRoot}/icon" val saveDir = "${applicationDirs.extensionsRoot}/icon"
return getCachedImageResponse(saveDir, apkName) { return getCachedImageResponse(saveDir, apkName) {
network.client.newCall( network.client.newCall(

View File

@ -22,6 +22,9 @@ 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
import org.jetbrains.exposed.sql.update import org.jetbrains.exposed.sql.update
import org.kodein.di.DI
import org.kodein.di.conf.global
import org.kodein.di.instance
import java.io.InputStream import java.io.InputStream
object Manga { object Manga {
@ -95,9 +98,10 @@ object Manga {
} }
} }
private val applicationDirs by DI.global.instance<ApplicationDirs>()
suspend fun getMangaThumbnail(mangaId: Int): Pair<InputStream, String> { suspend fun getMangaThumbnail(mangaId: Int): Pair<InputStream, String> {
val mangaEntry = transaction { MangaTable.select { MangaTable.id eq mangaId }.firstOrNull()!! } val mangaEntry = transaction { MangaTable.select { MangaTable.id eq mangaId }.firstOrNull()!! }
val saveDir = ApplicationDirs.thumbnailsRoot val saveDir = applicationDirs.thumbnailsRoot
val fileName = mangaId.toString() val fileName = mangaId.toString()
return getCachedImageResponse(saveDir, fileName) { return getCachedImageResponse(saveDir, fileName) {

View File

@ -21,6 +21,9 @@ import org.jetbrains.exposed.sql.and
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
import org.jetbrains.exposed.sql.update import org.jetbrains.exposed.sql.update
import org.kodein.di.DI
import org.kodein.di.conf.global
import org.kodein.di.instance
import java.io.File import java.io.File
import java.io.InputStream import java.io.InputStream
@ -73,6 +76,7 @@ object Page {
} }
// TODO: rewrite this to match tachiyomi // TODO: rewrite this to match tachiyomi
private val applicationDirs by DI.global.instance<ApplicationDirs>()
fun getChapterDir(mangaId: Int, chapterId: Int): String { fun getChapterDir(mangaId: Int, chapterId: Int): String {
val mangaEntry = transaction { MangaTable.select { MangaTable.id eq mangaId }.firstOrNull()!! } val mangaEntry = transaction { MangaTable.select { MangaTable.id eq mangaId }.firstOrNull()!! }
val sourceId = mangaEntry[MangaTable.sourceReference] val sourceId = mangaEntry[MangaTable.sourceReference]
@ -88,7 +92,7 @@ object Page {
val mangaTitle = mangaEntry[MangaTable.title] val mangaTitle = mangaEntry[MangaTable.title]
val sourceName = source.toString() val sourceName = source.toString()
val mangaDir = "${ApplicationDirs.mangaRoot}/$sourceName/$mangaTitle/$chapterDir" val mangaDir = "${applicationDirs.mangaRoot}/$sourceName/$mangaTitle/$chapterDir"
// make sure dirs exist // make sure dirs exist
File(mangaDir).mkdirs() File(mangaDir).mkdirs()
return mangaDir return mangaDir

View File

@ -16,10 +16,14 @@ import ir.armor.tachidesk.model.database.SourceTable
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
import org.kodein.di.DI
import org.kodein.di.conf.global
import org.kodein.di.instance
import java.util.concurrent.ConcurrentHashMap import java.util.concurrent.ConcurrentHashMap
object GetHttpSource { object GetHttpSource {
private val sourceCache = ConcurrentHashMap<Long, HttpSource>() private val sourceCache = ConcurrentHashMap<Long, HttpSource>()
private val applicationDirs by DI.global.instance<ApplicationDirs>()
fun getHttpSource(sourceId: Long): HttpSource { fun getHttpSource(sourceId: Long): HttpSource {
val cachedResult: HttpSource? = sourceCache[sourceId] val cachedResult: HttpSource? = sourceCache[sourceId]
@ -39,7 +43,7 @@ object GetHttpSource {
val apkName = extensionRecord[ExtensionTable.apkName] val apkName = extensionRecord[ExtensionTable.apkName]
val className = extensionRecord[ExtensionTable.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"
when (val instance = loadExtensionSources(jarPath, className)) { when (val instance = loadExtensionSources(jarPath, className)) {
is Source -> listOf(instance) is Source -> listOf(instance)

View File

@ -11,6 +11,9 @@ import ir.armor.tachidesk.server.ApplicationDirs
import mu.KotlinLogging import mu.KotlinLogging
import net.dongliu.apk.parser.ApkFile import net.dongliu.apk.parser.ApkFile
import net.dongliu.apk.parser.ApkParsers import net.dongliu.apk.parser.ApkParsers
import org.kodein.di.DI
import org.kodein.di.conf.global
import org.kodein.di.instance
import org.w3c.dom.Element import org.w3c.dom.Element
import org.w3c.dom.Node import org.w3c.dom.Node
import xyz.nulldev.androidcompat.pm.InstalledPackage.Companion.toList import xyz.nulldev.androidcompat.pm.InstalledPackage.Companion.toList
@ -31,6 +34,7 @@ import javax.xml.parsers.DocumentBuilderFactory
object PackageTools { object PackageTools {
private val logger = KotlinLogging.logger {} private val logger = KotlinLogging.logger {}
private val applicationDirs by DI.global.instance<ApplicationDirs>()
const val EXTENSION_FEATURE = "tachiyomi.extension" const val EXTENSION_FEATURE = "tachiyomi.extension"
const val METADATA_SOURCE_CLASS = "tachiyomi.extension.class" const val METADATA_SOURCE_CLASS = "tachiyomi.extension.class"
@ -65,7 +69,7 @@ object PackageTools {
.skipExceptions(false) .skipExceptions(false)
.to(jarFilePath) .to(jarFilePath)
if (handler.hasException()) { if (handler.hasException()) {
val errorFile: Path = File(ApplicationDirs.extensionsRoot).toPath().resolve("$fileNameWithoutType-error.txt") val errorFile: Path = File(applicationDirs.extensionsRoot).toPath().resolve("$fileNameWithoutType-error.txt")
logger.error( logger.error(
"Detail Error Information in File $errorFile\n" + "Detail Error Information in File $errorFile\n" +
"Please report this file to one of following link if possible (any one).\n" + "Please report this file to one of following link if possible (any one).\n" +

View File

@ -18,10 +18,14 @@ 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
import org.jetbrains.exposed.sql.transactions.transaction import org.jetbrains.exposed.sql.transactions.transaction
import org.kodein.di.DI
import org.kodein.di.conf.global
import org.kodein.di.instance
object DBMangaer { object DBMangaer {
val db by lazy { val db by lazy {
Database.connect("jdbc:h2:${ApplicationDirs.dataRoot}/database", "org.h2.Driver") val applicationDirs by DI.global.instance<ApplicationDirs>()
Database.connect("jdbc:h2:${applicationDirs.dataRoot}/database", "org.h2.Driver")
} }
} }

View File

@ -8,6 +8,7 @@ package ir.armor.tachidesk.server
* 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 ch.qos.logback.classic.Level import ch.qos.logback.classic.Level
import com.typesafe.config.Config
import eu.kanade.tachiyomi.App import eu.kanade.tachiyomi.App
import ir.armor.tachidesk.Main import ir.armor.tachidesk.Main
import ir.armor.tachidesk.model.dataclass.makeDataBaseTables import ir.armor.tachidesk.model.dataclass.makeDataBaseTables
@ -15,7 +16,11 @@ import ir.armor.tachidesk.server.util.systemTray
import mu.KotlinLogging import mu.KotlinLogging
import net.harawata.appdirs.AppDirsFactory import net.harawata.appdirs.AppDirsFactory
import org.kodein.di.DI import org.kodein.di.DI
import org.kodein.di.bind
import org.kodein.di.conf.global import org.kodein.di.conf.global
import org.kodein.di.instance
import org.kodein.di.singleton
import org.slf4j.Logger
import xyz.nulldev.androidcompat.AndroidCompat import xyz.nulldev.androidcompat.AndroidCompat
import xyz.nulldev.androidcompat.AndroidCompatInitializer import xyz.nulldev.androidcompat.AndroidCompatInitializer
import xyz.nulldev.ts.config.ConfigKodeinModule import xyz.nulldev.ts.config.ConfigKodeinModule
@ -24,8 +29,9 @@ import java.io.File
private val logger = KotlinLogging.logger {} private val logger = KotlinLogging.logger {}
object ApplicationDirs { class ApplicationDirs(
val dataRoot = AppDirsFactory.getInstance().getUserDataDir("Tachidesk", null, null)!! val dataRoot: String = AppDirsFactory.getInstance().getUserDataDir("Tachidesk", null, null)
) {
val extensionsRoot = "$dataRoot/extensions" val extensionsRoot = "$dataRoot/extensions"
val thumbnailsRoot = "$dataRoot/thumbnails" val thumbnailsRoot = "$dataRoot/thumbnails"
val mangaRoot = "$dataRoot/manga" val mangaRoot = "$dataRoot/manga"
@ -38,29 +44,48 @@ val systemTray by lazy { systemTray() }
val androidCompat by lazy { AndroidCompat() } val androidCompat by lazy { AndroidCompat() }
fun applicationSetup() { fun applicationSetup() {
// register server config val rootDir: String? = System.getProperty("ir.armor.tachidesk.rootDir")
GlobalConfigManager.registerModule( val dirs = if (rootDir != null) {
ServerConfig.register(GlobalConfigManager.config) ApplicationDirs(rootDir)
) } else {
ApplicationDirs().also {
// set application wide logging level System.setProperty("ir.armor.tachidesk.rootDir", it.dataRoot)
if (serverConfig.debugLogsEnabled) { }
(mu.KotlinLogging.logger(org.slf4j.Logger.ROOT_LOGGER_NAME).underlyingLogger as ch.qos.logback.classic.Logger).level = Level.DEBUG
} }
// make dirs we need // make dirs we need
listOf( listOf(
ApplicationDirs.dataRoot, dirs.dataRoot,
ApplicationDirs.extensionsRoot, dirs.extensionsRoot,
"${ApplicationDirs.extensionsRoot}/icon", dirs.extensionsRoot + "/icon",
ApplicationDirs.thumbnailsRoot dirs.thumbnailsRoot
).forEach { ).forEach {
File(it).mkdirs() File(it).mkdirs()
} }
GlobalConfigManager.registerModule(
ServerConfig.register(GlobalConfigManager.config)
)
// Application dirs
DI.global.addImport(DI.Module("Server") {
bind<ApplicationDirs>() with singleton { dirs }
})
// Load config API
DI.global.addImport(ConfigKodeinModule().create())
// Load Android compatibility dependencies
AndroidCompatInitializer().init()
// start app
androidCompat.startApp(App())
// set application wide logging level
if (serverConfig.debugLogsEnabled) {
(KotlinLogging.logger(Logger.ROOT_LOGGER_NAME).underlyingLogger as ch.qos.logback.classic.Logger).level = Level.DEBUG
}
// create conf file if doesn't exist // create conf file if doesn't exist
try { try {
val dataConfFile = File("${ApplicationDirs.dataRoot}/server.conf") val dataConfFile = File("${dirs.dataRoot}/server.conf")
if (!dataConfFile.exists()) { if (!dataConfFile.exists()) {
Main::class.java.getResourceAsStream("/server-reference.conf").use { input -> Main::class.java.getResourceAsStream("/server-reference.conf").use { input ->
dataConfFile.outputStream().use { output -> dataConfFile.outputStream().use { output ->
@ -75,19 +100,13 @@ fun applicationSetup() {
makeDataBaseTables() makeDataBaseTables()
// create system tray // create system tray
if (serverConfig.systemTrayEnabled) if (serverConfig.systemTrayEnabled) {
try { try {
systemTray systemTray
} catch (e: Exception) { } catch (e: Exception) {
e.printStackTrace() e.printStackTrace()
} }
}
// Load config API
DI.global.addImport(ConfigKodeinModule().create())
// Load Android compatibility dependencies
AndroidCompatInitializer().init()
// start app
androidCompat.startApp(App())
// Disable jetty's logging // Disable jetty's logging
System.setProperty("org.eclipse.jetty.util.log.announce", "false") System.setProperty("org.eclipse.jetty.util.log.announce", "false")

View File

@ -0,0 +1,187 @@
package ir.armor.tachidesk
import eu.kanade.tachiyomi.source.model.SChapter
import eu.kanade.tachiyomi.source.model.SManga
import eu.kanade.tachiyomi.source.online.HttpSource
import ir.armor.tachidesk.impl.Extension.installExtension
import ir.armor.tachidesk.impl.Extension.uninstallExtension
import ir.armor.tachidesk.impl.Extension.updateExtension
import ir.armor.tachidesk.impl.ExtensionsList.getExtensionList
import ir.armor.tachidesk.impl.Source.getSourceList
import ir.armor.tachidesk.impl.util.GetHttpSource.getHttpSource
import ir.armor.tachidesk.impl.util.awaitSingle
import ir.armor.tachidesk.model.dataclass.ExtensionDataClass
import ir.armor.tachidesk.server.applicationSetup
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.async
import kotlinx.coroutines.awaitAll
import kotlinx.coroutines.runBlocking
import kotlinx.coroutines.sync.Semaphore
import kotlinx.coroutines.sync.withPermit
import mu.KotlinLogging
import org.junit.jupiter.api.BeforeAll
import org.junit.jupiter.api.Test
import org.junit.jupiter.api.TestInstance
import rx.Observable
import java.io.File
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
class TestExtensions {
private val logger = KotlinLogging.logger {}
private lateinit var extensions: List<ExtensionDataClass>
private lateinit var sources: List<HttpSource>
private val mangaToFetch = mutableListOf<Pair<HttpSource, SManga>>()
private val failedToFetch = mutableListOf<Pair<HttpSource, Exception>>()
private val mangaFailedToFetch = mutableListOf<Triple<HttpSource, SManga, Exception>>()
private val chaptersToFetch = mutableListOf<Triple<HttpSource, SManga, SChapter>>()
private val chaptersFailedToFetch = mutableListOf<Triple<HttpSource, SManga, Throwable>>()
private val chaptersPageListFailedToFetch = mutableListOf<Triple<HttpSource, Pair<SManga, SChapter>, Exception>>()
@BeforeAll
fun setup() {
val dataRoot = File("tmp/TestDesk").absolutePath
System.setProperty("ir.armor.tachidesk.rootDir", dataRoot)
applicationSetup()
setLoggingEnabled(false)
runBlocking {
extensions = getExtensionList()
extensions.forEach {
when {
it.obsolete -> {
uninstallExtension(it.pkgName)
}
it.hasUpdate -> {
updateExtension(it.pkgName)
}
else -> {
installExtension(it.pkgName)
}
}
}
sources = getSourceList().map { getHttpSource(it.id.toLong()) }
}
setLoggingEnabled(true)
File("tmp/TestDesk/sources.txt").writeText(sources.joinToString("\n") { "${it.name} - ${it.lang.toUpperCase()} - ${it.id}" })
}
@Test
fun runTest() {
runBlocking(Dispatchers.Default) {
val semaphore = Semaphore(10)
sources.mapIndexed { index, source ->
async {
semaphore.withPermit {
logger.info { "$index - Now fetching popular manga from $source" }
try {
mangaToFetch += source to (source.fetchPopularManga(1)
.awaitSingleRepeat().mangas.firstOrNull()
?: throw Exception("Source returned no manga"))
} catch (e: Exception) {
logger.warn { "Failed to fetch popular manga from $source: ${e.message}" }
failedToFetch += source to e
}
}
}
}.awaitAll()
File("tmp/TestDesk/failedToFetch.txt").writeText(
failedToFetch.joinToString("\n") { (source, exception) ->
"${source.name} (${source.lang.toUpperCase()}, ${source.id}):" +
" ${exception.message}"
}
)
logger.info { "Now fetching manga info from ${mangaToFetch.size} sources" }
mangaToFetch.mapIndexed { index, (source, manga) ->
async {
semaphore.withPermit {
logger.info { "$index - Now fetching manga from $source" }
try {
manga.copyFrom(source.fetchMangaDetails(manga).awaitSingleRepeat())
manga.initialized = true
} catch (e: Exception) {
logger.warn {
"Failed to fetch manga info from $source for ${manga.title} (${source.mangaDetailsRequest(manga).url}): ${e.message}"
}
mangaFailedToFetch += Triple(source, manga, e)
}
}
}
}.awaitAll()
File("tmp/TestDesk/MangaFailedToFetch.txt").writeText(
mangaFailedToFetch.joinToString("\n") { (source, manga, exception) ->
"${source.name} (${source.lang}, ${source.id}):" +
" ${manga.title} (${source.mangaDetailsRequest(manga).url}):" +
" ${exception.message}"
}
)
logger.info { "Now fetching manga chapters from ${mangaToFetch.size} sources" }
mangaToFetch.filter { it.second.initialized }.mapIndexed { index, (source, manga) ->
async {
semaphore.withPermit {
logger.info { "$index - Now fetching manga chapters from $source" }
try {
chaptersToFetch += Triple(
source,
manga,
source.fetchChapterList(manga).awaitSingleRepeat().firstOrNull() ?: throw Exception("Source returned no chapters")
)
} catch (e: Exception) {
logger.warn {
"Failed to fetch manga chapters from $source for ${manga.title} (${source.mangaDetailsRequest(manga).url}): ${e.message}"
}
chaptersFailedToFetch += Triple(source, manga, e)
} catch (e: NoClassDefFoundError) {
logger.warn {
"Failed to fetch manga chapters from $source for ${manga.title} (${source.mangaDetailsRequest(manga).url}): ${e.message}"
}
chaptersFailedToFetch += Triple(source, manga, e)
}
}
}
}.awaitAll()
File("tmp/TestDesk/ChaptersFailedToFetch.txt").writeText(
chaptersFailedToFetch.joinToString("\n") { (source, manga, exception) ->
"${source.name} (${source.lang}, ${source.id}):" +
" ${manga.title} (${source.mangaDetailsRequest(manga).url}):" +
" ${exception.message}"
}
)
chaptersToFetch.mapIndexed { index, (source, manga, chapter) ->
async {
semaphore.withPermit {
logger.info { "$index - Now fetching page list from $source" }
try {
source.fetchPageList(chapter).awaitSingleRepeat()
} catch (e: Exception) {
logger.warn {
"Failed to fetch manga info from $source for ${manga.title} (${source.mangaDetailsRequest(manga).url}): ${e.message}"
}
chaptersPageListFailedToFetch += Triple(source, manga to chapter, e)
}
}
}
}.awaitAll()
File("tmp/TestDesk/ChapterPageListFailedToFetch.txt").writeText(
chaptersPageListFailedToFetch.joinToString("\n") { (source, manga, exception) ->
"${source.name} (${source.lang}, ${source.id}):" +
" ${manga.first.title} (${source.mangaDetailsRequest(manga.first).url}):" +
" ${manga.second.name} (${manga.second.url}): ${exception.message}"
}
)
}
}
private suspend fun <T> Observable<T>.awaitSingleRepeat(): T {
for (i in 1..2) {
try {
return awaitSingle()
} catch (e: Exception) {}
}
return awaitSingle()
}
}

View File

@ -0,0 +1,12 @@
package ir.armor.tachidesk
import ch.qos.logback.classic.Level
import mu.KotlinLogging
import org.slf4j.Logger
fun setLoggingEnabled(enabled: Boolean = true) {
val logger = (KotlinLogging.logger(Logger.ROOT_LOGGER_NAME).underlyingLogger as ch.qos.logback.classic.Logger)
logger.level = if (enabled) {
Level.DEBUG
} else Level.ERROR
}

View File

@ -0,0 +1,13 @@
# Server ip and port bindings
server.ip = "0.0.0.0"
server.port = 4567
# Socks5 proxy
server.socksProxy = false
server.socksProxyHost = ""
server.socksProxyPort = ""
# misc
server.debugLogsEnabled = true
server.systemTrayEnabled = false
server.initialOpenInBrowserEnabled = true