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 {
|
||||
// Android stub library
|
||||
implementation(fileTree("lib/"))
|
||||
|
||||
// JSON
|
||||
compileOnly("com.google.code.gson:gson:2.8.6")
|
||||
|
||||
// XML
|
||||
compileOnly(group= "xmlpull", name= "xmlpull", version= "1.1.3.1")
|
||||
compileOnly("xmlpull:xmlpull:1.1.3.4a")
|
||||
|
||||
// Config API
|
||||
implementation(project(":AndroidCompat:Config"))
|
||||
|
||||
// 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
|
||||
compileOnly("androidx.annotation:annotation:1.2.0-alpha01")
|
||||
compileOnly("androidx.annotation:annotation:1.2.0")
|
||||
|
||||
// substitute for duktape-android
|
||||
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
|
||||
|
||||
// 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-serialization-jvm:$multiplatformSettingsVersion")
|
||||
|
||||
// Android version of SimpleDateFormat
|
||||
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 {
|
||||
// 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
|
||||
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
|
||||
## TL;DR
|
||||
- Loading sources' manga list is at least twice as fast
|
||||
- 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 ability to turn off cache for image requests
|
||||
<!-- TODO: fill before release -->
|
||||
|
||||
## Tachidesk-Server
|
||||
### Public API
|
||||
@@ -32,14 +66,14 @@
|
||||
#### Visible changes
|
||||
- (r790) nice looking progress percentage
|
||||
- (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)
|
||||
- (r793) Optimize images ([#32](https://github.com/Suwayomi/Tachidesk-WebUI/pull/21) by @phanirithvij)
|
||||
- (r794) try fix #30 ([#31](https://github.com/Suwayomi/Tachidesk-WebUI/pull/21) by @phanirithvij)
|
||||
- (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/32) 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
|
||||
- (r796) show proper display name for source
|
||||
- (r797) fail gracefully when a thumbnail has errors
|
||||
- (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
|
||||
|
||||
#### Bug fixes
|
||||
|
||||
+31
-14
@@ -1,8 +1,11 @@
|
||||
import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
|
||||
import org.jmailen.gradle.kotlinter.tasks.FormatTask
|
||||
import org.jmailen.gradle.kotlinter.tasks.LintTask
|
||||
|
||||
plugins {
|
||||
kotlin("jvm") version kotlinVersion
|
||||
kotlin("plugin.serialization") version kotlinVersion
|
||||
id("org.jmailen.kotlinter") version "3.6.0"
|
||||
}
|
||||
|
||||
allprojects {
|
||||
@@ -12,10 +15,8 @@ allprojects {
|
||||
|
||||
repositories {
|
||||
mavenCentral()
|
||||
maven("https://maven.google.com/")
|
||||
google()
|
||||
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) {
|
||||
apply(plugin = "org.jetbrains.kotlin.jvm")
|
||||
apply(plugin = "org.jetbrains.kotlin.plugin.serialization")
|
||||
|
||||
java {
|
||||
sourceCompatibility = JavaVersion.VERSION_1_8
|
||||
targetCompatibility = JavaVersion.VERSION_1_8
|
||||
}
|
||||
|
||||
tasks.withType<KotlinCompile> {
|
||||
kotlinOptions {
|
||||
jvmTarget = JavaVersion.VERSION_1_8.toString()
|
||||
tasks {
|
||||
withType<KotlinCompile> {
|
||||
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 {
|
||||
// Kotlin
|
||||
implementation(kotlin("stdlib-jdk8"))
|
||||
@@ -46,7 +65,7 @@ configure(projects) {
|
||||
testImplementation(kotlin("test-junit5"))
|
||||
|
||||
// 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-jdk8:$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-protobuf:$kotlinSerializationVersion")
|
||||
|
||||
|
||||
// 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
|
||||
implementation("org.slf4j:slf4j-api:1.7.30")
|
||||
implementation("ch.qos.logback:logback-classic:1.2.3")
|
||||
implementation("io.github.microutils:kotlin-logging:2.0.6")
|
||||
implementation("org.slf4j:slf4j-api:1.7.32")
|
||||
implementation("ch.qos.logback:logback-classic:1.2.6")
|
||||
implementation("io.github.microutils:kotlin-logging:2.0.11")
|
||||
|
||||
// ReactiveX
|
||||
implementation("io.reactivex:rxjava:1.3.8")
|
||||
@@ -70,7 +88,7 @@ configure(projects) {
|
||||
implementation("com.jakewharton.rxrelay:rxrelay:1.2.0")
|
||||
|
||||
// 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
|
||||
implementation("com.typesafe:config:1.4.1")
|
||||
@@ -87,7 +105,6 @@ configure(projects) {
|
||||
// APK parser
|
||||
implementation("net.dongliu:apk-parser:2.6.10")
|
||||
|
||||
|
||||
// dependency both in AndroidCompat and server, version locked by javalin
|
||||
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"
|
||||
|
||||
// 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
|
||||
val tachideskRevision = runCatching {
|
||||
|
||||
+1
-1
@@ -1,5 +1,5 @@
|
||||
distributionBase=GRADLE_USER_HOME
|
||||
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
|
||||
zipStorePath=wrapper/dists
|
||||
|
||||
+17
-50
@@ -1,25 +1,11 @@
|
||||
|
||||
import com.github.jengelman.gradle.plugins.shadow.tasks.ShadowJar
|
||||
import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
|
||||
import org.jmailen.gradle.kotlinter.tasks.FormatTask
|
||||
import org.jmailen.gradle.kotlinter.tasks.LintTask
|
||||
import de.undercouch.gradle.tasks.download.Download
|
||||
import java.time.Instant
|
||||
|
||||
plugins {
|
||||
application
|
||||
kotlin("plugin.serialization")
|
||||
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.2"
|
||||
}
|
||||
|
||||
repositories {
|
||||
maven {
|
||||
url = uri("https://repo1.maven.org/maven2/")
|
||||
}
|
||||
maven {
|
||||
url = uri("https://jitpack.io")
|
||||
}
|
||||
id("com.github.gmazzo.buildconfig") version "3.0.3"
|
||||
}
|
||||
|
||||
dependencies {
|
||||
@@ -33,8 +19,9 @@ dependencies {
|
||||
// Javalin api
|
||||
implementation("io.javalin:javalin:4.0.0")
|
||||
// jackson version locked by javalin, ref: `io.javalin.core.util.OptionalDependency`
|
||||
implementation("com.fasterxml.jackson.core:jackson-databind:2.12.4")
|
||||
implementation("com.fasterxml.jackson.module:jackson-module-kotlin:2.12.4")
|
||||
val jacksonVersion = "2.12.4"
|
||||
implementation("com.fasterxml.jackson.core:jackson-databind:$jacksonVersion")
|
||||
implementation("com.fasterxml.jackson.module:jackson-module-kotlin:$jacksonVersion")
|
||||
|
||||
// Exposed ORM
|
||||
val exposedVersion = "0.34.1"
|
||||
@@ -46,8 +33,7 @@ dependencies {
|
||||
implementation("com.h2database:h2:1.4.200")
|
||||
|
||||
// Exposed Migrations
|
||||
val exposedMigrationsVersion = "3.1.2"
|
||||
implementation("com.github.Suwayomi:exposed-migrations:$exposedMigrationsVersion")
|
||||
implementation("com.github.Suwayomi:exposed-migrations:3.1.2")
|
||||
|
||||
// tray icon
|
||||
implementation("com.dorkbox:SystemTray:4.1")
|
||||
@@ -57,8 +43,8 @@ dependencies {
|
||||
implementation("com.github.inorichi.injekt:injekt-core:65b0440")
|
||||
implementation("com.squareup.okhttp3:okhttp:4.9.1")
|
||||
implementation("io.reactivex:rxjava:1.3.8")
|
||||
implementation("org.jsoup:jsoup:1.14.1")
|
||||
implementation("com.google.code.gson:gson:2.8.7")
|
||||
implementation("org.jsoup:jsoup:1.14.2")
|
||||
implementation("com.google.code.gson:gson:2.8.8")
|
||||
implementation("com.github.salomonbrys.kotson:kotson:2.5.0")
|
||||
|
||||
// Sort
|
||||
@@ -131,29 +117,17 @@ tasks {
|
||||
shadowJar {
|
||||
manifest {
|
||||
attributes(
|
||||
mapOf(
|
||||
"Main-Class" to MainClass,
|
||||
"Implementation-Title" to rootProject.name,
|
||||
"Implementation-Vendor" to "The Suwayomi Project",
|
||||
"Specification-Version" to tachideskVersion,
|
||||
"Implementation-Version" to tachideskRevision
|
||||
)
|
||||
"Main-Class" to MainClass,
|
||||
"Implementation-Title" to rootProject.name,
|
||||
"Implementation-Vendor" to "The Suwayomi Project",
|
||||
"Specification-Version" to tachideskVersion,
|
||||
"Implementation-Version" to tachideskRevision
|
||||
)
|
||||
}
|
||||
archiveBaseName.set(rootProject.name)
|
||||
archiveVersion.set(tachideskVersion)
|
||||
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 {
|
||||
useJUnit()
|
||||
@@ -164,7 +138,7 @@ tasks {
|
||||
}
|
||||
|
||||
named("run") {
|
||||
dependsOn("formatKotlin", "lintKotlin")
|
||||
dependsOn(":formatKotlin", ":lintKotlin")
|
||||
}
|
||||
|
||||
named<Copy>("processResources") {
|
||||
@@ -172,7 +146,7 @@ tasks {
|
||||
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")
|
||||
dest("src/main/resources/WebUI.zip")
|
||||
|
||||
@@ -187,8 +161,9 @@ tasks {
|
||||
it.readText().trim()
|
||||
}
|
||||
|
||||
if (zipRevision == webUIRevisionTag)
|
||||
if (zipRevision == webUIRevisionTag) {
|
||||
shouldOverwrite = false
|
||||
}
|
||||
}
|
||||
|
||||
return shouldOverwrite
|
||||
@@ -196,12 +171,4 @@ tasks {
|
||||
|
||||
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.Request
|
||||
import okhttp3.Response
|
||||
import okhttp3.ResponseBody.Companion.asResponseBody
|
||||
import okhttp3.ResponseBody.Companion.toResponseBody
|
||||
import okio.buffer
|
||||
import okio.source
|
||||
import org.jetbrains.exposed.sql.insert
|
||||
import org.jetbrains.exposed.sql.insertAndGetId
|
||||
import org.jetbrains.exposed.sql.select
|
||||
@@ -50,7 +53,7 @@ import java.io.File
|
||||
import java.io.FileInputStream
|
||||
import java.io.FileNotFoundException
|
||||
import java.io.InputStream
|
||||
import java.net.URL
|
||||
import java.net.URLDecoder
|
||||
import java.util.Locale
|
||||
import java.util.concurrent.TimeUnit
|
||||
import java.util.zip.ZipFile
|
||||
@@ -342,18 +345,14 @@ class LocalSource : HttpSource() {
|
||||
throw Exception("Chapter not found")
|
||||
}
|
||||
|
||||
private fun getFormat(file: File): Format {
|
||||
return with(file) {
|
||||
when {
|
||||
isDirectory -> Format.Directory(file)
|
||||
extension.equals("zip", true) -> Format.Zip(file)
|
||||
extension.equals("cbz", true) -> Format.Zip(file)
|
||||
extension.equals("rar", true) -> Format.Rar(file)
|
||||
extension.equals("cbr", true) -> Format.Rar(file)
|
||||
extension.equals("epub", true) -> Format.Epub(file)
|
||||
private fun getFormat(file: File): Format = with(file) {
|
||||
when {
|
||||
isDirectory -> Format.Directory(this)
|
||||
extension.equals("zip", true) || extension.equals("cbz", true) -> Format.Zip(this)
|
||||
extension.equals("rar", true) || extension.equals("cbr", true) -> Format.Rar(this)
|
||||
extension.equals("epub", true) -> Format.Epub(this)
|
||||
|
||||
else -> throw Exception("Invalid chapter format")
|
||||
}
|
||||
else -> throw Exception("Invalid chapter format")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -439,19 +438,28 @@ class LocalSource : HttpSource() {
|
||||
}
|
||||
|
||||
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 {
|
||||
val request = chain.request()
|
||||
val url = request.url
|
||||
val fileUrl = restoreFileUrl(url.toString())
|
||||
val filePath = restoreFilePath(url.toString())
|
||||
return try {
|
||||
Response.Builder()
|
||||
.body(URL(fileUrl).readBytes().toResponseBody())
|
||||
.body(File(filePath).source().buffer().asResponseBody())
|
||||
.code(200)
|
||||
.message("Some file")
|
||||
.protocol(Protocol.HTTP_1_0)
|
||||
@@ -461,7 +469,7 @@ private object FileSystemInterceptor : Interceptor {
|
||||
Response.Builder()
|
||||
.body("".toResponseBody())
|
||||
.code(404)
|
||||
.message(e.message ?: "File not found ($fileUrl)")
|
||||
.message(e.message ?: "File not found ($filePath)")
|
||||
.protocol(Protocol.HTTP_1_0)
|
||||
.request(request)
|
||||
.build()
|
||||
|
||||
@@ -82,7 +82,7 @@ object PackageTools {
|
||||
)
|
||||
handler.dump(errorFile, emptyArray<String>())
|
||||
} 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.MethodVisitor
|
||||
import org.objectweb.asm.Opcodes
|
||||
import org.objectweb.asm.tree.ClassNode
|
||||
import suwayomi.tachidesk.manga.impl.util.storage.use
|
||||
import java.io.File
|
||||
import java.io.IOException
|
||||
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
|
||||
import java.nio.file.FileSystems
|
||||
import java.nio.file.Files
|
||||
import java.nio.file.Path
|
||||
import kotlin.streams.asSequence
|
||||
|
||||
object BytecodeEditor {
|
||||
private val logger = KotlinLogging.logger {}
|
||||
@@ -33,77 +28,52 @@ object BytecodeEditor {
|
||||
*
|
||||
* @param jarFile The JarFile to replace class references in
|
||||
*/
|
||||
fun fixAndroidClasses(jarFile: File) {
|
||||
val nodes = loadClasses(jarFile)
|
||||
.mapValues { (className, classFileBuffer) ->
|
||||
logger.trace { "Processing class $className" }
|
||||
transform(classFileBuffer)
|
||||
} + loadNonClasses(jarFile)
|
||||
|
||||
saveAsJar(nodes, jarFile)
|
||||
}
|
||||
|
||||
/**
|
||||
* 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()
|
||||
fun fixAndroidClasses(jarFile: Path) {
|
||||
FileSystems.newFileSystem(jarFile, null as ClassLoader?)?.use {
|
||||
Files.walk(it.getPath("/")).asSequence()
|
||||
.filterNotNull()
|
||||
.filterNot(Files::isDirectory)
|
||||
.mapNotNull(::getClassBytes)
|
||||
.map(::transform)
|
||||
.forEach(::write)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get class file in [jar] for [entry]
|
||||
* Get class bytes from a [Path]
|
||||
*
|
||||
* @param jar The jar to get the class from
|
||||
* @param entry The entry in the jar
|
||||
* @param path The path entry to get the class bytes from
|
||||
*
|
||||
* @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 {
|
||||
jar.getInputStream(entry).use { stream ->
|
||||
if (entry.name.endsWith(".class")) {
|
||||
val bytes = stream.readBytes()
|
||||
if (bytes.size < 4) {
|
||||
// Invalid class size
|
||||
return@use null
|
||||
}
|
||||
val cafebabe = String.format(
|
||||
"%02X%02X%02X%02X",
|
||||
bytes[0],
|
||||
bytes[1],
|
||||
bytes[2],
|
||||
bytes[3]
|
||||
)
|
||||
if (cafebabe.lowercase() != "cafebabe") {
|
||||
// Corrupted class
|
||||
return@use null
|
||||
}
|
||||
if (path.toString().endsWith(".class")) {
|
||||
val bytes = Files.readAllBytes(path)
|
||||
if (bytes.size < 4) {
|
||||
// Invalid class size
|
||||
return null
|
||||
}
|
||||
val cafebabe = String.format(
|
||||
"%02X%02X%02X%02X",
|
||||
bytes[0],
|
||||
bytes[1],
|
||||
bytes[2],
|
||||
bytes[3]
|
||||
)
|
||||
if (cafebabe.lowercase() != "cafebabe") {
|
||||
// Corrupted class
|
||||
return null
|
||||
}
|
||||
|
||||
getNode(bytes).name to bytes
|
||||
} else null
|
||||
}
|
||||
} catch (e: IOException) {
|
||||
logger.error(e) { "Error loading jar file" }
|
||||
path to bytes
|
||||
} else null
|
||||
} catch (e: Exception) {
|
||||
logger.error(e) { "Error loading class from Path: $path" }
|
||||
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
|
||||
*/
|
||||
@@ -153,9 +123,9 @@ object BytecodeEditor {
|
||||
*
|
||||
* @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
|
||||
val cr = ClassReader(classfileBuffer)
|
||||
val cr = ClassReader(pair.second)
|
||||
val cw = ClassWriter(cr, 0)
|
||||
// Modify the class
|
||||
cr.accept(
|
||||
@@ -277,51 +247,10 @@ object BytecodeEditor {
|
||||
},
|
||||
0
|
||||
)
|
||||
return cw.toByteArray()
|
||||
return pair.first to cw.toByteArray()
|
||||
}
|
||||
|
||||
/**
|
||||
* Load non-class files from the jar, such as icons and the manifest
|
||||
*
|
||||
* @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()
|
||||
}
|
||||
}
|
||||
private fun write(pair: Pair<Path, ByteArray>) {
|
||||
Files.write(pair.first, pair.second)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -82,7 +82,7 @@ object PackageTools {
|
||||
)
|
||||
handler.dump(errorFile, emptyArray<String>())
|
||||
} else {
|
||||
BytecodeEditor.fixAndroidClasses(jarFilePath.toFile())
|
||||
BytecodeEditor.fixAndroidClasses(jarFilePath)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user