Compare commits
11 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| bc2072e81f | |||
| f36bc3f643 | |||
| f7901ad843 | |||
| 3771030ed6 | |||
| 57197e58b5 | |||
| ac601399ac | |||
| 6a0e221153 | |||
| 6a949fc851 | |||
| f1a077dc2f | |||
| f20962b02b | |||
| 77e057f244 |
@@ -1,68 +1,28 @@
|
|||||||
|
|
||||||
plugins {
|
|
||||||
application
|
|
||||||
kotlin("plugin.serialization")
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
repositories {
|
|
||||||
mavenCentral()
|
|
||||||
maven {
|
|
||||||
url = uri("https://jitpack.io")
|
|
||||||
}
|
|
||||||
|
|
||||||
maven {
|
|
||||||
url = uri("https://maven.google.com")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
// Android stub library
|
// Android stub library
|
||||||
implementation(fileTree("lib/"))
|
implementation(fileTree("lib/"))
|
||||||
|
|
||||||
// JSON
|
|
||||||
compileOnly("com.google.code.gson:gson:2.8.6")
|
|
||||||
|
|
||||||
// XML
|
// XML
|
||||||
compileOnly(group= "xmlpull", name= "xmlpull", version= "1.1.3.1")
|
compileOnly("xmlpull:xmlpull:1.1.3.4a")
|
||||||
|
|
||||||
// Config API
|
// Config API
|
||||||
implementation(project(":AndroidCompat:Config"))
|
implementation(project(":AndroidCompat:Config"))
|
||||||
|
|
||||||
// APK sig verifier
|
// APK sig verifier
|
||||||
compileOnly("com.android.tools.build:apksig:4.2.0-alpha13")
|
compileOnly("com.android.tools.build:apksig:7.1.0-alpha12")
|
||||||
|
|
||||||
// AndroidX annotations
|
// AndroidX annotations
|
||||||
compileOnly("androidx.annotation:annotation:1.2.0-alpha01")
|
compileOnly("androidx.annotation:annotation:1.2.0")
|
||||||
|
|
||||||
// substitute for duktape-android
|
// substitute for duktape-android
|
||||||
implementation("org.mozilla:rhino-runtime:1.7.13") // slimmer version of 'org.mozilla:rhino'
|
implementation("org.mozilla:rhino-runtime:1.7.13") // slimmer version of 'org.mozilla:rhino'
|
||||||
implementation("org.mozilla:rhino-engine:1.7.13") // provides the same interface as 'javax.script' a.k.a Nashorn
|
implementation("org.mozilla:rhino-engine:1.7.13") // provides the same interface as 'javax.script' a.k.a Nashorn
|
||||||
|
|
||||||
// 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.8"
|
||||||
implementation("com.russhwolf:multiplatform-settings-jvm:$multiplatformSettingsVersion")
|
implementation("com.russhwolf:multiplatform-settings-jvm:$multiplatformSettingsVersion")
|
||||||
implementation("com.russhwolf:multiplatform-settings-serialization-jvm:$multiplatformSettingsVersion")
|
implementation("com.russhwolf:multiplatform-settings-serialization-jvm:$multiplatformSettingsVersion")
|
||||||
|
|
||||||
// Android version of SimpleDateFormat
|
// Android version of SimpleDateFormat
|
||||||
implementation("com.ibm.icu:icu4j:69.1")
|
implementation("com.ibm.icu:icu4j:69.1")
|
||||||
}
|
}
|
||||||
|
|
||||||
tasks {
|
|
||||||
withType<org.jetbrains.kotlin.gradle.tasks.KotlinCompile> {
|
|
||||||
kotlinOptions.freeCompilerArgs = listOf("-Xopt-in=kotlin.RequiresOptIn")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
//def fatJarTask = tasks.getByPath(':AndroidCompat:JVMPatch:fatJar')
|
|
||||||
//
|
|
||||||
//// Copy JVM core patches
|
|
||||||
//task copyJVMPatches(type: Copy) {
|
|
||||||
// from fatJarTask.outputs.files
|
|
||||||
// into 'src/main/resources/patches'
|
|
||||||
//}
|
|
||||||
//
|
|
||||||
//compileOnly(Java.dependsOn gradle.includedBuild('dex2jar').task(':dex-translator:assemble')
|
|
||||||
//compileOnly(Java.dependsOn copyJVMPatches
|
|
||||||
//copyJVMPatches.dependsOn fatJarTask
|
|
||||||
//
|
|
||||||
|
|||||||
@@ -13,7 +13,10 @@ import com.fasterxml.jackson.annotation.JsonIgnore;
|
|||||||
public class TwoStatePreference extends Preference {
|
public class TwoStatePreference extends Preference {
|
||||||
// Note: remove @JsonIgnore and implement methods if any extension ever uses these methods or the variables behind them
|
// Note: remove @JsonIgnore and implement methods if any extension ever uses these methods or the variables behind them
|
||||||
|
|
||||||
public TwoStatePreference(Context context) { super(context); }
|
public TwoStatePreference(Context context) {
|
||||||
|
super(context);
|
||||||
|
setDefaultValue(false);
|
||||||
|
}
|
||||||
|
|
||||||
@JsonIgnore
|
@JsonIgnore
|
||||||
public boolean isChecked() { throw new RuntimeException("Stub!"); }
|
public boolean isChecked() { throw new RuntimeException("Stub!"); }
|
||||||
|
|||||||
+39
-5
@@ -1,10 +1,44 @@
|
|||||||
|
# Server: v0.5.2 + WebUI: r807
|
||||||
|
## TL;DR
|
||||||
|
- Fixed Local source not working on Windows
|
||||||
|
- Fixed Chapter numbers being shown incorrectly
|
||||||
|
|
||||||
|
## Tachidesk-Server
|
||||||
|
### Public API
|
||||||
|
#### Non-breaking changes
|
||||||
|
- N/A
|
||||||
|
|
||||||
|
#### Breaking changes
|
||||||
|
- N/A
|
||||||
|
|
||||||
|
#### Bug fixes
|
||||||
|
- N/A
|
||||||
|
|
||||||
|
### Private API
|
||||||
|
- (r942) Gradle Updates ([#199](https://github.com/Suwayomi/Tachidesk-WebUI/pull/199) by @Syer10)
|
||||||
|
- (r941) Update BytecodeEditor to use Java NIO Paths ([#200](https://github.com/Suwayomi/Tachidesk-WebUI/pull/200) by @Syer10)
|
||||||
|
|
||||||
|
|
||||||
|
## Tachidesk-WebUI
|
||||||
|
#### Visible changes
|
||||||
|
- (r804) update text positioning on Reader and Player ([#35](https://github.com/Suwayomi/Tachidesk-WebUI/pull/35) by @voltrare)
|
||||||
|
- (r806) Source card for Local source is different
|
||||||
|
- (r807) add Local source guide
|
||||||
|
|
||||||
|
#### Bug fixes
|
||||||
|
- (r805) fix chapter name
|
||||||
|
|
||||||
|
#### Internal changes
|
||||||
|
- N/A
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
# Server: v0.5.1 + WebUI: r803
|
# Server: v0.5.1 + WebUI: r803
|
||||||
## TL;DR
|
## TL;DR
|
||||||
- Loading sources' manga list is at least twice as fast
|
- Loading sources' manga list is at least twice as fast
|
||||||
- Added support for Tachiyomi's Local source
|
- Added support for Tachiyomi's Local source
|
||||||
- Added BasicAuth support, now you can protect your Tachidesk instance if you are running it on a public server
|
- Added BasicAuth support, now you can protect your Tachidesk instance if you are running it on a public server
|
||||||
- Added ability to turn off cache for image requests
|
- Added ability to turn off cache for image requests
|
||||||
<!-- TODO: fill before release -->
|
|
||||||
|
|
||||||
## Tachidesk-Server
|
## Tachidesk-Server
|
||||||
### Public API
|
### Public API
|
||||||
@@ -32,14 +66,14 @@
|
|||||||
#### Visible changes
|
#### Visible changes
|
||||||
- (r790) nice looking progress percentage
|
- (r790) nice looking progress percentage
|
||||||
- (r791) show a Delete button for downloaded chapters
|
- (r791) show a Delete button for downloaded chapters
|
||||||
- (r792) Update hover effect using more of Material-UI color pallete ([#29](https://github.com/Suwayomi/Tachidesk-WebUI/pull/21) by @voltrare)
|
- (r792) Update hover effect using more of Material-UI color pallete ([#29](https://github.com/Suwayomi/Tachidesk-WebUI/pull/29) by @voltrare)
|
||||||
- (r793) Optimize images ([#32](https://github.com/Suwayomi/Tachidesk-WebUI/pull/21) by @phanirithvij)
|
- (r793) Optimize images ([#32](https://github.com/Suwayomi/Tachidesk-WebUI/pull/32) by @phanirithvij)
|
||||||
- (r794) try fix #30 ([#31](https://github.com/Suwayomi/Tachidesk-WebUI/pull/21) by @phanirithvij)
|
- (r794) try fix #30 ([#31](https://github.com/Suwayomi/Tachidesk-WebUI/pull/31) by @phanirithvij)
|
||||||
- (r795) fix viewing page number when the string is long
|
- (r795) fix viewing page number when the string is long
|
||||||
- (r796) show proper display name for source
|
- (r796) show proper display name for source
|
||||||
- (r797) fail gracefully when a thumbnail has errors
|
- (r797) fail gracefully when a thumbnail has errors
|
||||||
- (r798) fix when a source fails to load mangas
|
- (r798) fix when a source fails to load mangas
|
||||||
- (r800) add Local source ([#31](https://github.com/Suwayomi/Tachidesk-WebUI/pull/21))
|
- (r800) add Local source ([#31](https://github.com/Suwayomi/Tachidesk-WebUI/pull/31))
|
||||||
- (r803) add support for useCache
|
- (r803) add support for useCache
|
||||||
|
|
||||||
#### Bug fixes
|
#### Bug fixes
|
||||||
|
|||||||
+31
-14
@@ -1,8 +1,11 @@
|
|||||||
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.LintTask
|
||||||
|
|
||||||
plugins {
|
plugins {
|
||||||
kotlin("jvm") version kotlinVersion
|
kotlin("jvm") version kotlinVersion
|
||||||
kotlin("plugin.serialization") version kotlinVersion
|
kotlin("plugin.serialization") version kotlinVersion
|
||||||
|
id("org.jmailen.kotlinter") version "3.6.0"
|
||||||
}
|
}
|
||||||
|
|
||||||
allprojects {
|
allprojects {
|
||||||
@@ -12,10 +15,8 @@ allprojects {
|
|||||||
|
|
||||||
repositories {
|
repositories {
|
||||||
mavenCentral()
|
mavenCentral()
|
||||||
maven("https://maven.google.com/")
|
google()
|
||||||
maven("https://jitpack.io")
|
maven("https://jitpack.io")
|
||||||
maven("https://oss.sonatype.org/content/repositories/snapshots/")
|
|
||||||
maven("https://dl.google.com/dl/android/maven2/")
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -27,18 +28,36 @@ val projects = listOf(
|
|||||||
|
|
||||||
configure(projects) {
|
configure(projects) {
|
||||||
apply(plugin = "org.jetbrains.kotlin.jvm")
|
apply(plugin = "org.jetbrains.kotlin.jvm")
|
||||||
|
apply(plugin = "org.jetbrains.kotlin.plugin.serialization")
|
||||||
|
|
||||||
java {
|
java {
|
||||||
sourceCompatibility = JavaVersion.VERSION_1_8
|
sourceCompatibility = JavaVersion.VERSION_1_8
|
||||||
targetCompatibility = JavaVersion.VERSION_1_8
|
targetCompatibility = JavaVersion.VERSION_1_8
|
||||||
}
|
}
|
||||||
|
|
||||||
tasks.withType<KotlinCompile> {
|
tasks {
|
||||||
kotlinOptions {
|
withType<KotlinCompile> {
|
||||||
jvmTarget = JavaVersion.VERSION_1_8.toString()
|
kotlinOptions {
|
||||||
|
jvmTarget = JavaVersion.VERSION_1_8.toString()
|
||||||
|
freeCompilerArgs = listOf(
|
||||||
|
"-Xopt-in=kotlin.RequiresOptIn",
|
||||||
|
"-Xopt-in=kotlinx.coroutines.ExperimentalCoroutinesApi",
|
||||||
|
"-Xopt-in=kotlinx.coroutines.InternalCoroutinesApi",
|
||||||
|
"-Xopt-in=kotlinx.serialization.ExperimentalSerializationApi",
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
withType<LintTask> {
|
||||||
|
source(files("src/kotlin"))
|
||||||
|
}
|
||||||
|
|
||||||
|
withType<FormatTask> {
|
||||||
|
source(files("src/kotlin"))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
// Kotlin
|
// Kotlin
|
||||||
implementation(kotlin("stdlib-jdk8"))
|
implementation(kotlin("stdlib-jdk8"))
|
||||||
@@ -46,7 +65,7 @@ configure(projects) {
|
|||||||
testImplementation(kotlin("test-junit5"))
|
testImplementation(kotlin("test-junit5"))
|
||||||
|
|
||||||
// coroutines
|
// coroutines
|
||||||
val coroutinesVersion = "1.5.1"
|
val coroutinesVersion = "1.5.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")
|
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-test:$coroutinesVersion")
|
||||||
@@ -55,14 +74,13 @@ configure(projects) {
|
|||||||
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")
|
||||||
|
|
||||||
|
|
||||||
// Dependency Injection
|
// Dependency Injection
|
||||||
implementation("org.kodein.di:kodein-di-conf-jvm:7.7.0")
|
implementation("org.kodein.di:kodein-di-conf-jvm:7.8.0")
|
||||||
|
|
||||||
// Logging
|
// Logging
|
||||||
implementation("org.slf4j:slf4j-api:1.7.30")
|
implementation("org.slf4j:slf4j-api:1.7.32")
|
||||||
implementation("ch.qos.logback:logback-classic:1.2.3")
|
implementation("ch.qos.logback:logback-classic:1.2.6")
|
||||||
implementation("io.github.microutils:kotlin-logging:2.0.6")
|
implementation("io.github.microutils:kotlin-logging:2.0.11")
|
||||||
|
|
||||||
// ReactiveX
|
// ReactiveX
|
||||||
implementation("io.reactivex:rxjava:1.3.8")
|
implementation("io.reactivex:rxjava:1.3.8")
|
||||||
@@ -70,7 +88,7 @@ configure(projects) {
|
|||||||
implementation("com.jakewharton.rxrelay:rxrelay:1.2.0")
|
implementation("com.jakewharton.rxrelay:rxrelay:1.2.0")
|
||||||
|
|
||||||
// dependency both in AndroidCompat and extensions, version locked by Tachiyomi app/extensions
|
// dependency both in AndroidCompat and extensions, version locked by Tachiyomi app/extensions
|
||||||
implementation("org.jsoup:jsoup:1.14.1")
|
implementation("org.jsoup:jsoup:1.14.2")
|
||||||
|
|
||||||
// dependency of :AndroidCompat:Config
|
// dependency of :AndroidCompat:Config
|
||||||
implementation("com.typesafe:config:1.4.1")
|
implementation("com.typesafe:config:1.4.1")
|
||||||
@@ -87,7 +105,6 @@ configure(projects) {
|
|||||||
// APK parser
|
// APK parser
|
||||||
implementation("net.dongliu:apk-parser:2.6.10")
|
implementation("net.dongliu:apk-parser:2.6.10")
|
||||||
|
|
||||||
|
|
||||||
// dependency both in AndroidCompat and server, version locked by javalin
|
// dependency both in AndroidCompat and server, version locked by javalin
|
||||||
implementation("com.fasterxml.jackson.core:jackson-annotations:2.12.4")
|
implementation("com.fasterxml.jackson.core:jackson-annotations:2.12.4")
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,9 +12,9 @@ const val kotlinVersion = "1.5.30"
|
|||||||
const val MainClass = "suwayomi.tachidesk.MainKt"
|
const val MainClass = "suwayomi.tachidesk.MainKt"
|
||||||
|
|
||||||
// should be bumped with each stable release
|
// should be bumped with each stable release
|
||||||
val tachideskVersion = System.getenv("ProductVersion") ?: "v0.5.1"
|
val tachideskVersion = System.getenv("ProductVersion") ?: "v0.5.2"
|
||||||
|
|
||||||
val webUIRevisionTag = System.getenv("WebUIRevision") ?: "r803"
|
val webUIRevisionTag = System.getenv("WebUIRevision") ?: "r807"
|
||||||
|
|
||||||
// counts commits on the master branch
|
// counts commits on the master branch
|
||||||
val tachideskRevision = runCatching {
|
val tachideskRevision = runCatching {
|
||||||
|
|||||||
+1
-1
@@ -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.1.1-all.zip
|
distributionUrl=https\://services.gradle.org/distributions/gradle-7.2-all.zip
|
||||||
zipStoreBase=GRADLE_USER_HOME
|
zipStoreBase=GRADLE_USER_HOME
|
||||||
zipStorePath=wrapper/dists
|
zipStorePath=wrapper/dists
|
||||||
|
|||||||
+17
-50
@@ -1,25 +1,11 @@
|
|||||||
|
|
||||||
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 de.undercouch.gradle.tasks.download.Download
|
||||||
import org.jmailen.gradle.kotlinter.tasks.FormatTask
|
|
||||||
import org.jmailen.gradle.kotlinter.tasks.LintTask
|
|
||||||
import java.time.Instant
|
import java.time.Instant
|
||||||
|
|
||||||
plugins {
|
plugins {
|
||||||
application
|
application
|
||||||
kotlin("plugin.serialization")
|
|
||||||
id("com.github.johnrengelman.shadow") version "7.0.0"
|
id("com.github.johnrengelman.shadow") version "7.0.0"
|
||||||
id("org.jmailen.kotlinter") version "3.6.0"
|
id("com.github.gmazzo.buildconfig") version "3.0.3"
|
||||||
id("com.github.gmazzo.buildconfig") version "3.0.2"
|
|
||||||
}
|
|
||||||
|
|
||||||
repositories {
|
|
||||||
maven {
|
|
||||||
url = uri("https://repo1.maven.org/maven2/")
|
|
||||||
}
|
|
||||||
maven {
|
|
||||||
url = uri("https://jitpack.io")
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
@@ -33,8 +19,9 @@ dependencies {
|
|||||||
// Javalin api
|
// Javalin api
|
||||||
implementation("io.javalin:javalin:4.0.0")
|
implementation("io.javalin:javalin:4.0.0")
|
||||||
// jackson version locked by javalin, ref: `io.javalin.core.util.OptionalDependency`
|
// jackson version locked by javalin, ref: `io.javalin.core.util.OptionalDependency`
|
||||||
implementation("com.fasterxml.jackson.core:jackson-databind:2.12.4")
|
val jacksonVersion = "2.12.4"
|
||||||
implementation("com.fasterxml.jackson.module:jackson-module-kotlin:2.12.4")
|
implementation("com.fasterxml.jackson.core:jackson-databind:$jacksonVersion")
|
||||||
|
implementation("com.fasterxml.jackson.module:jackson-module-kotlin:$jacksonVersion")
|
||||||
|
|
||||||
// Exposed ORM
|
// Exposed ORM
|
||||||
val exposedVersion = "0.34.1"
|
val exposedVersion = "0.34.1"
|
||||||
@@ -46,8 +33,7 @@ dependencies {
|
|||||||
implementation("com.h2database:h2:1.4.200")
|
implementation("com.h2database:h2:1.4.200")
|
||||||
|
|
||||||
// Exposed Migrations
|
// Exposed Migrations
|
||||||
val exposedMigrationsVersion = "3.1.2"
|
implementation("com.github.Suwayomi:exposed-migrations:3.1.2")
|
||||||
implementation("com.github.Suwayomi:exposed-migrations:$exposedMigrationsVersion")
|
|
||||||
|
|
||||||
// tray icon
|
// tray icon
|
||||||
implementation("com.dorkbox:SystemTray:4.1")
|
implementation("com.dorkbox:SystemTray:4.1")
|
||||||
@@ -57,8 +43,8 @@ dependencies {
|
|||||||
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")
|
||||||
implementation("io.reactivex:rxjava:1.3.8")
|
implementation("io.reactivex:rxjava:1.3.8")
|
||||||
implementation("org.jsoup:jsoup:1.14.1")
|
implementation("org.jsoup:jsoup:1.14.2")
|
||||||
implementation("com.google.code.gson:gson:2.8.7")
|
implementation("com.google.code.gson:gson:2.8.8")
|
||||||
implementation("com.github.salomonbrys.kotson:kotson:2.5.0")
|
implementation("com.github.salomonbrys.kotson:kotson:2.5.0")
|
||||||
|
|
||||||
// Sort
|
// Sort
|
||||||
@@ -131,29 +117,17 @@ tasks {
|
|||||||
shadowJar {
|
shadowJar {
|
||||||
manifest {
|
manifest {
|
||||||
attributes(
|
attributes(
|
||||||
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)
|
||||||
archiveVersion.set(tachideskVersion)
|
archiveVersion.set(tachideskVersion)
|
||||||
archiveClassifier.set(tachideskRevision)
|
archiveClassifier.set(tachideskRevision)
|
||||||
}
|
}
|
||||||
withType<KotlinCompile> {
|
|
||||||
kotlinOptions {
|
|
||||||
freeCompilerArgs = listOf(
|
|
||||||
"-Xopt-in=kotlin.RequiresOptIn",
|
|
||||||
"-Xopt-in=kotlinx.coroutines.ExperimentalCoroutinesApi",
|
|
||||||
"-Xopt-in=kotlinx.coroutines.InternalCoroutinesApi",
|
|
||||||
"-Xopt-in=kotlinx.serialization.ExperimentalSerializationApi",
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
test {
|
test {
|
||||||
useJUnit()
|
useJUnit()
|
||||||
@@ -164,7 +138,7 @@ tasks {
|
|||||||
}
|
}
|
||||||
|
|
||||||
named("run") {
|
named("run") {
|
||||||
dependsOn("formatKotlin", "lintKotlin")
|
dependsOn(":formatKotlin", ":lintKotlin")
|
||||||
}
|
}
|
||||||
|
|
||||||
named<Copy>("processResources") {
|
named<Copy>("processResources") {
|
||||||
@@ -172,7 +146,7 @@ tasks {
|
|||||||
mustRunAfter("downloadWebUI")
|
mustRunAfter("downloadWebUI")
|
||||||
}
|
}
|
||||||
|
|
||||||
register<de.undercouch.gradle.tasks.download.Download>("downloadWebUI") {
|
register<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")
|
||||||
|
|
||||||
@@ -187,8 +161,9 @@ tasks {
|
|||||||
it.readText().trim()
|
it.readText().trim()
|
||||||
}
|
}
|
||||||
|
|
||||||
if (zipRevision == webUIRevisionTag)
|
if (zipRevision == webUIRevisionTag) {
|
||||||
shouldOverwrite = false
|
shouldOverwrite = false
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return shouldOverwrite
|
return shouldOverwrite
|
||||||
@@ -196,12 +171,4 @@ tasks {
|
|||||||
|
|
||||||
overwrite(shouldOverwrite())
|
overwrite(shouldOverwrite())
|
||||||
}
|
}
|
||||||
|
|
||||||
withType<LintTask> {
|
|
||||||
source(files("src/kotlin"))
|
|
||||||
}
|
|
||||||
|
|
||||||
withType<FormatTask> {
|
|
||||||
source(files("src/kotlin"))
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -32,7 +32,10 @@ import okhttp3.OkHttpClient
|
|||||||
import okhttp3.Protocol
|
import okhttp3.Protocol
|
||||||
import okhttp3.Request
|
import okhttp3.Request
|
||||||
import okhttp3.Response
|
import okhttp3.Response
|
||||||
|
import okhttp3.ResponseBody.Companion.asResponseBody
|
||||||
import okhttp3.ResponseBody.Companion.toResponseBody
|
import okhttp3.ResponseBody.Companion.toResponseBody
|
||||||
|
import okio.buffer
|
||||||
|
import okio.source
|
||||||
import org.jetbrains.exposed.sql.insert
|
import org.jetbrains.exposed.sql.insert
|
||||||
import org.jetbrains.exposed.sql.insertAndGetId
|
import org.jetbrains.exposed.sql.insertAndGetId
|
||||||
import org.jetbrains.exposed.sql.select
|
import org.jetbrains.exposed.sql.select
|
||||||
@@ -50,7 +53,7 @@ import java.io.File
|
|||||||
import java.io.FileInputStream
|
import java.io.FileInputStream
|
||||||
import java.io.FileNotFoundException
|
import java.io.FileNotFoundException
|
||||||
import java.io.InputStream
|
import java.io.InputStream
|
||||||
import java.net.URL
|
import java.net.URLDecoder
|
||||||
import java.util.Locale
|
import java.util.Locale
|
||||||
import java.util.concurrent.TimeUnit
|
import java.util.concurrent.TimeUnit
|
||||||
import java.util.zip.ZipFile
|
import java.util.zip.ZipFile
|
||||||
@@ -342,18 +345,14 @@ class LocalSource : HttpSource() {
|
|||||||
throw Exception("Chapter not found")
|
throw Exception("Chapter not found")
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun getFormat(file: File): Format {
|
private fun getFormat(file: File): Format = with(file) {
|
||||||
return with(file) {
|
when {
|
||||||
when {
|
isDirectory -> Format.Directory(this)
|
||||||
isDirectory -> Format.Directory(file)
|
extension.equals("zip", true) || extension.equals("cbz", true) -> Format.Zip(this)
|
||||||
extension.equals("zip", true) -> Format.Zip(file)
|
extension.equals("rar", true) || extension.equals("cbr", true) -> Format.Rar(this)
|
||||||
extension.equals("cbz", true) -> Format.Zip(file)
|
extension.equals("epub", true) -> Format.Epub(this)
|
||||||
extension.equals("rar", true) -> Format.Rar(file)
|
|
||||||
extension.equals("cbr", true) -> Format.Rar(file)
|
|
||||||
extension.equals("epub", true) -> Format.Epub(file)
|
|
||||||
|
|
||||||
else -> throw Exception("Invalid chapter format")
|
else -> throw Exception("Invalid chapter format")
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -439,19 +438,28 @@ class LocalSource : HttpSource() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private object FileSystemInterceptor : Interceptor {
|
private object FileSystemInterceptor : Interceptor {
|
||||||
fun fakeUrlFrom(path: String) = "http://$path"
|
fun fakeUrlFrom(path: String): String = "http://$path"
|
||||||
|
|
||||||
private fun restoreFileUrl(markedFakeHttpUrl: String): String {
|
|
||||||
return markedFakeHttpUrl.replaceFirst("http:", "file:/")
|
private fun restoreFilePath(url: String): String {
|
||||||
|
val path = URLDecoder.decode(url.replaceFirst("http://", ""), "UTF-8")
|
||||||
|
|
||||||
|
// Windows
|
||||||
|
if (System.getProperty("os.name").lowercase().startsWith("win")) {
|
||||||
|
// convert paths like "c/Users/..." to "c:/Users/..."
|
||||||
|
return StringBuilder(path).insert(1, ":").toString()
|
||||||
|
}
|
||||||
|
|
||||||
|
return "/$path"
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun intercept(chain: Interceptor.Chain): Response {
|
override fun intercept(chain: Interceptor.Chain): Response {
|
||||||
val request = chain.request()
|
val request = chain.request()
|
||||||
val url = request.url
|
val url = request.url
|
||||||
val fileUrl = restoreFileUrl(url.toString())
|
val filePath = restoreFilePath(url.toString())
|
||||||
return try {
|
return try {
|
||||||
Response.Builder()
|
Response.Builder()
|
||||||
.body(URL(fileUrl).readBytes().toResponseBody())
|
.body(File(filePath).source().buffer().asResponseBody())
|
||||||
.code(200)
|
.code(200)
|
||||||
.message("Some file")
|
.message("Some file")
|
||||||
.protocol(Protocol.HTTP_1_0)
|
.protocol(Protocol.HTTP_1_0)
|
||||||
@@ -461,7 +469,7 @@ private object FileSystemInterceptor : Interceptor {
|
|||||||
Response.Builder()
|
Response.Builder()
|
||||||
.body("".toResponseBody())
|
.body("".toResponseBody())
|
||||||
.code(404)
|
.code(404)
|
||||||
.message(e.message ?: "File not found ($fileUrl)")
|
.message(e.message ?: "File not found ($filePath)")
|
||||||
.protocol(Protocol.HTTP_1_0)
|
.protocol(Protocol.HTTP_1_0)
|
||||||
.request(request)
|
.request(request)
|
||||||
.build()
|
.build()
|
||||||
|
|||||||
@@ -82,7 +82,7 @@ object PackageTools {
|
|||||||
)
|
)
|
||||||
handler.dump(errorFile, emptyArray<String>())
|
handler.dump(errorFile, emptyArray<String>())
|
||||||
} else {
|
} else {
|
||||||
BytecodeEditor.fixAndroidClasses(jarFilePath.toFile())
|
BytecodeEditor.fixAndroidClasses(jarFilePath)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -15,15 +15,10 @@ import org.objectweb.asm.FieldVisitor
|
|||||||
import org.objectweb.asm.Handle
|
import org.objectweb.asm.Handle
|
||||||
import org.objectweb.asm.MethodVisitor
|
import org.objectweb.asm.MethodVisitor
|
||||||
import org.objectweb.asm.Opcodes
|
import org.objectweb.asm.Opcodes
|
||||||
import org.objectweb.asm.tree.ClassNode
|
import java.nio.file.FileSystems
|
||||||
import suwayomi.tachidesk.manga.impl.util.storage.use
|
import java.nio.file.Files
|
||||||
import java.io.File
|
import java.nio.file.Path
|
||||||
import java.io.IOException
|
import kotlin.streams.asSequence
|
||||||
import java.util.jar.JarEntry
|
|
||||||
import java.util.jar.JarFile
|
|
||||||
import java.util.jar.JarOutputStream
|
|
||||||
import java.util.zip.ZipEntry
|
|
||||||
import java.util.zip.ZipInputStream
|
|
||||||
|
|
||||||
object BytecodeEditor {
|
object BytecodeEditor {
|
||||||
private val logger = KotlinLogging.logger {}
|
private val logger = KotlinLogging.logger {}
|
||||||
@@ -33,77 +28,52 @@ object BytecodeEditor {
|
|||||||
*
|
*
|
||||||
* @param jarFile The JarFile to replace class references in
|
* @param jarFile The JarFile to replace class references in
|
||||||
*/
|
*/
|
||||||
fun fixAndroidClasses(jarFile: File) {
|
fun fixAndroidClasses(jarFile: Path) {
|
||||||
val nodes = loadClasses(jarFile)
|
FileSystems.newFileSystem(jarFile, null as ClassLoader?)?.use {
|
||||||
.mapValues { (className, classFileBuffer) ->
|
Files.walk(it.getPath("/")).asSequence()
|
||||||
logger.trace { "Processing class $className" }
|
.filterNotNull()
|
||||||
transform(classFileBuffer)
|
.filterNot(Files::isDirectory)
|
||||||
} + loadNonClasses(jarFile)
|
.mapNotNull(::getClassBytes)
|
||||||
|
.map(::transform)
|
||||||
saveAsJar(nodes, jarFile)
|
.forEach(::write)
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Load all classes inside the [jar] [File]
|
|
||||||
*
|
|
||||||
* @param jar The JarFile to load classes from
|
|
||||||
*
|
|
||||||
* @return [Map] with class names and [ByteArray]s of bytecode
|
|
||||||
*/
|
|
||||||
private fun loadClasses(jar: File): Map<String, ByteArray> {
|
|
||||||
return JarFile(jar).use { jarFile ->
|
|
||||||
jarFile.entries()
|
|
||||||
.asSequence()
|
|
||||||
.mapNotNull {
|
|
||||||
readJar(jarFile, it)
|
|
||||||
}
|
|
||||||
.toMap()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get class file in [jar] for [entry]
|
* Get class bytes from a [Path]
|
||||||
*
|
*
|
||||||
* @param jar The jar to get the class from
|
* @param path The path entry to get the class bytes from
|
||||||
* @param entry The entry in the jar
|
|
||||||
*
|
*
|
||||||
* @return [Pair] of the class name plus the class [ByteArray], or null if it's not a valid class
|
* @return [Pair] of the [Path] plus the class [ByteArray], or null if it's not a valid class
|
||||||
*/
|
*/
|
||||||
private fun readJar(jar: JarFile, entry: JarEntry): Pair<String, ByteArray>? {
|
private fun getClassBytes(path: Path): Pair<Path, ByteArray>? {
|
||||||
return try {
|
return try {
|
||||||
jar.getInputStream(entry).use { stream ->
|
if (path.toString().endsWith(".class")) {
|
||||||
if (entry.name.endsWith(".class")) {
|
val bytes = Files.readAllBytes(path)
|
||||||
val bytes = stream.readBytes()
|
if (bytes.size < 4) {
|
||||||
if (bytes.size < 4) {
|
// Invalid class size
|
||||||
// Invalid class size
|
return null
|
||||||
return@use null
|
}
|
||||||
}
|
val cafebabe = String.format(
|
||||||
val cafebabe = String.format(
|
"%02X%02X%02X%02X",
|
||||||
"%02X%02X%02X%02X",
|
bytes[0],
|
||||||
bytes[0],
|
bytes[1],
|
||||||
bytes[1],
|
bytes[2],
|
||||||
bytes[2],
|
bytes[3]
|
||||||
bytes[3]
|
)
|
||||||
)
|
if (cafebabe.lowercase() != "cafebabe") {
|
||||||
if (cafebabe.lowercase() != "cafebabe") {
|
// Corrupted class
|
||||||
// Corrupted class
|
return null
|
||||||
return@use null
|
}
|
||||||
}
|
|
||||||
|
|
||||||
getNode(bytes).name to bytes
|
path to bytes
|
||||||
} else null
|
} else null
|
||||||
}
|
} catch (e: Exception) {
|
||||||
} catch (e: IOException) {
|
logger.error(e) { "Error loading class from Path: $path" }
|
||||||
logger.error(e) { "Error loading jar file" }
|
|
||||||
null
|
null
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun getNode(bytes: ByteArray): ClassNode {
|
|
||||||
val cr = ClassReader(bytes)
|
|
||||||
return ClassNode().also { cr.accept(it, ClassReader.EXPAND_FRAMES) }
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The path where replacement classes will reside
|
* The path where replacement classes will reside
|
||||||
*/
|
*/
|
||||||
@@ -153,9 +123,9 @@ object BytecodeEditor {
|
|||||||
*
|
*
|
||||||
* @return [ByteArray] with modified bytecode
|
* @return [ByteArray] with modified bytecode
|
||||||
*/
|
*/
|
||||||
private fun transform(classfileBuffer: ByteArray): ByteArray {
|
private fun transform(pair: Pair<Path, ByteArray>): Pair<Path, ByteArray> {
|
||||||
// Read the class and prepare to modify it
|
// Read the class and prepare to modify it
|
||||||
val cr = ClassReader(classfileBuffer)
|
val cr = ClassReader(pair.second)
|
||||||
val cw = ClassWriter(cr, 0)
|
val cw = ClassWriter(cr, 0)
|
||||||
// Modify the class
|
// Modify the class
|
||||||
cr.accept(
|
cr.accept(
|
||||||
@@ -277,51 +247,10 @@ object BytecodeEditor {
|
|||||||
},
|
},
|
||||||
0
|
0
|
||||||
)
|
)
|
||||||
return cw.toByteArray()
|
return pair.first to cw.toByteArray()
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
private fun write(pair: Pair<Path, ByteArray>) {
|
||||||
* Load non-class files from the jar, such as icons and the manifest
|
Files.write(pair.first, pair.second)
|
||||||
*
|
|
||||||
* @param [jarFile] The file to load resources from
|
|
||||||
*
|
|
||||||
* @return [Map] of resources
|
|
||||||
*/
|
|
||||||
private fun loadNonClasses(jarFile: File): Map<String, ByteArray> {
|
|
||||||
val entries = mutableMapOf<String, ByteArray>()
|
|
||||||
ZipInputStream(jarFile.inputStream()).use { stream ->
|
|
||||||
var nextEntry: ZipEntry?
|
|
||||||
while (stream.nextEntry.also { nextEntry = it } != null) {
|
|
||||||
nextEntry?.use(stream) { entry ->
|
|
||||||
// If it ends with class or is a directory ignore it
|
|
||||||
if (!entry.name.endsWith(".class") && !entry.isDirectory) {
|
|
||||||
val bytes = stream.readBytes()
|
|
||||||
entries[entry.name] = bytes
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return entries
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Save jar with modified content
|
|
||||||
*
|
|
||||||
* @param outBytes [Map] of names and [ByteArray]s of content to save inside the jar
|
|
||||||
* @param file JarFile to save to
|
|
||||||
*/
|
|
||||||
private fun saveAsJar(outBytes: Map<String, ByteArray>, file: File) {
|
|
||||||
JarOutputStream(file.outputStream()).use { out ->
|
|
||||||
outBytes.forEach { (entry, value) ->
|
|
||||||
// Append extension to class entries
|
|
||||||
out.putNextEntry(
|
|
||||||
ZipEntry(
|
|
||||||
entry + if (entry.contains(".")) "" else ".class"
|
|
||||||
)
|
|
||||||
)
|
|
||||||
out.write(value)
|
|
||||||
out.closeEntry()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -82,7 +82,7 @@ object PackageTools {
|
|||||||
)
|
)
|
||||||
handler.dump(errorFile, emptyArray<String>())
|
handler.dump(errorFile, emptyArray<String>())
|
||||||
} else {
|
} else {
|
||||||
BytecodeEditor.fixAndroidClasses(jarFilePath.toFile())
|
BytecodeEditor.fixAndroidClasses(jarFilePath)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user