Compare commits

...

13 Commits

Author SHA1 Message Date
Aria Moradi b4b7b5d572 bump to v0.4.6
CI Publish / Validate Gradle Wrapper (push) Successful in 12s
CI Publish / Build artifacts and release (push) Failing after 17s
2021-08-18 04:29:14 +04:30
Aria Moradi 291c2e692d clean up build.gradle files, move constants to buildSrc 2021-08-18 04:24:58 +04:30
Aria Moradi 8a9a4f21b1 remove some stuff we don't use 2021-08-18 04:06:13 +04:30
Aria Moradi cd31332b39 better download progress 2021-08-18 03:55:52 +04:30
Aria Moradi cc8d2162a0 fix compile issue 2021-08-18 02:59:07 +04:30
Aria Moradi e6313cdc67 yeet improvments from jui 2021-08-18 01:21:17 +04:30
Aria Moradi a5578a7ac7 fix compile warnings 2021-08-17 23:54:02 +04:30
Aria Moradi fcdda6406e update dependencies 2021-08-17 23:53:41 +04:30
Aria Moradi d3a6662c60 make it compile 2021-08-15 03:16:13 +04:30
Aria Moradi 5474eddf84 fix some inconsitencies 2021-08-15 02:41:23 +04:30
Aria Moradi b666cd47d4 fix shouldOverwrite 2021-08-15 00:25:08 +04:30
Aria Moradi 8a986383fe fixes #175, better webUI download task 2021-08-14 17:10:41 +04:30
Aria Moradi 9fa17f617e add anime seach functionality 2021-08-11 08:47:07 +04:30
37 changed files with 223 additions and 164 deletions
-4
View File
@@ -1,4 +0,0 @@
dependencies {
// Config API, moved to the global build.gradle
// implementation("com.typesafe:config:1.4.0")
}
+2 -11
View File
@@ -20,16 +20,9 @@ dependencies {
// Android stub library // Android stub library
implementation(fileTree("lib/")) implementation(fileTree("lib/"))
// Android JAR libs
// compileOnly( fileTree(dir: new File(rootProject.rootDir, "libs/other"), include: "*.jar")
// JSON // JSON
compileOnly("com.google.code.gson:gson:2.8.6") compileOnly("com.google.code.gson:gson:2.8.6")
// Javassist
compileOnly("org.javassist:javassist:3.27.0-GA")
// XML // XML
compileOnly(group= "xmlpull", name= "xmlpull", version= "1.1.3.1") compileOnly(group= "xmlpull", name= "xmlpull", version= "1.1.3.1")
@@ -43,10 +36,8 @@ dependencies {
compileOnly("androidx.annotation:annotation:1.2.0-alpha01") compileOnly("androidx.annotation:annotation:1.2.0-alpha01")
// substitute for duktape-android // substitute for duktape-android
// 'org.mozilla:rhino' includes some code that we don't need so use 'org.mozilla:rhino-runtime' instead implementation("org.mozilla:rhino-runtime:1.7.13") // slimmer version of 'org.mozilla:rhino'
implementation("org.mozilla:rhino-runtime:1.7.13") implementation("org.mozilla:rhino-engine:1.7.13") // provides the same interface as 'javax.script' a.k.a Nashorn
// 'org.mozilla:rhino-engine' provides the same interface as 'javax.script' a.k.a Nashorn
implementation("org.mozilla:rhino-engine:1.7.13")
// Kotlin wrapper around Java Preferences, makes certain things easier // Kotlin wrapper around Java Preferences, makes certain things easier
val multiplatformSettingsVersion = "0.7.7" val multiplatformSettingsVersion = "0.7.7"
@@ -1,5 +0,0 @@
package kotlinx.coroutines.experimental.android
import kotlinx.coroutines.GlobalScope
val UI = GlobalScope.coroutineContext
@@ -2,7 +2,6 @@ package xyz.nulldev.androidcompat
import org.kodein.di.DI import org.kodein.di.DI
import org.kodein.di.conf.global import org.kodein.di.conf.global
import xyz.nulldev.androidcompat.bytecode.ModApplier
import xyz.nulldev.androidcompat.config.ApplicationInfoConfigModule import xyz.nulldev.androidcompat.config.ApplicationInfoConfigModule
import xyz.nulldev.androidcompat.config.FilesConfigModule import xyz.nulldev.androidcompat.config.FilesConfigModule
import xyz.nulldev.androidcompat.config.SystemConfigModule import xyz.nulldev.androidcompat.config.SystemConfigModule
@@ -12,12 +11,7 @@ import xyz.nulldev.ts.config.GlobalConfigManager
* Initializes the Android compatibility module * Initializes the Android compatibility module
*/ */
class AndroidCompatInitializer { class AndroidCompatInitializer {
val modApplier by lazy { ModApplier() }
fun init() { fun init() {
modApplier.apply()
DI.global.addImport(AndroidCompatModule().create()) DI.global.addImport(AndroidCompatModule().create())
//Register config modules //Register config modules
@@ -1,22 +0,0 @@
package xyz.nulldev.androidcompat.bytecode
import javassist.CtClass
import mu.KotlinLogging
/**
* Applies Javassist modifications
*/
class ModApplier {
val logger = KotlinLogging.logger {}
fun apply() {
logger.info { "Applying Javassist mods..." }
val modifiedClasses = mutableListOf<CtClass>()
modifiedClasses.forEach {
it.toClass()
}
}
}
@@ -4,11 +4,21 @@ import java.io.InputStream
import java.io.Reader import java.io.Reader
import java.math.BigDecimal import java.math.BigDecimal
import java.net.URL import java.net.URL
import java.sql.*
import java.sql.Array import java.sql.Array
import java.sql.Blob
import java.sql.Clob
import java.sql.Date import java.sql.Date
import java.util.* import java.sql.NClob
import java.sql.Ref
import java.sql.ResultSet
import java.sql.ResultSetMetaData
import java.sql.RowId
import java.sql.SQLXML
import java.sql.Time
import java.sql.Timestamp
import java.util.Calendar
@Suppress("UNCHECKED_CAST")
class ScrollableResultSet(val parent: ResultSet) : ResultSet by parent { class ScrollableResultSet(val parent: ResultSet) : ResultSet by parent {
private val cachedContent = mutableListOf<ResultSetEntry>() private val cachedContent = mutableListOf<ResultSetEntry>()
@@ -18,7 +18,7 @@ class ServiceSupport {
private val logger = KotlinLogging.logger {} private val logger = KotlinLogging.logger {}
fun startService(context: Context, intent: Intent) { fun startService(@Suppress("UNUSED_PARAMETER") context: Context, intent: Intent) {
val name = intentToClassName(intent) val name = intentToClassName(intent)
logger.debug { "Starting service: $name" } logger.debug { "Starting service: $name" }
@@ -35,7 +35,7 @@ class ServiceSupport {
} }
} }
fun stopService(context: Context, intent: Intent) { fun stopService(@Suppress("UNUSED_PARAMETER") context: Context, intent: Intent) {
val name = intentToClassName(intent) val name = intentToClassName(intent)
stopService(name) stopService(name)
} }
@@ -25,6 +25,7 @@ object KodeinGlobalHelper {
* Get a dependency * Get a dependency
*/ */
@JvmStatic @JvmStatic
@Suppress("UNCHECKED_CAST")
fun <T : Any> instance(type: Class<T>, kodein: DI? = null): T { fun <T : Any> instance(type: Class<T>, kodein: DI? = null): T {
return when(type) { return when(type) {
AndroidFiles::class.java -> { AndroidFiles::class.java -> {
+5 -4
View File
@@ -1,8 +1,8 @@
import org.jetbrains.kotlin.gradle.tasks.KotlinCompile import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
plugins { plugins {
kotlin("jvm") version "1.4.32" kotlin("jvm") version kotlinVersion
kotlin("plugin.serialization") version "1.4.32" apply false kotlin("plugin.serialization") version kotlinVersion
} }
allprojects { allprojects {
@@ -46,12 +46,12 @@ configure(projects) {
testImplementation(kotlin("test-junit5")) testImplementation(kotlin("test-junit5"))
// coroutines // coroutines
val coroutinesVersion = "1.4.3" val coroutinesVersion = "1.5.0"
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") implementation("org.jetbrains.kotlinx:kotlinx-coroutines-test:$coroutinesVersion")
val kotlinSerializationVersion = "1.1.0" val kotlinSerializationVersion = "1.2.1"
implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:$kotlinSerializationVersion") implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:$kotlinSerializationVersion")
implementation("org.jetbrains.kotlinx:kotlinx-serialization-protobuf:$kotlinSerializationVersion") implementation("org.jetbrains.kotlinx:kotlinx-serialization-protobuf:$kotlinSerializationVersion")
@@ -80,6 +80,7 @@ configure(projects) {
implementation("net.harawata:appdirs:1.2.1") implementation("net.harawata:appdirs:1.2.1")
// dex2jar: https://github.com/DexPatcher/dex2jar/releases/tag/v2.1-20190905-lanchon // dex2jar: https://github.com/DexPatcher/dex2jar/releases/tag/v2.1-20190905-lanchon
// note: watch https://github.com/ThexXTURBOXx/dex2jar for future development
implementation("com.github.DexPatcher.dex2jar:dex-tools:v2.1-20190905-lanchon") implementation("com.github.DexPatcher.dex2jar:dex-tools:v2.1-20190905-lanchon")
// APK parser // APK parser
+11
View File
@@ -0,0 +1,11 @@
plugins {
`kotlin-dsl`
}
repositories {
mavenCentral()
}
dependencies {
implementation("net.lingala.zip4j:zip4j:2.9.0")
}
+33
View File
@@ -0,0 +1,33 @@
import java.io.BufferedReader
/*
* 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/. */
const val kotlinVersion = "1.5.21"
const val MainClass = "suwayomi.tachidesk.MainKt"
// should be bumped with each stable release
val tachideskVersion = System.getenv("ProductVersion") ?: "v0.4.6"
val webUIRevisionTag = System.getenv("WebUIRevision") ?: "r24"
// counts commit count on master
val tachideskRevision = runCatching {
System.getenv("ProductRevision") ?: Runtime
.getRuntime()
.exec("git rev-list HEAD --count")
.let { process ->
process.waitFor()
val output = process.inputStream.use {
it.bufferedReader().use(BufferedReader::readText)
}
process.destroy()
"r" + output.trim()
}
}.getOrDefault("r0")
+1 -1
View File
@@ -1,5 +1,5 @@
distributionBase=GRADLE_USER_HOME distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-7.0-all.zip distributionUrl=https\://services.gradle.org/distributions/gradle-7.1.1-all.zip
zipStoreBase=GRADLE_USER_HOME zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists zipStorePath=wrapper/dists
@@ -1 +1 @@
jre\bin\javaw "-Dsuwayomi.tachidesk.config.server.webInterface=electron" "-Dsuwayomi.tachidesk.config.server.electronPath=electron/electron.exe" -jar Tachidesk.jar jre\bin\javaw "-Dsuwayomi.tachidesk.config.server.webUIInterface=electron" "-Dsuwayomi.tachidesk.config.server.electronPath=electron/electron.exe" -jar Tachidesk.jar
+49 -47
View File
@@ -1,15 +1,15 @@
import com.github.jengelman.gradle.plugins.shadow.tasks.ShadowJar import com.github.jengelman.gradle.plugins.shadow.tasks.ShadowJar
import org.jetbrains.kotlin.gradle.tasks.KotlinCompile import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
import org.jmailen.gradle.kotlinter.tasks.FormatTask import org.jmailen.gradle.kotlinter.tasks.FormatTask
import org.jmailen.gradle.kotlinter.tasks.LintTask import org.jmailen.gradle.kotlinter.tasks.LintTask
import java.io.BufferedReader
import java.time.Instant import java.time.Instant
plugins { plugins {
application application
id("com.github.johnrengelman.shadow") version "7.0.0" id("com.github.johnrengelman.shadow") version "7.0.0"
id("org.jmailen.kotlinter") version "3.4.3" id("org.jmailen.kotlinter") version "3.4.3"
id("de.fuerstenau.buildconfig") version "1.1.8" id("com.github.gmazzo.buildconfig") version "3.0.2"
} }
repositories { repositories {
@@ -53,7 +53,6 @@ dependencies {
implementation("com.dorkbox:Utilities:1.9") implementation("com.dorkbox:Utilities:1.9")
// dependencies of Tachiyomi extensions, some are duplicate, keeping it here for reference // dependencies of Tachiyomi extensions, some are duplicate, keeping it here for reference
implementation("com.github.inorichi.injekt:injekt-core:65b0440") implementation("com.github.inorichi.injekt:injekt-core:65b0440")
implementation("com.squareup.okhttp3:okhttp:4.9.1") implementation("com.squareup.okhttp3:okhttp:4.9.1")
@@ -81,14 +80,13 @@ dependencies {
// implementation(fileTree("lib/")) // implementation(fileTree("lib/"))
} }
val MainClass = "suwayomi.tachidesk.MainKt"
application { application {
mainClass.set(MainClass) mainClass.set(MainClass)
// for testing electron // uncomment for testing electron
// applicationDefaultJvmArgs = listOf( // applicationDefaultJvmArgs = listOf(
// "-Dsuwayomi.tachidesk.webInterface=electron", // "-Dsuwayomi.tachidesk.config.server.webUIInterface=electron",
// "-Dsuwayomi.tachidesk.electronPath=/usr/bin/electron" // "-Dsuwayomi.tachidesk.config.server.electronPath=/usr/bin/electron"
// ) // )
} }
@@ -100,56 +98,40 @@ sourceSets {
} }
} }
// should be bumped with each stable release
val tachideskVersion = System.getenv("ProductVersion") ?: "v0.4.5"
val webUIRevisionTag = System.getenv("WebUIRevision") ?: "r23"
// counts commit count on master
val tachideskRevision = runCatching {
System.getenv("ProductRevision") ?: Runtime
.getRuntime()
.exec("git rev-list HEAD --count")
.let { process ->
process.waitFor()
val output = process.inputStream.use {
it.bufferedReader().use(BufferedReader::readText)
}
process.destroy()
"r" + output.trim()
}
}.getOrDefault("r0")
buildConfig { buildConfig {
clsName = "BuildConfig" className("BuildConfig")
packageName = "suwayomi.tachidesk.server" packageName("suwayomi.tachidesk.server")
useKotlinOutput()
buildConfigField("String", "NAME", rootProject.name) fun quoteWrap(obj: Any): String = """"$obj""""
buildConfigField("String", "VERSION", tachideskVersion)
buildConfigField("String", "REVISION", tachideskRevision) buildConfigField("String", "NAME", quoteWrap(rootProject.name))
buildConfigField("String", "BUILD_TYPE", if (System.getenv("ProductBuildType") == "Stable") "Stable" else "Preview") buildConfigField("String", "VERSION", quoteWrap(tachideskVersion))
buildConfigField("String", "REVISION", quoteWrap(tachideskRevision))
buildConfigField("String", "BUILD_TYPE", quoteWrap(if (System.getenv("ProductBuildType") == "Stable") "Stable" else "Preview"))
buildConfigField("long", "BUILD_TIME", Instant.now().epochSecond.toString()) buildConfigField("long", "BUILD_TIME", Instant.now().epochSecond.toString())
buildConfigField("String", "WEBUI_REPO", "https://github.com/Suwayomi/Tachidesk-WebUI-preview") buildConfigField("String", "WEBUI_REPO", quoteWrap("https://github.com/Suwayomi/Tachidesk-WebUI-preview"))
buildConfigField("String", "WEBUI_TAG", webUIRevisionTag) buildConfigField("String", "WEBUI_TAG", quoteWrap(webUIRevisionTag))
buildConfigField("String", "GITHUB", "https://github.com/Suwayomi/Tachidesk") buildConfigField("String", "GITHUB", quoteWrap("https://github.com/Suwayomi/Tachidesk"))
buildConfigField("String", "DISCORD", "https://discord.gg/DDZdqZWaHA") buildConfigField("String", "DISCORD", quoteWrap("https://discord.gg/DDZdqZWaHA"))
} }
tasks { tasks {
shadowJar { shadowJar {
manifest { manifest {
attributes( attributes(
mapOf( mapOf(
"Main-Class" to MainClass, "Main-Class" to MainClass,
"Implementation-Title" to rootProject.name, "Implementation-Title" to rootProject.name,
"Implementation-Vendor" to "The Suwayomi Project", "Implementation-Vendor" to "The Suwayomi Project",
"Specification-Version" to tachideskVersion, "Specification-Version" to tachideskVersion,
"Implementation-Version" to tachideskRevision "Implementation-Version" to tachideskRevision
) )
) )
} }
archiveBaseName.set(rootProject.name) archiveBaseName.set(rootProject.name)
@@ -159,9 +141,9 @@ tasks {
withType<KotlinCompile> { withType<KotlinCompile> {
kotlinOptions { kotlinOptions {
freeCompilerArgs = listOf( freeCompilerArgs = listOf(
"-Xopt-in=kotlin.RequiresOptIn", "-Xopt-in=kotlin.RequiresOptIn",
"-Xopt-in=kotlinx.coroutines.ExperimentalCoroutinesApi", "-Xopt-in=kotlinx.coroutines.ExperimentalCoroutinesApi",
"-Xopt-in=kotlinx.coroutines.InternalCoroutinesApi" "-Xopt-in=kotlinx.coroutines.InternalCoroutinesApi"
) )
} }
} }
@@ -175,7 +157,7 @@ tasks {
} }
named("run") { named("run") {
dependsOn("formatKotlin", "lintKotlin") dependsOn("formatKotlin", "lintKotlin", "downloadWebUI")
} }
named<Copy>("processResources") { named<Copy>("processResources") {
@@ -186,6 +168,26 @@ tasks {
register<de.undercouch.gradle.tasks.download.Download>("downloadWebUI") { register<de.undercouch.gradle.tasks.download.Download>("downloadWebUI") {
src("https://github.com/Suwayomi/Tachidesk-WebUI-preview/releases/download/$webUIRevisionTag/Tachidesk-WebUI-$webUIRevisionTag.zip") src("https://github.com/Suwayomi/Tachidesk-WebUI-preview/releases/download/$webUIRevisionTag/Tachidesk-WebUI-$webUIRevisionTag.zip")
dest("src/main/resources/WebUI.zip") dest("src/main/resources/WebUI.zip")
fun shouldOverwrite(): Boolean {
val zipPath = project.projectDir.absolutePath + "/src/main/resources/WebUI.zip"
val zipFile = net.lingala.zip4j.ZipFile(zipPath)
var shouldOverwrite = true
if (zipFile.isValidZipFile) {
val zipRevision = zipFile.getInputStream(zipFile.getFileHeader("revision")).bufferedReader().use {
it.readText().trim()
}
if (zipRevision == webUIRevisionTag)
shouldOverwrite = false
}
return shouldOverwrite
}
overwrite(shouldOverwrite())
} }
withType<LintTask> { withType<LintTask> {
@@ -54,7 +54,7 @@ abstract class AnimeHttpSource : AnimeCatalogueSource {
* Note the generated id sets the sign bit to 0. * Note the generated id sets the sign bit to 0.
*/ */
override val id by lazy { override val id by lazy {
val key = "${name.toLowerCase()}/$lang/$versionId" val key = "${name.lowercase()}/$lang/$versionId"
val bytes = MessageDigest.getInstance("MD5").digest(key.toByteArray()) val bytes = MessageDigest.getInstance("MD5").digest(key.toByteArray())
(0..7).map { bytes[it].toLong() and 0xff shl 8 * (7 - it) }.reduce(Long::or) and Long.MAX_VALUE (0..7).map { bytes[it].toLong() and 0xff shl 8 * (7 - it) }.reduce(Long::or) and Long.MAX_VALUE
} }
@@ -80,7 +80,7 @@ abstract class AnimeHttpSource : AnimeCatalogueSource {
/** /**
* Visible name of the source. * Visible name of the source.
*/ */
override fun toString() = "$name (${lang.toUpperCase()})" override fun toString() = "$name (${lang.uppercase()})"
/** /**
* Returns an observable containing a page with a list of anime. Normally it's not needed to * Returns an observable containing a page with a list of anime. Normally it's not needed to
@@ -18,6 +18,7 @@ import okhttp3.OkHttpClient
// import uy.kohesive.injekt.injectLazy // import uy.kohesive.injekt.injectLazy
import java.util.concurrent.TimeUnit import java.util.concurrent.TimeUnit
@Suppress("UNUSED_PARAMETER")
class NetworkHelper(context: Context) { class NetworkHelper(context: Context) {
// private val preferences: PreferencesHelper by injectLazy() // private val preferences: PreferencesHelper by injectLazy()
@@ -110,6 +110,7 @@ fun Call.asObservableSuccess(): Observable<Response> {
// return progressClient.newCall(request) // return progressClient.newCall(request)
// } // }
@Suppress("UNUSED_PARAMETER")
fun OkHttpClient.newCallWithProgress(request: Request, listener: ProgressListener): Call { fun OkHttpClient.newCallWithProgress(request: Request, listener: ProgressListener): Call {
val progressClient = newBuilder() val progressClient = newBuilder()
// .cache(null) // .cache(null)
@@ -55,7 +55,7 @@ abstract class HttpSource : CatalogueSource {
* Note the generated id sets the sign bit to 0. * Note the generated id sets the sign bit to 0.
*/ */
override val id by lazy { override val id by lazy {
val key = "${name.toLowerCase()}/$lang/$versionId" val key = "${name.lowercase()}/$lang/$versionId"
val bytes = MessageDigest.getInstance("MD5").digest(key.toByteArray()) val bytes = MessageDigest.getInstance("MD5").digest(key.toByteArray())
(0..7).map { bytes[it].toLong() and 0xff shl 8 * (7 - it) }.reduce(Long::or) and Long.MAX_VALUE (0..7).map { bytes[it].toLong() and 0xff shl 8 * (7 - it) }.reduce(Long::or) and Long.MAX_VALUE
} }
@@ -81,7 +81,7 @@ abstract class HttpSource : CatalogueSource {
/** /**
* Visible name of the source. * Visible name of the source.
*/ */
override fun toString() = "$name (${lang.toUpperCase()})" override fun toString() = "$name (${lang.uppercase()})"
/** /**
* Returns an observable containing a page with a list of manga. Normally it's not needed to * Returns an observable containing a page with a list of manga. Normally it's not needed to
@@ -14,6 +14,7 @@ import suwayomi.tachidesk.anime.impl.AnimeList.getAnimeList
import suwayomi.tachidesk.anime.impl.Episode.getEpisode import suwayomi.tachidesk.anime.impl.Episode.getEpisode
import suwayomi.tachidesk.anime.impl.Episode.getEpisodeList import suwayomi.tachidesk.anime.impl.Episode.getEpisodeList
import suwayomi.tachidesk.anime.impl.Episode.modifyEpisode import suwayomi.tachidesk.anime.impl.Episode.modifyEpisode
import suwayomi.tachidesk.anime.impl.Search.sourceSearch
import suwayomi.tachidesk.anime.impl.Source.getAnimeSource import suwayomi.tachidesk.anime.impl.Source.getAnimeSource
import suwayomi.tachidesk.anime.impl.Source.getSourceList import suwayomi.tachidesk.anime.impl.Source.getSourceList
import suwayomi.tachidesk.anime.impl.extension.Extension.getExtensionIcon import suwayomi.tachidesk.anime.impl.extension.Extension.getExtensionIcon
@@ -219,13 +220,13 @@ object AnimeAPI {
// ctx.json(sourceGlobalSearch(searchTerm)) // ctx.json(sourceGlobalSearch(searchTerm))
// } // }
// //
// // single source search // single source search
// app.get("/api/v1/source/:sourceId/search/:searchTerm/:pageNum") { ctx -> app.get("/api/v1/anime/source/:sourceId/search/:searchTerm/:pageNum") { ctx ->
// val sourceId = ctx.pathParam("sourceId").toLong() val sourceId = ctx.pathParam("sourceId").toLong()
// val searchTerm = ctx.pathParam("searchTerm") val searchTerm = ctx.pathParam("searchTerm")
// val pageNum = ctx.pathParam("pageNum").toInt() val pageNum = ctx.pathParam("pageNum").toInt()
// ctx.json(JavalinSetup.future { sourceSearch(sourceId, searchTerm, pageNum) }) ctx.json(future { sourceSearch(sourceId, searchTerm, pageNum) })
// } }
// //
// // source filter list // // source filter list
// app.get("/api/v1/source/:sourceId/filters/") { ctx -> // app.get("/api/v1/source/:sourceId/filters/") { ctx ->
@@ -0,0 +1,21 @@
package suwayomi.tachidesk.anime.impl
/*
* 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 suwayomi.tachidesk.anime.impl.AnimeList.processEntries
import suwayomi.tachidesk.anime.impl.util.GetAnimeHttpSource.getAnimeHttpSource
import suwayomi.tachidesk.anime.model.dataclass.PagedAnimeListDataClass
import suwayomi.tachidesk.manga.impl.util.lang.awaitSingle
object Search {
suspend fun sourceSearch(sourceId: Long, searchTerm: String, pageNum: Int): PagedAnimeListDataClass {
val source = getAnimeHttpSource(sourceId)
val searchManga = source.fetchSearchAnime(pageNum, searchTerm, source.getFilterList()).awaitSingle()
return searchManga.processEntries(sourceId)
}
}
@@ -138,7 +138,7 @@ object Extension {
else -> "all" else -> "all"
} }
val extensionName = packageInfo.applicationInfo.nonLocalizedLabel.toString().substringAfter("Tachiyomi: ") val extensionName = packageInfo.applicationInfo.nonLocalizedLabel.toString().substringAfter("Aniyomi: ")
// update extension info // update extension info
transaction { transaction {
@@ -32,7 +32,7 @@ object ExtensionGithubApi {
libVersion in LIB_VERSION_MIN..LIB_VERSION_MAX libVersion in LIB_VERSION_MIN..LIB_VERSION_MAX
} }
.map { element -> .map { element ->
val name = element["name"].string.substringAfter("Tachiyomi: ") val name = element["name"].string.substringAfter("Aniyomi: ")
val pkgName = element["pkg"].string val pkgName = element["pkg"].string
val apkName = element["apk"].string val apkName = element["apk"].string
val versionName = element["version"].string val versionName = element["version"].string
@@ -7,7 +7,6 @@ package suwayomi.tachidesk.manga
* 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 io.javalin.Javalin
import io.javalin.apibuilder.ApiBuilder.delete import io.javalin.apibuilder.ApiBuilder.delete
import io.javalin.apibuilder.ApiBuilder.get import io.javalin.apibuilder.ApiBuilder.get
import io.javalin.apibuilder.ApiBuilder.patch import io.javalin.apibuilder.ApiBuilder.patch
@@ -22,7 +21,7 @@ import suwayomi.tachidesk.manga.controller.MangaController
import suwayomi.tachidesk.manga.controller.SourceController import suwayomi.tachidesk.manga.controller.SourceController
object MangaAPI { object MangaAPI {
fun defineEndpoints(app: Javalin) { fun defineEndpoints() {
path("extension") { path("extension") {
get("list", ExtensionController::list) get("list", ExtensionController::list)
@@ -82,7 +81,7 @@ object MangaAPI {
patch(":categoryId", LibraryController::categoryModify) patch(":categoryId", LibraryController::categoryModify)
delete(":categoryId", LibraryController::categoryDelete) delete(":categoryId", LibraryController::categoryDelete)
patch(":categoryId/reorder", LibraryController::categoryReorder) patch(":categoryId/reorder", LibraryController::categoryReorder) // TODO: the underlying code doesn't need `:categoryId`, remove it
} }
} }
@@ -54,10 +54,9 @@ object LibraryController {
/** category re-ordering */ /** category re-ordering */
fun categoryReorder(ctx: Context) { fun categoryReorder(ctx: Context) {
val categoryId = ctx.pathParam("categoryId").toInt()
val from = ctx.formParam("from")!!.toInt() val from = ctx.formParam("from")!!.toInt()
val to = ctx.formParam("to")!!.toInt() val to = ctx.formParam("to")!!.toInt()
Category.reorderCategory(categoryId, from, to) Category.reorderCategory(from, to)
ctx.status(200) ctx.status(200)
} }
} }
@@ -97,7 +97,7 @@ object MangaController {
fun chapterList(ctx: Context) { fun chapterList(ctx: Context) {
val mangaId = ctx.pathParam("mangaId").toInt() val mangaId = ctx.pathParam("mangaId").toInt()
val onlineFetch = ctx.queryParam("onlineFetch")?.toBoolean() val onlineFetch = ctx.queryParam("onlineFetch", "false").toBoolean()
ctx.json(future { Chapter.getChapterList(mangaId, onlineFetch) }) ctx.json(future { Chapter.getChapterList(mangaId, onlineFetch) })
} }
@@ -47,7 +47,7 @@ object Category {
/** /**
* Move the category from position `from` to `to` * Move the category from position `from` to `to`
*/ */
fun reorderCategory(categoryId: Int, from: Int, to: Int) { fun reorderCategory(from: Int, to: Int) {
transaction { transaction {
val categories = CategoryTable.selectAll().orderBy(CategoryTable.order to SortOrder.ASC).toMutableList() val categories = CategoryTable.selectAll().orderBy(CategoryTable.order to SortOrder.ASC).toMutableList()
categories.add(to - 1, categories.removeAt(from - 1)) categories.add(to - 1, categories.removeAt(from - 1))
@@ -30,8 +30,8 @@ import java.time.Instant
object Chapter { object Chapter {
/** get chapter list when showing a manga */ /** get chapter list when showing a manga */
suspend fun getChapterList(mangaId: Int, onlineFetch: Boolean?): List<ChapterDataClass> { suspend fun getChapterList(mangaId: Int, onlineFetch: Boolean = false): List<ChapterDataClass> {
return if (onlineFetch == true) { return if (onlineFetch) {
getSourceChapters(mangaId) getSourceChapters(mangaId)
} else { } else {
transaction { transaction {
@@ -40,10 +40,7 @@ object Chapter {
ChapterTable.toDataClass(it) ChapterTable.toDataClass(it)
} }
}.ifEmpty { }.ifEmpty {
// If it was explicitly set to offline dont grab chapters getSourceChapters(mangaId)
if (onlineFetch == null) {
getSourceChapters(mangaId)
} else emptyList()
} }
} }
} }
@@ -163,7 +160,10 @@ object Chapter {
// update page list for this chapter // update page list for this chapter
transaction { transaction {
pageList.forEach { page -> pageList.forEach { page ->
val pageEntry = transaction { PageTable.select { (PageTable.chapter eq chapterId) and (PageTable.index eq page.index) }.firstOrNull() } val pageEntry = transaction {
PageTable.select { (PageTable.chapter eq chapterId) and (PageTable.index eq page.index) }
.firstOrNull()
}
if (pageEntry == null) { if (pageEntry == null) {
PageTable.insert { PageTable.insert {
it[index] = page.index it[index] = page.index
@@ -210,7 +210,14 @@ object Chapter {
} }
} }
fun modifyChapter(mangaId: Int, chapterIndex: Int, isRead: Boolean?, isBookmarked: Boolean?, markPrevRead: Boolean?, lastPageRead: Int?) { fun modifyChapter(
mangaId: Int,
chapterIndex: Int,
isRead: Boolean?,
isBookmarked: Boolean?,
markPrevRead: Boolean?,
lastPageRead: Int?
) {
transaction { transaction {
if (listOf(isRead, isBookmarked, lastPageRead).any { it != null }) { if (listOf(isRead, isBookmarked, lastPageRead).any { it != null }) {
ChapterTable.update({ (ChapterTable.manga eq mangaId) and (ChapterTable.chapterIndex eq chapterIndex) }) { update -> ChapterTable.update({ (ChapterTable.manga eq mangaId) and (ChapterTable.chapterIndex eq chapterIndex) }) { update ->
@@ -244,9 +251,11 @@ object Chapter {
fun modifyChapterMeta(mangaId: Int, chapterIndex: Int, key: String, value: String) { fun modifyChapterMeta(mangaId: Int, chapterIndex: Int, key: String, value: String) {
transaction { transaction {
val chapter = ChapterTable.select { (ChapterTable.manga eq mangaId) and (ChapterTable.chapterIndex eq chapterIndex) } val chapter =
.first()[ChapterTable.id] ChapterTable.select { (ChapterTable.manga eq mangaId) and (ChapterTable.chapterIndex eq chapterIndex) }
val meta = transaction { ChapterMetaTable.select { (ChapterMetaTable.ref eq chapter) and (ChapterMetaTable.key eq key) } }.firstOrNull() .first()[ChapterTable.id]
val meta =
transaction { ChapterMetaTable.select { (ChapterMetaTable.ref eq chapter) and (ChapterMetaTable.key eq key) } }.firstOrNull()
if (meta == null) { if (meta == null) {
ChapterMetaTable.insert { ChapterMetaTable.insert {
it[ChapterMetaTable.key] = key it[ChapterMetaTable.key] = key
@@ -13,22 +13,25 @@ import suwayomi.tachidesk.manga.impl.util.lang.awaitSingle
import suwayomi.tachidesk.manga.model.dataclass.PagedMangaListDataClass import suwayomi.tachidesk.manga.model.dataclass.PagedMangaListDataClass
object Search { object Search {
// TODO
fun sourceFilters(sourceId: Long) {
val source = getHttpSource(sourceId)
// source.getFilterList().toItems()
}
suspend fun sourceSearch(sourceId: Long, searchTerm: String, pageNum: Int): PagedMangaListDataClass { suspend fun sourceSearch(sourceId: Long, searchTerm: String, pageNum: Int): PagedMangaListDataClass {
val source = getHttpSource(sourceId) val source = getHttpSource(sourceId)
val searchManga = source.fetchSearchManga(pageNum, searchTerm, source.getFilterList()).awaitSingle() val searchManga = source.fetchSearchManga(pageNum, searchTerm, source.getFilterList()).awaitSingle()
return searchManga.processEntries(sourceId) return searchManga.processEntries(sourceId)
} }
// TODO
@Suppress("UNUSED_PARAMETER", "UNUSED_VARIABLE")
fun sourceFilters(sourceId: Long) {
val source = getHttpSource(sourceId)
// source.getFilterList().toItems()
}
@Suppress("UNUSED_PARAMETER")
fun sourceGlobalSearch(searchTerm: String) { fun sourceGlobalSearch(searchTerm: String) {
// TODO // TODO
} }
@Suppress("unused")
data class FilterWrapper( data class FilterWrapper(
val type: String, val type: String,
val filter: Any val filter: Any
@@ -141,6 +141,7 @@ object LegacyBackupImport : LegacyBackupBase() {
* @param history history data from json * @param history history data from json
* @param tracks tracking data from json * @param tracks tracking data from json
*/ */
@Suppress("UNUSED_PARAMETER")
private suspend fun restoreMangaData( private suspend fun restoreMangaData(
manga: Manga, manga: Manga,
source: Source, source: Source,
@@ -204,6 +205,7 @@ object LegacyBackupImport : LegacyBackupBase() {
return fetchedManga return fetchedManga
} }
@Suppress("UNUSED_PARAMETER") // TODO: remove this suppress when update Chapters is written
private fun updateChapters(source: Source, fetchedManga: SManga, chapters: List<Chapter>) { private fun updateChapters(source: Source, fetchedManga: SManga, chapters: List<Chapter>) {
// TODO("Not yet implemented") // TODO("Not yet implemented")
} }
@@ -42,11 +42,11 @@ object LegacyBackupValidator {
.sorted() .sorted()
} }
val trackers = mangas // val trackers = mangas
.filter { it.asJsonObject.has("track") } // .filter { it.asJsonObject.has("track") }
.flatMap { it.asJsonObject["track"].asJsonArray } // .flatMap { it.asJsonObject["track"].asJsonArray }
.map { it.asJsonObject["s"].asInt } // .map { it.asJsonObject["s"].asInt }
.distinct() // .distinct()
val missingTrackers = listOf("") val missingTrackers = listOf("")
// val missingTrackers = trackers // val missingTrackers = trackers
@@ -85,7 +85,7 @@ object BytecodeEditor {
bytes[2], bytes[2],
bytes[3] bytes[3]
) )
if (cafebabe.toLowerCase() != "cafebabe") { if (cafebabe.lowercase() != "cafebabe") {
// Corrupted class // Corrupted class
return@use null return@use null
} }
@@ -81,7 +81,7 @@ object JavalinSetup {
app.routes { app.routes {
path("api/v1/") { path("api/v1/") {
GlobalAPI.defineEndpoints() GlobalAPI.defineEndpoints()
MangaAPI.defineEndpoints(app) MangaAPI.defineEndpoints()
AnimeAPI.defineEndpoints(app) // TODO: migrate Anime endpoints AnimeAPI.defineEndpoints(app) // TODO: migrate Anime endpoints
} }
} }
@@ -29,7 +29,7 @@ class ServerConfig(config: Config, moduleName: String = "") : ConfigModule(confi
// webUI // webUI
val webUIEnabled: Boolean by overridableWithSysProperty val webUIEnabled: Boolean by overridableWithSysProperty
val initialOpenInBrowserEnabled: Boolean by overridableWithSysProperty val initialOpenInBrowserEnabled: Boolean by overridableWithSysProperty
val webUIBrowser: String by overridableWithSysProperty val webUIInterface: String by overridableWithSysProperty
val electronPath: String by overridableWithSysProperty val electronPath: String by overridableWithSysProperty
companion object { companion object {
@@ -18,7 +18,7 @@ object Browser {
fun openInBrowser() { fun openInBrowser() {
if (serverConfig.webUIEnabled) { if (serverConfig.webUIEnabled) {
if (serverConfig.webUIBrowser == ("electron")) { if (serverConfig.webUIInterface == ("electron")) {
try { try {
val electronPath = serverConfig.electronPath val electronPath = serverConfig.electronPath
electronInstances.add(ProcessBuilder(electronPath, appBaseUrl).start()) electronInstances.add(ProcessBuilder(electronPath, appBaseUrl).start())
@@ -14,8 +14,9 @@ import org.kodein.di.conf.global
import org.kodein.di.instance import org.kodein.di.instance
import suwayomi.tachidesk.server.ApplicationDirs import suwayomi.tachidesk.server.ApplicationDirs
import suwayomi.tachidesk.server.BuildConfig import suwayomi.tachidesk.server.BuildConfig
import java.io.BufferedInputStream
import java.io.File import java.io.File
import java.io.InputStream
import java.net.HttpURLConnection
import java.net.URL import java.net.URL
import java.nio.charset.StandardCharsets import java.nio.charset.StandardCharsets
import java.security.MessageDigest import java.security.MessageDigest
@@ -58,9 +59,12 @@ fun setupWebUI() {
val webUIZipFile = File(webUIZipPath) val webUIZipFile = File(webUIZipPath)
// try with resources first // try with resources first
val resourceWebUI = try { val resourceWebUI: InputStream? = try {
BuildConfig::class.java.getResourceAsStream("/WebUI.zip") BuildConfig::class.java.getResourceAsStream("/WebUI.zip")
} catch (e: NullPointerException) { null } } catch (e: NullPointerException) {
logger.info { "No bundled WebUI.zip found!" }
null
}
if (resourceWebUI == null) { // is not bundled if (resourceWebUI == null) { // is not bundled
// download webUI zip // download webUI zip
@@ -71,18 +75,25 @@ fun setupWebUI() {
val data = ByteArray(1024) val data = ByteArray(1024)
webUIZipFile.outputStream().use { webUIZipFileOut -> webUIZipFile.outputStream().use { webUIZipFileOut ->
BufferedInputStream(URL(webUIZipURL).openStream()).use { inp ->
val connection = URL(webUIZipURL).openConnection() as HttpURLConnection
connection.connect()
val contentLength = connection.contentLength
connection.inputStream.buffered().use { inp ->
var totalCount = 0 var totalCount = 0
var tresh = 0
print("Download progress: % 00")
while (true) { while (true) {
val count = inp.read(data, 0, 1024) val count = inp.read(data, 0, 1024)
totalCount += count
if (totalCount > tresh + 10 * 1024) {
tresh = totalCount
print(" *")
}
if (count == -1) if (count == -1)
break break
totalCount += count
val percentage = (totalCount.toFloat() / contentLength * 100).toInt().toString().padStart(2, '0')
print("\b\b$percentage")
webUIZipFileOut.write(data, 0, count) webUIZipFileOut.write(data, 0, count)
} }
println() println()
@@ -14,5 +14,5 @@ server.systemTrayEnabled = true
# webUI # webUI
server.webUIEnabled = true server.webUIEnabled = true
server.initialOpenInBrowserEnabled = true server.initialOpenInBrowserEnabled = true
server.webUIBrowser = "browser" # "browser" or "electron" server.webUIInterface = "browser" # "browser" or "electron"
server.electronPath = "" server.electronPath = ""
@@ -72,7 +72,7 @@ class TestExtensions {
sources = getSourceList().map { getHttpSource(it.id.toLong()) } sources = getSourceList().map { getHttpSource(it.id.toLong()) }
} }
setLoggingEnabled(true) setLoggingEnabled(true)
File("tmp/TestDesk/sources.txt").writeText(sources.joinToString("\n") { "${it.name} - ${it.lang.toUpperCase()} - ${it.id}" }) File("tmp/TestDesk/sources.txt").writeText(sources.joinToString("\n") { "${it.name} - ${it.lang.uppercase()} - ${it.id}" })
} }
@Test @Test
@@ -99,7 +99,7 @@ class TestExtensions {
}.awaitAll() }.awaitAll()
File("tmp/TestDesk/failedToFetch.txt").writeText( File("tmp/TestDesk/failedToFetch.txt").writeText(
failedToFetch.joinToString("\n") { (source, exception) -> failedToFetch.joinToString("\n") { (source, exception) ->
"${source.name} (${source.lang.toUpperCase()}, ${source.id}):" + "${source.name} (${source.lang.uppercase()}, ${source.id}):" +
" ${exception.message}" " ${exception.message}"
} }
) )