Compare commits

..

2 Commits

Author SHA1 Message Date
Mitchell Syer
c1c60a69ee Update issue_moderator.yml 2024-09-02 21:47:31 -04:00
renovate[bot]
f7f6ee69f0 Update tachiyomiorg/issue-moderator-action action to v2 2024-09-01 23:58:10 +00:00
323 changed files with 4733 additions and 8960 deletions

44
.github/ISSUE_TEMPLATE/bug_report.md vendored Normal file
View File

@@ -0,0 +1,44 @@
---
name: "🐞 Bug report"
title: "[Bug] <short description>"
about: "Report a bug"
labels: "bug"
---
**PLEASE READ THIS**
I acknowledge that:
- I have updated to the latest version of the app.
- I have tried the troubleshooting guide described in `README.md`
- If this is a request for adding/changing an extension it should be brought up to your extension repo.
- If this is an issue with some extension not working properly, It does work inside Tachiyomi as intended.
- I have searched the existing issues and this is a new ticket **NOT** a duplicate or related to another open issue
- I will fill out the title and the information in this template
Note that the issue will be automatically closed if you do not fill out the title or requested information.
**DELETE THIS SECTION IF YOU HAVE READ AND ACKNOWLEDGED IT**
---
## Device information
- Suwayomi-Server version: (Example: v1.1.1-r1535-win32)
- Server Operating System: (Example: Ubuntu 20.04)
- Server Desktop Environment: N/A or (Example: Gnome 40)
- Server JVM version: bundled with win32 or (Example: Java 8 Update 281 or OpenJDK 8u281)
- Client Operating System: <usually the same as above Server Operating System>
- Client Web Browser: (Example: Google Chrome 89.0.4389.82)
## Steps to reproduce
1. First Step
2. Second Step
### Expected behavior
Describe what should have happened. Remove this line after you are done.
### Actual behavior
Describe what happens instead. Remove this line after you are done.
## Other details
Describe additional details If necessary. Remove this line after you are done.

View File

@@ -1,144 +0,0 @@
name: 🐞 Bug report
description: Report a bug in Suwayomi-Server
labels: [bug]
body:
- type: textarea
id: reproduce-steps
attributes:
label: Steps to reproduce
description: Provide an example of the issue.
placeholder: |
Example:
1. First step
2. Second step
3. Issue here
validations:
required: true
- type: textarea
id: expected-behavior
attributes:
label: Expected behavior
description: Explain what you should expect to happen.
placeholder: |
Example: "This should happen..."
validations:
required: true
- type: textarea
id: actual-behavior
attributes:
label: Actual behavior
description: Explain what actually happens.
placeholder: |
Example: "This happened instead..."
validations:
required: true
- type: input
id: suwayomi-server-version
attributes:
label: Suwayomi-Server version
description: You can find your Suwayomi-Server version in **More → About**.
placeholder: |
Example: "v2.0.1727"
validations:
required: true
- type: input
id: server-os
attributes:
label: Server operating system
description: The operating system on which Suwayomi-Server is running on
placeholder: |
Example: "Windows 11 Pro 24H2 | Ubuntu 24.04.2 LTS"
validations:
required: true
- type: input
id: server-desktop-environment
attributes:
label: Server Desktop Environment
description:
placeholder: |
Example: "Gnome 40"
validations:
required: false
- type: input
id: server-jvm-version
attributes:
label: Server JVM version
description: The java version used to run Suwayomi-Server
placeholder: |
Example: "openjdk 21.0.5 2024-10-15 LTS"
validations:
required: true
- type: input
id: client-name
attributes:
label: Used client name
description:
placeholder: |
Example: "Suwayomi-WebUI"
validations:
required: true
- type: input
id: client-version
attributes:
label: Client version
description:
placeholder: |
Example: "v1.2.3"
validations:
required: true
- type: input
id: client-browser
attributes:
label: Used web browser
description: The browser which is used to open Suwayomi-WebUI
placeholder: |
Example: "Chrome 134.0.6998.118 (64-Bit) | FireFox 136.0.2 (64-Bit) | Electron v35.0.2"
validations:
required: true
- type: input
id: client-os
attributes:
label: Client operating system
description: The system on which the Suwayomi-WebUI is running on
placeholder: |
Example: "Windows 11 Pro 24H2 | Ubuntu 24.04.2 LTS"
validations:
required: true
- type: textarea
id: other-details
attributes:
label: Other details
description: The more information that gets provided the better, especially via videos and images
placeholder: |
Additional details and attachments.
- type: checkboxes
id: acknowledgements
attributes:
label: Acknowledgements
description: Read this carefully, we will close and ignore your issue if you skimmed through this.
options:
- label: I have searched the existing issues and this is a new ticket, **NOT** a duplicate or related to another open or closed issue.
required: true
- label: I have written a short but informative title (ideally less than ~100 characters).
required: true
- label: I have tried the troubleshooting guide described in [README.md](https://github.com/Suwayomi/Suwayomi-Server?tab=readme-ov-file#troubleshooting-and-support)
required: true
- label: I have updated to the **[latest version](https://github.com/suwayomi/suwayomi-server/releases/latest)**.
required: true
- label: I have filled out all of the requested information in this form, including specific version numbers.
required: true
- label: I understand that **Suwayomi does not have or fix any extensions**, and I **will not receive help** for any issues related to sources or extensions.
required: true

View File

@@ -1,5 +1 @@
blank_issues_enabled: false
contact_links:
- name: ☎️ Support
url: https://discord.gg/DDZdqZWaHA
about: Join our discord to get help for anything that is not a bug or a feature request

View File

@@ -0,0 +1,29 @@
---
name: "🌟 Feature request"
title: "[Feature Request] <short description>"
about: "Suggest a feature to improve the project"
labels: "enhancement"
---
**PLEASE READ THIS**
I acknowledge that:
- I have updated to the latest version of the app.
- I have tried the troubleshooting guide described in `README.md`
- If this is a request for adding/changing an extension it should be brought up to your extension repo.
- If this is an issue with some extension not working properly, It does work in Tachiyomi application as intended.
- I have searched the existing issues and this is a new ticket **NOT** a duplicate or related to another open issue
- I will fill out the title and the information in this template
Note that the issue will be automatically closed if you do not fill out the title or requested information.
**DELETE THIS SECTION IF YOU HAVE READ AND ACKNOWLEDGED IT**
---
## What feature should be added to Suwayomi?
Explain What the feature is and how it should work in detail. Remove this line after you are done.
## Why/Project's Benefit/Existing Problem
Explain why this should be added. Remove this line after you are done.

View File

@@ -1,37 +0,0 @@
name: 🌟 Feature request
description: Suggest a feature to improve Suwayomi-Server
labels: [enhancement]
body:
- type: textarea
id: feature-description
attributes:
label: Describe your suggested feature
description: How can Suwayomi-Server be improved?
placeholder: |
Example:
"It should work like this..."
validations:
required: true
- type: textarea
id: other-details
attributes:
label: Other details
placeholder: |
Additional details and attachments.
- type: checkboxes
id: acknowledgements
attributes:
label: Acknowledgements
description: Read this carefully, we will close and ignore your issue if you skimmed through this.
options:
- label: I have searched the existing issues and this is a new ticket, **NOT** a duplicate or related to another open or closed issue.
required: true
- label: I have written a short but informative title (ideally less than ~100 characters).
required: true
- label: I have updated to the **[latest version](https://github.com/suwayomi/suwayomi-server/releases/latest)**.
required: true
- label: I have filled out all of the requested information in this form, including specific version numbers.
required: true

View File

@@ -17,7 +17,7 @@ jobs:
uses: actions/checkout@v4
- name: Validate Gradle Wrapper
uses: gradle/wrapper-validation-action@v3
uses: gradle/wrapper-validation-action@v1
build:
name: Build pull request
@@ -32,15 +32,12 @@ jobs:
path: master
fetch-depth: 0
- name: Set up JDK
- name: Set up JDK 1.8
uses: actions/setup-java@v4
with:
java-version: 21
java-version: 8
distribution: 'temurin'
- name: Setup Gradle
uses: gradle/actions/setup-gradle@v4
- name: Copy CI gradle.properties
run: |
cd master
@@ -48,6 +45,8 @@ jobs:
cp .github/runner-files/ci-gradle.properties ~/.gradle/gradle.properties
- name: Build Jar
working-directory: master
run: ./gradlew ktlintCheck :server:shadowJar --stacktrace
uses: gradle/gradle-build-action@v2
with:
build-root-directory: master
arguments: ktlintCheck :server:shadowJar --stacktrace

View File

@@ -15,10 +15,10 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Clone repo
uses: actions/checkout@v4
uses: actions/checkout@v3
- name: Validate Gradle Wrapper
uses: gradle/wrapper-validation-action@v3
uses: gradle/wrapper-validation-action@v1
build:
name: Build Jar
@@ -32,15 +32,12 @@ jobs:
path: master
fetch-depth: 0
- name: Set up JDK
- name: Set up JDK 1.8
uses: actions/setup-java@v4
with:
java-version: 21
java-version: 8
distribution: 'temurin'
- name: Setup Gradle
uses: gradle/actions/setup-gradle@v4
- name: Copy CI gradle.properties
run: |
cd master
@@ -48,20 +45,22 @@ jobs:
cp .github/runner-files/ci-gradle.properties ~/.gradle/gradle.properties
- name: Build Jar
uses: gradle/gradle-build-action@v2
env:
ProductBuildType: "Preview"
working-directory: master
run: ./gradlew :server:shadowJar --stacktrace
with:
build-root-directory: master
arguments: :server:shadowJar --stacktrace
- name: Upload Jar
uses: actions/upload-artifact@v4
uses: actions/upload-artifact@v3
with:
name: jar
path: master/server/build/*.jar
if-no-files-found: error
- name: Upload icons
uses: actions/upload-artifact@v4
uses: actions/upload-artifact@v3
with:
name: icon
path: master/server/src/main/resources/icon
@@ -71,43 +70,12 @@ jobs:
run: tar -cvzf scripts.tar.gz -C master/ scripts/
- name: Upload scripts.tar.gz
uses: actions/upload-artifact@v4
uses: actions/upload-artifact@v3
with:
name: scripts
path: scripts.tar.gz
if-no-files-found: error
jlink:
runs-on: ${{ matrix.os }}
strategy:
matrix:
include:
- os: ubuntu-latest
name: linux-x64
- os: windows-latest
name: windows-x64
- os: macos-14
name: macOS-arm64
- os: macos-13
name: macOS-x64
os: [ubuntu-latest, windows-latest, macos-14, macos-13]
steps:
- name: Set up JDK
uses: actions/setup-java@v4
with:
java-version: 21
distribution: 'temurin'
- name: Package JDK
run: jlink --add-modules java.base,java.compiler,java.datatransfer,java.desktop,java.instrument,java.logging,java.management,java.naming,java.prefs,java.scripting,java.se,java.security.jgss,java.security.sasl,java.sql,java.transaction.xa,java.xml,jdk.attach,jdk.crypto.ec,jdk.jdi,jdk.management,jdk.net,jdk.random,jdk.unsupported,jdk.unsupported.desktop,jdk.zipfs --output suwa --strip-debug --no-man-pages --no-header-files --compress=2
- name: Upload JRE package
uses: actions/upload-artifact@v4
with:
name: ${{ matrix.name }}-jre
path: suwa
bundle:
strategy:
fail-fast: false
@@ -119,32 +87,26 @@ jobs:
- macOS-x64
- macOS-arm64
- windows-x64
- windows-x86
name: Make ${{ matrix.os }} release
needs: [build,jlink]
needs: build
runs-on: ubuntu-latest
steps:
- name: Download Jar
uses: actions/download-artifact@v4
uses: actions/download-artifact@v3
with:
name: jar
path: server/build
- name: Download JRE
uses: actions/download-artifact@v4
if: matrix.os != 'linux-assets' && matrix.os != 'debian-all'
with:
name: ${{ matrix.os }}-jre
path: jre
- name: Download icons
uses: actions/download-artifact@v4
uses: actions/download-artifact@v3
with:
name: icon
path: server/src/main/resources/icon
- name: Download scripts.tar.gz
uses: actions/download-artifact@v4
uses: actions/download-artifact@v3
with:
name: scripts
@@ -155,7 +117,7 @@ jobs:
scripts/bundler.sh -o upload/ ${{ matrix.os }}
- name: Upload ${{ matrix.os }} release
uses: actions/upload-artifact@v4
uses: actions/upload-artifact@v3
with:
name: ${{ matrix.os }}
path: upload/*
@@ -165,37 +127,41 @@ jobs:
needs: bundle
runs-on: ubuntu-latest
steps:
- uses: actions/download-artifact@v4
- uses: actions/download-artifact@v3
with:
name: jar
path: release
- uses: actions/download-artifact@v4
- uses: actions/download-artifact@v3
with:
name: debian-all
path: release
- uses: actions/download-artifact@v4
- uses: actions/download-artifact@v3
with:
name: linux-assets
path: release
- uses: actions/download-artifact@v4
- uses: actions/download-artifact@v3
with:
name: linux-x64
path: release
- uses: actions/download-artifact@v4
- uses: actions/download-artifact@v3
with:
name: macOS-x64
path: release
- uses: actions/download-artifact@v4
- uses: actions/download-artifact@v3
with:
name: macOS-arm64
path: release
- uses: actions/download-artifact@v4
- uses: actions/download-artifact@v3
with:
name: windows-x64
path: release
- uses: actions/download-artifact@v3
with:
name: windows-x86
path: release
- name: Checkout Preview branch
uses: actions/checkout@v4
uses: actions/checkout@v3
with:
repository: "Suwayomi/Suwayomi-Server-preview"
ref: main
@@ -227,7 +193,7 @@ jobs:
git push origin $TAG
- name: Upload Preview Release
uses: softprops/action-gh-release@v2
uses: softprops/action-gh-release@v1
with:
token: ${{ secrets.DEPLOY_PREVIEW_TOKEN }}
repository: "Suwayomi/Suwayomi-Server-preview"

44
.github/workflows/issue_moderator.yml vendored Normal file
View File

@@ -0,0 +1,44 @@
name: Issue moderator
on:
issues:
types: [opened, edited, reopened]
issue_comment:
types: [created]
jobs:
autoclose:
runs-on: ubuntu-latest
steps:
- name: Moderate issues
uses: tachiyomiorg/issue-moderator-action@v2
with:
repo-token: ${{ github.token }}
auto-close-rules: |
[
{
"type": "title",
"regex": ".*<short description>.*",
"message": "You did not fill out the description in the title"
},
{
"type": "title",
"regex": ".*(<|>)+.*",
"message": "You did not remove Angle brackets(< and >) from the title"
},
{
"type": "body",
"regex": ".*DELETE THIS SECTION IF YOU HAVE READ AND ACKNOWLEDGED IT.*",
"message": "The acknowledgment section was not removed"
},
{
"type": "body",
"regex": ".*(Suwayomi-Server version|Server Operating System|Server Desktop Environment|Server JVM version|Client Operating System|Client Web Browser):.*(\\(Example:|<usually).*",
"message": "The requested information was not filled out"
},
{
"type": "body",
"regex": ".*Remove this line after you are done.*",
"message": "The lines requesting to be removed were not removed."
}
]

View File

@@ -19,7 +19,7 @@ jobs:
uses: actions/checkout@v4
- name: Validate Gradle Wrapper
uses: gradle/wrapper-validation-action@v3
uses: gradle/wrapper-validation-action@v1
build:
name: Build Jar
@@ -33,15 +33,12 @@ jobs:
path: master
fetch-depth: 0
- name: Set up JDK
- name: Set up JDK 1.8
uses: actions/setup-java@v4
with:
java-version: 21
java-version: 8
distribution: 'temurin'
- name: Setup Gradle
uses: gradle/actions/setup-gradle@v4
- name: Copy CI gradle.properties
run: |
cd master
@@ -50,10 +47,12 @@ jobs:
~/.gradle/gradle.properties
- name: Build and copy webUI, Build Jar
uses: gradle/gradle-build-action@v2
env:
ProductBuildType: "Stable"
working-directory: master
run: ./gradlew :server:downloadWebUI :server:shadowJar --stacktrace
with:
build-root-directory: master
arguments: :server:downloadWebUI :server:shadowJar --stacktrace
- name: Upload Jar
uses: actions/upload-artifact@v4
@@ -79,37 +78,6 @@ jobs:
path: scripts.tar.gz
if-no-files-found: error
jlink:
runs-on: ${{ matrix.os }}
strategy:
matrix:
include:
- os: ubuntu-latest
name: linux-x64
- os: windows-latest
name: windows-x64
- os: macos-14
name: macOS-arm64
- os: macos-13
name: macOS-x64
os: [ubuntu-latest, windows-latest, macos-14, macos-13]
steps:
- name: Set up JDK
uses: actions/setup-java@v4
with:
java-version: 21
distribution: 'temurin'
- name: Package JDK
run: jlink --add-modules java.base,java.compiler,java.datatransfer,java.desktop,java.instrument,java.logging,java.management,java.naming,java.prefs,java.scripting,java.se,java.security.jgss,java.security.sasl,java.sql,java.transaction.xa,java.xml,jdk.attach,jdk.crypto.ec,jdk.jdi,jdk.management,jdk.net,jdk.random,jdk.unsupported,jdk.unsupported.desktop,jdk.zipfs --output suwa --strip-debug --no-man-pages --no-header-files --compress=2
- name: Upload JDK package
uses: actions/upload-artifact@v4
with:
name: ${{ matrix.name }}-jre
path: suwa
bundle:
strategy:
fail-fast: false
@@ -121,9 +89,10 @@ jobs:
- macOS-x64
- macOS-arm64
- windows-x64
- windows-x86
name: Make ${{ matrix.os }} release
needs: [build, jlink]
needs: build
runs-on: ubuntu-latest
steps:
- name: Download Jar
@@ -132,13 +101,6 @@ jobs:
name: jar
path: server/build
- name: Download JRE
uses: actions/download-artifact@v4
if: matrix.os != 'linux-assets' && matrix.os != 'debian-all'
with:
name: ${{ matrix.os }}-jre
path: jre
- name: Download icons
uses: actions/download-artifact@v4
with:
@@ -196,12 +158,16 @@ jobs:
with:
name: windows-x64
path: release
- uses: actions/download-artifact@v4
with:
name: windows-x86
path: release
- name: Generate checksums
run: cd release && sha256sum * > Checksums.sha256
- name: Release
uses: softprops/action-gh-release@v2
uses: softprops/action-gh-release@v1
with:
token: ${{ secrets.DEPLOY_RELEASE_TOKEN }}
draft: true

1
.gitignore vendored
View File

@@ -5,7 +5,6 @@ gradle.properties
.fleet
# But we need these
!.idea/runConfigurations
.kotlin
# Ignore Gradle build output directory
build

View File

@@ -1,19 +1,7 @@
plugins {
id(
libs.plugins.kotlin.jvm
.get()
.pluginId,
)
id(
libs.plugins.kotlin.serialization
.get()
.pluginId,
)
id(
libs.plugins.ktlint
.get()
.pluginId,
)
id(libs.plugins.kotlin.jvm.get().pluginId)
id(libs.plugins.kotlin.serialization.get().pluginId)
id(libs.plugins.ktlint.get().pluginId)
}
dependencies {

View File

@@ -0,0 +1,13 @@
package xyz.nulldev.ts.config
import org.kodein.di.DI
import org.kodein.di.bind
import org.kodein.di.singleton
class ConfigKodeinModule {
fun create() =
DI.Module("ConfigManager") {
// Config module
bind<ConfigManager>() with singleton { GlobalConfigManager }
}
}

View File

@@ -14,9 +14,9 @@ import com.typesafe.config.ConfigValue
import com.typesafe.config.ConfigValueFactory
import com.typesafe.config.parser.ConfigDocument
import com.typesafe.config.parser.ConfigDocumentFactory
import io.github.oshai.kotlinlogging.KotlinLogging
import kotlinx.coroutines.sync.Mutex
import kotlinx.coroutines.sync.withLock
import mu.KotlinLogging
import java.io.File
/**
@@ -47,10 +47,11 @@ open class ConfigManager {
@Suppress("UNCHECKED_CAST")
fun <T : ConfigModule> module(type: Class<T>): T = loadedModules[type] as T
private fun getUserConfig(): Config =
userConfigFile.let {
private fun getUserConfig(): Config {
return userConfigFile.let {
ConfigFactory.parseFile(it)
}
}
/**
* Load configs
@@ -71,8 +72,7 @@ open class ConfigManager {
val userConfig = getUserConfig()
val config =
ConfigFactory
.empty()
ConfigFactory.empty()
.withFallback(baseConfig)
.withFallback(userConfig)
.withFallback(compatConfig)
@@ -153,13 +153,11 @@ open class ConfigManager {
}
var newUserConfigDoc: ConfigDocument = resetUserConfig(false)
userConfig
.entrySet()
.filter {
serverConfig.hasPath(
it.key,
)
}.forEach { newUserConfigDoc = newUserConfigDoc.withValue(it.key, it.value) }
userConfig.entrySet().filter {
serverConfig.hasPath(
it.key,
)
}.forEach { newUserConfigDoc = newUserConfigDoc.withValue(it.key, it.value) }
userConfigFile.writeText(newUserConfigDoc.render())
}

View File

@@ -1,9 +0,0 @@
package xyz.nulldev.ts.config
import org.koin.core.module.Module
import org.koin.dsl.module
fun configManagerModule(): Module =
module {
single<ConfigManager> { GlobalConfigManager }
}

View File

@@ -17,25 +17,17 @@ import kotlin.reflect.KProperty
* Abstract config module.
*/
@Suppress("UNUSED_PARAMETER")
abstract class ConfigModule(
getConfig: () -> Config,
)
abstract class ConfigModule(getConfig: () -> Config)
/**
* Abstract jvm-commandline-argument-overridable config module.
*/
abstract class SystemPropertyOverridableConfigModule(
getConfig: () -> Config,
moduleName: String,
) : ConfigModule(getConfig) {
abstract class SystemPropertyOverridableConfigModule(getConfig: () -> Config, moduleName: String) : ConfigModule(getConfig) {
val overridableConfig = SystemPropertyOverrideDelegate(getConfig, moduleName)
}
/** Defines a config property that is overridable with jvm `-D` commandline arguments prefixed with [CONFIG_PREFIX] */
class SystemPropertyOverrideDelegate(
val getConfig: () -> Config,
val moduleName: String,
) {
class SystemPropertyOverrideDelegate(val getConfig: () -> Config, val moduleName: String) {
inline operator fun <R, reified T> getValue(
thisRef: R,
property: KProperty<*>,

View File

@@ -15,20 +15,20 @@ import ch.qos.logback.core.rolling.RollingFileAppender
import ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy
import ch.qos.logback.core.util.FileSize
import com.typesafe.config.Config
import io.github.oshai.kotlinlogging.DelegatingKLogger
import io.github.oshai.kotlinlogging.KotlinLogging
import mu.KotlinLogging
import org.slf4j.Logger
import org.slf4j.LoggerFactory
private fun fileSizeValueOfOrDefault(
fileSizeStr: String,
default: String,
): FileSize =
try {
): FileSize {
return try {
FileSize.valueOf(fileSizeStr)
} catch (e: IllegalArgumentException) {
FileSize.valueOf(default)
}
}
private const val FILE_APPENDER_NAME = "SuwayomiDefaultAppender"
@@ -61,7 +61,7 @@ private fun createRollingFileAppender(
context = logContext
setParent(appender)
fileNamePattern = "$logDirPath/${logFilename}_%d{yyyy-MM-dd}_%i.log.gz"
maxHistory = maxFiles.coerceAtLeast(0)
maxHistory = maxFiles
setMaxFileSize(fileSizeValueOfOrDefault(maxFileSize, "10mb"))
setTotalSizeCap(fileSizeValueOfOrDefault(maxTotalSize, "100mb"))
start()
@@ -73,8 +73,9 @@ private fun createRollingFileAppender(
return appender
}
private fun getBaseLogger(): ch.qos.logback.classic.Logger =
((KotlinLogging.logger(Logger.ROOT_LOGGER_NAME) as DelegatingKLogger<*>).underlyingLogger as ch.qos.logback.classic.Logger)
private fun getBaseLogger(): ch.qos.logback.classic.Logger {
return (KotlinLogging.logger(Logger.ROOT_LOGGER_NAME).underlyingLogger as ch.qos.logback.classic.Logger)
}
private fun getLogger(name: String): ch.qos.logback.classic.Logger {
val context = LoggerFactory.getILoggerFactory() as LoggerContext

View File

@@ -1,19 +1,7 @@
plugins {
id(
libs.plugins.kotlin.jvm
.get()
.pluginId,
)
id(
libs.plugins.kotlin.serialization
.get()
.pluginId,
)
id(
libs.plugins.ktlint
.get()
.pluginId,
)
id(libs.plugins.kotlin.jvm.get().pluginId)
id(libs.plugins.kotlin.serialization.get().pluginId)
id(libs.plugins.ktlint.get().pluginId)
}
dependencies {
@@ -36,8 +24,8 @@ dependencies {
// AndroidX annotations
compileOnly(libs.android.annotations)
// substitute for duktape-android/quickjs
implementation(libs.bundles.polyglot)
// substitute for duktape-android
implementation(libs.bundles.rhino)
// Kotlin wrapper around Java Preferences, makes certain things easier
implementation(libs.bundles.settings)

View File

@@ -25,7 +25,7 @@ import android.os.IBinder;
import android.util.Log;
import kotlin.NotImplementedError;
import xyz.nulldev.androidcompat.service.ServiceSupport;
import xyz.nulldev.androidcompat.util.KoinGlobalHelper;
import xyz.nulldev.androidcompat.util.KodeinGlobalHelper;
import java.io.FileDescriptor;
import java.io.PrintWriter;
@@ -299,7 +299,7 @@ import java.lang.annotation.RetentionPolicy;
*/
public abstract class Service extends ContextWrapper implements ComponentCallbacks2 {
private static final ServiceSupport serviceSupport = KoinGlobalHelper.instance(ServiceSupport.class);
private static final ServiceSupport serviceSupport = KodeinGlobalHelper.instance(ServiceSupport.class);
private static final String TAG = "Service";
/**
@@ -328,7 +328,7 @@ public abstract class Service extends ContextWrapper implements ComponentCallbac
public Service() {
//==================[THIS LINE MODIFIED FROM ANDROID SOURCE!]==================
//Service must be initialized with a base context!
super(KoinGlobalHelper.instance(Context.class));
super(KodeinGlobalHelper.instance(Context.class));
}
/** Return the application that owns this service. */
public final Application getApplication() {

View File

@@ -1,7 +1,7 @@
package android.os;
import xyz.nulldev.androidcompat.io.AndroidFiles;
import xyz.nulldev.androidcompat.util.KoinGlobalHelper;
import xyz.nulldev.androidcompat.util.KodeinGlobalHelper;
import java.io.File;
@@ -9,7 +9,7 @@ import java.io.File;
* Android compatibility layer for files
*/
public class Environment {
private static AndroidFiles androidFiles = KoinGlobalHelper.instance(AndroidFiles.class);
private static AndroidFiles androidFiles = KodeinGlobalHelper.instance(AndroidFiles.class);
public static String DIRECTORY_ALARMS = getHomeDirectory("Alarms").getAbsolutePath();
public static String DIRECTORY_DCIM = getHomeDirectory("DCIM").getAbsolutePath();

View File

@@ -1,99 +1,69 @@
package app.cash.quickjs;
import org.graalvm.polyglot.*;
import org.mozilla.javascript.ConsString;
import org.mozilla.javascript.NativeArray;
import javax.script.ScriptEngine;
import javax.script.ScriptEngineManager;
import java.io.Closeable;
import java.math.BigInteger;
import java.util.Arrays;
public final class QuickJs implements Closeable {
private Context context;
private ScriptEngine engine;
public static QuickJs create() {
return new QuickJs();
return new QuickJs(new ScriptEngineManager());
}
public QuickJs() {
this.context = Context
.newBuilder("js")
.allowHostAccess(HostAccess.ALL)
.allowPolyglotAccess(PolyglotAccess.NONE)
.allowHostClassLoading(false)
.build();
context.enter();
public QuickJs(ScriptEngineManager manager) {
this.engine = manager.getEngineByName("rhino");
}
public Object evaluate(String script, String ignoredFileName) {
public Object evaluate(String script, String fileName) {
return this.evaluate(script);
}
public Object evaluate(String script) {
try {
Value value = context.eval("js", script);
Object value = engine.eval(script);
return translateType(value);
} catch (Exception exception) {
throw new QuickJsException(exception.getMessage(), exception);
}
}
private Object translateType(Value obj) {
if (obj.isBoolean()) {
return obj.asBoolean();
} else if (obj.hasArrayElements()) {
if (obj.getArraySize() == 0) {
return new int[0];
} else {
Value element = obj.getArrayElement(0);
if (element.isBoolean()) {
return obj.as(boolean[].class);
} else if (element.isNumber()) {
if (element.fitsInInt()) {
return obj.as(int[].class);
} else if (element.fitsInBigInteger()) {
return Arrays.stream(obj.as(BigInteger[].class)).map(BigInteger::longValue).toArray();
} else {
return obj.as(double[].class);
}
} else if (element.isHostObject()) {
return obj.as(Object[].class);
} else if (element.isString()) {
return obj.as(String[].class);
}
private Object translateType(Object obj) {
if (obj instanceof NativeArray) {
NativeArray array = (NativeArray) obj;
long length = array.getLength();
Object[] objects = new Object[(int) length];
for (int i = 0; i < (int) length; i++) {
objects[i] = translateType(array.get(i));
}
} else if (obj.isNumber()) {
if (obj.fitsInInt()) {
return obj.asInt();
} else if (obj.fitsInBigInteger()) {
return obj.asBigInteger().longValue();
} else {
return obj.asDouble();
}
} else if (obj.isHostObject()) {
return obj.asHostObject();
} else if (obj.isString()) {
return obj.asString();
return objects;
}
if (obj instanceof ConsString) {
ConsString consString = (ConsString) obj;
return consString.toString();
}
if (obj instanceof Long) {
Long value = (Long) obj;
return value.intValue();
}
return obj;
}
public byte[] compile(String sourceCode, String ignoredFileName) {
public byte[] compile(String sourceCode, String fileName) {
return sourceCode.getBytes();
}
public Object execute(byte[] bytecode) {
return this.evaluate(new String(bytecode));
}
public <T> void set(String name, Class<T> ignoredType, T object) {
context.getBindings("js").putMember(name, object);
}
@Override
public void close() {
if (this.context != null) {
this.context.leave();
this.context.close();
this.context = null;
}
this.engine = null;
}
}

View File

@@ -17,7 +17,7 @@ package dalvik.system;
import org.jetbrains.annotations.Nullable;
import xyz.nulldev.androidcompat.pm.PackageController;
import xyz.nulldev.androidcompat.util.KoinGlobalHelper;
import xyz.nulldev.androidcompat.util.KodeinGlobalHelper;
import java.io.File;
import java.io.IOException;
@@ -33,7 +33,7 @@ import java.util.Enumeration;
* {@link ClassLoader} implementations.
*/
public class BaseDexClassLoader extends ClassLoader {
private PackageController controller = KoinGlobalHelper.instance(PackageController.class);
private PackageController controller = KodeinGlobalHelper.instance(PackageController.class);
private final URLClassLoader realClassloader;

View File

@@ -1,11 +1,13 @@
package xyz.nulldev.androidcompat
import android.app.Application
import org.koin.mp.KoinPlatformTools
import org.kodein.di.DI
import org.kodein.di.conf.global
import org.kodein.di.instance
import xyz.nulldev.androidcompat.androidimpl.CustomContext
class AndroidCompat {
val context: CustomContext by KoinPlatformTools.defaultContext().get().inject()
val context: CustomContext by DI.global.instance()
fun startApp(application: Application) {
application.attach(context)

View File

@@ -1,5 +1,7 @@
package xyz.nulldev.androidcompat
import org.kodein.di.DI
import org.kodein.di.conf.global
import xyz.nulldev.androidcompat.config.ApplicationInfoConfigModule
import xyz.nulldev.androidcompat.config.FilesConfigModule
import xyz.nulldev.androidcompat.config.SystemConfigModule
@@ -10,6 +12,8 @@ import xyz.nulldev.ts.config.GlobalConfigManager
*/
class AndroidCompatInitializer {
fun init() {
DI.global.addImport(AndroidCompatModule().create())
// Register config modules
GlobalConfigManager.registerModules(
FilesConfigModule.register(GlobalConfigManager.config),

View File

@@ -1,8 +1,11 @@
package xyz.nulldev.androidcompat
import android.content.Context
import org.koin.core.module.Module
import org.koin.dsl.module
import org.kodein.di.DI
import org.kodein.di.bind
import org.kodein.di.conf.global
import org.kodein.di.instance
import org.kodein.di.singleton
import xyz.nulldev.androidcompat.androidimpl.CustomContext
import xyz.nulldev.androidcompat.androidimpl.FakePackageManager
import xyz.nulldev.androidcompat.info.ApplicationInfoImpl
@@ -14,19 +17,25 @@ import xyz.nulldev.androidcompat.service.ServiceSupport
* AndroidCompatModule
*/
fun androidCompatModule(): Module =
module {
single { AndroidFiles() }
class AndroidCompatModule {
fun create() =
DI.Module("AndroidCompat") {
bind<AndroidFiles>() with singleton { AndroidFiles() }
single { ApplicationInfoImpl(get()) }
bind<ApplicationInfoImpl>() with singleton { ApplicationInfoImpl() }
single { ServiceSupport() }
bind<ServiceSupport>() with singleton { ServiceSupport() }
single { FakePackageManager() }
bind<FakePackageManager>() with singleton { FakePackageManager() }
single { PackageController() }
bind<PackageController>() with singleton { PackageController() }
single { CustomContext() }
single<Context> { get<CustomContext>() }
}
// Context
bind<CustomContext>() with singleton { CustomContext() }
bind<Context>() with
singleton {
val context: Context by DI.global.instance<CustomContext>()
context
}
}
}

View File

@@ -32,14 +32,15 @@ import android.os.*;
import android.view.Display;
import android.view.DisplayAdjustments;
import org.jetbrains.annotations.NotNull;
import org.koin.core.Koin;
import org.jetbrains.annotations.Nullable;
import org.kodein.di.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import xyz.nulldev.androidcompat.info.ApplicationInfoImpl;
import xyz.nulldev.androidcompat.io.AndroidFiles;
import xyz.nulldev.androidcompat.io.sharedprefs.JavaSharedPreferences;
import xyz.nulldev.androidcompat.service.ServiceSupport;
import xyz.nulldev.androidcompat.util.KoinGlobalHelper;
import xyz.nulldev.androidcompat.util.KodeinGlobalHelper;
import java.io.*;
import java.util.HashMap;
@@ -50,25 +51,26 @@ import java.util.Map;
* Custom context implementation.
*
*/
public class CustomContext extends Context {
private final Koin koin;
public class CustomContext extends Context implements DIAware {
private final DI kodein;
public CustomContext() {
this(KoinGlobalHelper.koin());
this(KodeinGlobalHelper.kodein());
}
public CustomContext(Koin koin) {
this.koin = koin;
public CustomContext(DI kodein) {
this.kodein = kodein;
//Init configs
androidFiles = KoinGlobalHelper.instance(AndroidFiles.class, getDi());
applicationInfo = KoinGlobalHelper.instance(ApplicationInfoImpl.class, getDi());
serviceSupport = KoinGlobalHelper.instance(ServiceSupport.class, getDi());
fakePackageManager = KoinGlobalHelper.instance(FakePackageManager.class, getDi());
androidFiles = KodeinGlobalHelper.instance(AndroidFiles.class, getDi());
applicationInfo = KodeinGlobalHelper.instance(ApplicationInfoImpl.class, getDi());
serviceSupport = KodeinGlobalHelper.instance(ServiceSupport.class, getDi());
fakePackageManager = KodeinGlobalHelper.instance(FakePackageManager.class, getDi());
}
@NotNull
public Koin getDi() {
return koin;
@Override
public DI getDi() {
return kodein;
}
private AndroidFiles androidFiles;
@@ -717,5 +719,17 @@ public class CustomContext extends Context {
public boolean isCredentialProtectedStorage() {
return false;
}
@NotNull
@Override
public DIContext<?> getDiContext() {
return getDi().getDiContext();
}
@Nullable
@Override
public DITrigger getDiTrigger() {
return null;
}
}

View File

@@ -16,14 +16,14 @@ import android.os.UserHandle;
import kotlin.NotImplementedError;
import xyz.nulldev.androidcompat.pm.InstalledPackage;
import xyz.nulldev.androidcompat.pm.PackageController;
import xyz.nulldev.androidcompat.util.KoinGlobalHelper;
import xyz.nulldev.androidcompat.util.KodeinGlobalHelper;
import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;
public class FakePackageManager extends PackageManager {
private PackageController controller = KoinGlobalHelper.instance(PackageController.class);
private PackageController controller = KodeinGlobalHelper.instance(PackageController.class);
@Override
public PackageInfo getPackageInfo(String packageName, int flags) throws NameNotFoundException {

View File

@@ -8,9 +8,7 @@ import xyz.nulldev.ts.config.ConfigModule
* Application info config.
*/
class ApplicationInfoConfigModule(
getConfig: () -> Config,
) : ConfigModule(getConfig) {
class ApplicationInfoConfigModule(getConfig: () -> Config) : ConfigModule(getConfig) {
val packageName: String by getConfig()
val debug: Boolean by getConfig()

View File

@@ -8,9 +8,7 @@ import xyz.nulldev.ts.config.ConfigModule
* Files configuration modules. Specifies where to store the Android files.
*/
class FilesConfigModule(
getConfig: () -> Config,
) : ConfigModule(getConfig) {
class FilesConfigModule(getConfig: () -> Config) : ConfigModule(getConfig) {
val dataDir: String by getConfig()
val filesDir: String by getConfig()
val noBackupFilesDir: String by getConfig()

View File

@@ -4,9 +4,7 @@ import com.typesafe.config.Config
import io.github.config4k.getValue
import xyz.nulldev.ts.config.ConfigModule
class SystemConfigModule(
val getConfig: () -> Config,
) : ConfigModule(getConfig) {
class SystemConfigModule(val getConfig: () -> Config) : ConfigModule(getConfig) {
val isDebuggable: Boolean by getConfig()
val propertyPrefix = "properties."

View File

@@ -19,9 +19,7 @@ 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 columnCache = mutableMapOf<String, Int>()
private var lastReturnWasNull = false
@@ -31,10 +29,9 @@ class ScrollableResultSet(
val parentMetadata = parent.metaData
val columnCount = parentMetadata.columnCount
val columnLabels =
(1..columnCount)
.map {
parentMetadata.getColumnLabel(it)
}.toTypedArray()
(1..columnCount).map {
parentMetadata.getColumnLabel(it)
}.toTypedArray()
init {
val columnCount = columnCount
@@ -48,17 +45,20 @@ class ScrollableResultSet(
while (parent.next()) {
cachedContent +=
ResultSetEntry().apply {
for (i in 1..columnCount) {
for (i in 1..columnCount)
data += parent.getObject(i)
}
}
resultSetLength++
}
}
private fun notImplemented(): Nothing = throw UnsupportedOperationException("This class currently does not support this operation!")
private fun notImplemented(): Nothing {
throw UnsupportedOperationException("This class currently does not support this operation!")
}
private fun cursorValid(): Boolean = isAfterLast || isBeforeFirst
private fun cursorValid(): Boolean {
return isAfterLast || isBeforeFirst
}
private fun internalMove(row: Int) {
if (cursor < 0) {
@@ -76,16 +76,22 @@ class ScrollableResultSet(
return obj
}
private fun obj(column: String?): Any? = obj(cachedFindColumn(column))
private fun obj(column: String?): Any? {
return obj(cachedFindColumn(column))
}
private fun cachedFindColumn(column: String?) =
columnCache.getOrPut(column!!, {
findColumn(column)
})
override fun getNClob(columnIndex: Int): NClob = obj(columnIndex) as NClob
override fun getNClob(columnIndex: Int): NClob {
return obj(columnIndex) as NClob
}
override fun getNClob(columnLabel: String?): NClob = obj(columnLabel) as NClob
override fun getNClob(columnLabel: String?): NClob {
return obj(columnLabel) as NClob
}
override fun updateNString(
columnIndex: Int,
@@ -254,11 +260,17 @@ class ScrollableResultSet(
notImplemented()
}
override fun getBoolean(columnIndex: Int): Boolean = obj(columnIndex) as Boolean
override fun getBoolean(columnIndex: Int): Boolean {
return obj(columnIndex) as Boolean
}
override fun getBoolean(columnLabel: String?): Boolean = obj(columnLabel) as Boolean
override fun getBoolean(columnLabel: String?): Boolean {
return obj(columnLabel) as Boolean
}
override fun isFirst(): Boolean = cursor - 1 < resultSetLength
override fun isFirst(): Boolean {
return cursor - 1 < resultSetLength
}
override fun getBigDecimal(
columnIndex: Int,
@@ -276,9 +288,13 @@ class ScrollableResultSet(
notImplemented()
}
override fun getBigDecimal(columnIndex: Int): BigDecimal = obj(columnIndex) as BigDecimal
override fun getBigDecimal(columnIndex: Int): BigDecimal {
return obj(columnIndex) as BigDecimal
}
override fun getBigDecimal(columnLabel: String?): BigDecimal = obj(columnLabel) as BigDecimal
override fun getBigDecimal(columnLabel: String?): BigDecimal {
return obj(columnLabel) as BigDecimal
}
override fun updateBytes(
columnIndex: Int,
@@ -294,7 +310,9 @@ class ScrollableResultSet(
notImplemented()
}
override fun isLast(): Boolean = cursor == resultSetLength
override fun isLast(): Boolean {
return cursor == resultSetLength
}
override fun insertRow() {
notImplemented()
@@ -333,7 +351,9 @@ class ScrollableResultSet(
return cursorValid()
}
override fun isAfterLast(): Boolean = cursor > resultSetLength
override fun isAfterLast(): Boolean {
return cursor > resultSetLength
}
override fun relative(rows: Int): Boolean {
internalMove(cursor + rows)
@@ -345,9 +365,8 @@ class ScrollableResultSet(
internalMove(row)
} else {
last()
for (i in 1..row) {
for (i in 1..row)
previous()
}
}
return cursorValid()
}
@@ -375,13 +394,19 @@ class ScrollableResultSet(
return cursorValid()
}
override fun getFloat(columnIndex: Int): Float = obj(columnIndex) as Float
override fun getFloat(columnIndex: Int): Float {
return obj(columnIndex) as Float
}
override fun getFloat(columnLabel: String?): Float = obj(columnLabel) as Float
override fun getFloat(columnLabel: String?): Float {
return obj(columnLabel) as Float
}
override fun wasNull() = lastReturnWasNull
override fun getRow(): Int = cursor
override fun getRow(): Int {
return cursor
}
override fun first(): Boolean {
internalMove(1)
@@ -434,9 +459,13 @@ class ScrollableResultSet(
notImplemented()
}
override fun getURL(columnIndex: Int): URL = obj(columnIndex) as URL
override fun getURL(columnIndex: Int): URL {
return obj(columnIndex) as URL
}
override fun getURL(columnLabel: String?): URL = obj(columnLabel) as URL
override fun getURL(columnLabel: String?): URL {
return obj(columnLabel) as URL
}
override fun updateShort(
columnIndex: Int,
@@ -614,13 +643,21 @@ class ScrollableResultSet(
notImplemented()
}
override fun getByte(columnIndex: Int): Byte = obj(columnIndex) as Byte
override fun getByte(columnIndex: Int): Byte {
return obj(columnIndex) as Byte
}
override fun getByte(columnLabel: String?): Byte = obj(columnLabel) as Byte
override fun getByte(columnLabel: String?): Byte {
return obj(columnLabel) as Byte
}
override fun getString(columnIndex: Int): String? = obj(columnIndex) as String?
override fun getString(columnIndex: Int): String? {
return obj(columnIndex) as String?
}
override fun getString(columnLabel: String?): String? = obj(columnLabel) as String?
override fun getString(columnLabel: String?): String? {
return obj(columnLabel) as String?
}
override fun updateSQLXML(
columnIndex: Int,
@@ -650,9 +687,13 @@ class ScrollableResultSet(
notImplemented()
}
override fun getObject(columnIndex: Int): Any? = obj(columnIndex)
override fun getObject(columnIndex: Int): Any? {
return obj(columnIndex)
}
override fun getObject(columnLabel: String?): Any? = obj(columnLabel)
override fun getObject(columnLabel: String?): Any? {
return obj(columnLabel)
}
override fun getObject(
columnIndex: Int,
@@ -673,12 +714,16 @@ class ScrollableResultSet(
override fun <T : Any?> getObject(
columnIndex: Int,
type: Class<T>?,
): T = obj(columnIndex) as T
): T {
return obj(columnIndex) as T
}
override fun <T : Any?> getObject(
columnLabel: String?,
type: Class<T>?,
): T = obj(columnLabel) as T
): T {
return obj(columnLabel) as T
}
override fun previous(): Boolean {
internalMove(cursor - 1)
@@ -711,9 +756,13 @@ class ScrollableResultSet(
}
}
override fun getLong(columnIndex: Int): Long = castToLong(obj(columnIndex))
override fun getLong(columnIndex: Int): Long {
return castToLong(obj(columnIndex))
}
override fun getLong(columnLabel: String?): Long = castToLong(obj(columnLabel))
override fun getLong(columnLabel: String?): Long {
return castToLong(obj(columnLabel))
}
override fun getClob(columnIndex: Int): Clob {
// TODO Maybe?
@@ -791,9 +840,13 @@ class ScrollableResultSet(
notImplemented()
}
override fun getNString(columnIndex: Int): String = obj(columnIndex) as String
override fun getNString(columnIndex: Int): String {
return obj(columnIndex) as String
}
override fun getNString(columnLabel: String?): String = obj(columnLabel) as String
override fun getNString(columnLabel: String?): String {
return obj(columnLabel) as String
}
override fun getArray(columnIndex: Int): Array {
// TODO Maybe?
@@ -827,11 +880,17 @@ class ScrollableResultSet(
notImplemented()
}
override fun getCharacterStream(columnIndex: Int): Reader = getNCharacterStream(columnIndex)
override fun getCharacterStream(columnIndex: Int): Reader {
return getNCharacterStream(columnIndex)
}
override fun getCharacterStream(columnLabel: String?): Reader = getNCharacterStream(columnLabel)
override fun getCharacterStream(columnLabel: String?): Reader {
return getNCharacterStream(columnLabel)
}
override fun isBeforeFirst(): Boolean = cursor - 1 < resultSetLength
override fun isBeforeFirst(): Boolean {
return cursor - 1 < resultSetLength
}
override fun updateBoolean(
columnIndex: Int,
@@ -867,13 +926,21 @@ class ScrollableResultSet(
notImplemented()
}
override fun getShort(columnIndex: Int): Short = obj(columnIndex) as Short
override fun getShort(columnIndex: Int): Short {
return obj(columnIndex) as Short
}
override fun getShort(columnLabel: String?): Short = obj(columnLabel) as Short
override fun getShort(columnLabel: String?): Short {
return obj(columnLabel) as Short
}
override fun getAsciiStream(columnIndex: Int): InputStream = getBinaryStream(columnIndex)
override fun getAsciiStream(columnIndex: Int): InputStream {
return getBinaryStream(columnIndex)
}
override fun getAsciiStream(columnLabel: String?): InputStream = getBinaryStream(columnLabel)
override fun getAsciiStream(columnLabel: String?): InputStream {
return getBinaryStream(columnLabel)
}
override fun updateTime(
columnIndex: Int,
@@ -941,9 +1008,13 @@ class ScrollableResultSet(
notImplemented()
}
override fun getNCharacterStream(columnIndex: Int): Reader = getBinaryStream(columnIndex).reader()
override fun getNCharacterStream(columnIndex: Int): Reader {
return getBinaryStream(columnIndex).reader()
}
override fun getNCharacterStream(columnLabel: String?): Reader = getBinaryStream(columnLabel).reader()
override fun getNCharacterStream(columnLabel: String?): Reader {
return getBinaryStream(columnLabel).reader()
}
override fun updateArray(
columnIndex: Int,
@@ -959,27 +1030,45 @@ class ScrollableResultSet(
notImplemented()
}
override fun getBytes(columnIndex: Int): ByteArray = obj(columnIndex) as ByteArray
override fun getBytes(columnIndex: Int): ByteArray {
return obj(columnIndex) as ByteArray
}
override fun getBytes(columnLabel: String?): ByteArray = obj(columnLabel) as ByteArray
override fun getBytes(columnLabel: String?): ByteArray {
return obj(columnLabel) as ByteArray
}
override fun getDouble(columnIndex: Int): Double = obj(columnIndex) as Double
override fun getDouble(columnIndex: Int): Double {
return obj(columnIndex) as Double
}
override fun getDouble(columnLabel: String?): Double = obj(columnLabel) as Double
override fun getDouble(columnLabel: String?): Double {
return obj(columnLabel) as Double
}
override fun getUnicodeStream(columnIndex: Int): InputStream = getBinaryStream(columnIndex)
override fun getUnicodeStream(columnIndex: Int): InputStream {
return getBinaryStream(columnIndex)
}
override fun getUnicodeStream(columnLabel: String?): InputStream = getBinaryStream(columnLabel)
override fun getUnicodeStream(columnLabel: String?): InputStream {
return getBinaryStream(columnLabel)
}
override fun rowInserted() = false
private fun thisIsWrapperFor(iface: Class<*>?) = this.javaClass.isInstance(iface)
override fun isWrapperFor(iface: Class<*>?): Boolean = thisIsWrapperFor(iface) || parent.isWrapperFor(iface)
override fun isWrapperFor(iface: Class<*>?): Boolean {
return thisIsWrapperFor(iface) || parent.isWrapperFor(iface)
}
override fun getInt(columnIndex: Int): Int = obj(columnIndex) as Int
override fun getInt(columnIndex: Int): Int {
return obj(columnIndex) as Int
}
override fun getInt(columnLabel: String?): Int = obj(columnLabel) as Int
override fun getInt(columnLabel: String?): Int {
return obj(columnLabel) as Int
}
override fun updateNull(columnIndex: Int) {
notImplemented()
@@ -999,8 +1088,8 @@ class ScrollableResultSet(
notImplemented()
}
override fun getMetaData(): ResultSetMetaData =
object : ResultSetMetaData by parentMetadata {
override fun getMetaData(): ResultSetMetaData {
return object : ResultSetMetaData by parentMetadata {
override fun isReadOnly(column: Int) = true
override fun isWritable(column: Int) = false
@@ -1009,12 +1098,19 @@ class ScrollableResultSet(
override fun getColumnCount() = this@ScrollableResultSet.columnCount
override fun getColumnLabel(column: Int): String = columnLabels[column - 1]
override fun getColumnLabel(column: Int): String {
return columnLabels[column - 1]
}
}
}
override fun getBinaryStream(columnIndex: Int): InputStream = (obj(columnIndex) as ByteArray).inputStream()
override fun getBinaryStream(columnIndex: Int): InputStream {
return (obj(columnIndex) as ByteArray).inputStream()
}
override fun getBinaryStream(columnLabel: String?): InputStream = (obj(columnLabel) as ByteArray).inputStream()
override fun getBinaryStream(columnLabel: String?): InputStream {
return (obj(columnLabel) as ByteArray).inputStream()
}
override fun updateCharacterStream(
columnIndex: Int,

View File

@@ -1,12 +1,16 @@
package xyz.nulldev.androidcompat.info
import android.content.pm.ApplicationInfo
import org.kodein.di.DI
import org.kodein.di.DIAware
import org.kodein.di.conf.global
import org.kodein.di.instance
import xyz.nulldev.androidcompat.config.ApplicationInfoConfigModule
import xyz.nulldev.ts.config.ConfigManager
class ApplicationInfoImpl(
private val configManager: ConfigManager,
) : ApplicationInfo() {
class ApplicationInfoImpl(override val di: DI = DI.global) : ApplicationInfo(), DIAware {
val configManager: ConfigManager by di.instance()
val appInfoConfig: ApplicationInfoConfigModule
get() = configManager.module()

View File

@@ -8,9 +8,7 @@ import java.io.File
/**
* Android file constants.
*/
class AndroidFiles(
val configManager: ConfigManager = GlobalConfigManager,
) {
class AndroidFiles(val configManager: ConfigManager = GlobalConfigManager) {
val filesConfig: FilesConfigModule
get() = configManager.module()
@@ -32,8 +30,9 @@ class AndroidFiles(
val packagesDir: File get() = registerFile(filesConfig.packageDir)
fun registerFile(file: String): File =
File(file).apply {
fun registerFile(file: String): File {
return File(file).apply {
mkdirs()
}
}
}

View File

@@ -14,11 +14,11 @@ import com.russhwolf.settings.Settings
import com.russhwolf.settings.serialization.decodeValue
import com.russhwolf.settings.serialization.decodeValueOrNull
import com.russhwolf.settings.serialization.encodeValue
import io.github.oshai.kotlinlogging.KotlinLogging
import kotlinx.serialization.ExperimentalSerializationApi
import kotlinx.serialization.SerializationException
import kotlinx.serialization.builtins.SetSerializer
import kotlinx.serialization.builtins.serializer
import mu.KotlinLogging
import xyz.nulldev.androidcompat.util.SafePath
import xyz.nulldev.ts.config.ApplicationRootDir
import java.util.Properties
@@ -30,9 +30,7 @@ import kotlin.io.path.inputStream
import kotlin.io.path.outputStream
@OptIn(ExperimentalSerializationApi::class, ExperimentalSettingsApi::class)
class JavaSharedPreferences(
key: String,
) : SharedPreferences {
class JavaSharedPreferences(key: String) : SharedPreferences {
companion object {
private val logger = KotlinLogging.logger {}
}
@@ -74,17 +72,20 @@ class JavaSharedPreferences(
private val listeners = mutableMapOf<SharedPreferences.OnSharedPreferenceChangeListener, (String) -> Unit>()
// TODO: 2021-05-29 Need to find a way to get this working with all pref types
override fun getAll(): MutableMap<String, *> = preferences.keys.associateWith { preferences.getStringOrNull(it) }.toMutableMap()
override fun getAll(): MutableMap<String, *> {
return preferences.keys.associateWith { preferences.getStringOrNull(it) }.toMutableMap()
}
override fun getString(
key: String,
defValue: String?,
): String? =
if (defValue != null) {
): String? {
return if (defValue != null) {
preferences.getString(key, defValue)
} else {
preferences.getStringOrNull(key)
}
}
override fun getStringSet(
key: String,
@@ -104,48 +105,50 @@ class JavaSharedPreferences(
override fun getInt(
key: String,
defValue: Int,
): Int = preferences.getInt(key, defValue)
): Int {
return preferences.getInt(key, defValue)
}
override fun getLong(
key: String,
defValue: Long,
): Long = preferences.getLong(key, defValue)
): Long {
return preferences.getLong(key, defValue)
}
override fun getFloat(
key: String,
defValue: Float,
): Float = preferences.getFloat(key, defValue)
): Float {
return preferences.getFloat(key, defValue)
}
override fun getBoolean(
key: String,
defValue: Boolean,
): Boolean = preferences.getBoolean(key, defValue)
): Boolean {
return preferences.getBoolean(key, defValue)
}
override fun contains(key: String): Boolean = key in preferences.keys
override fun contains(key: String): Boolean {
return key in preferences.keys
}
override fun edit(): SharedPreferences.Editor =
Editor(preferences) { key ->
override fun edit(): SharedPreferences.Editor {
return Editor(preferences) { key ->
listeners.forEach { (_, listener) ->
listener(key)
}
}
}
class Editor(
private val preferences: Settings,
private val notify: (String) -> Unit,
) : SharedPreferences.Editor {
class Editor(private val preferences: Settings, private val notify: (String) -> Unit) : SharedPreferences.Editor {
private val actions = mutableListOf<Action>()
private sealed class Action {
data class Add(
val key: String,
val value: Any,
) : Action()
data class Remove(
val key: String,
) : Action()
data class Add(val key: String, val value: Any) : Action()
data class Remove(val key: String) : Action()
data object Clear : Action()
}

View File

@@ -14,9 +14,7 @@ import java.io.File
import javax.imageio.ImageIO
import javax.xml.parsers.DocumentBuilderFactory
data class InstalledPackage(
val root: File,
) {
data class InstalledPackage(val root: File) {
val apk = File(root, "package.apk")
val jar = File(root, "translated.jar")
val icon = File(root, "icon.png")
@@ -36,21 +34,18 @@ data class InstalledPackage(
Bundle().apply {
val appTag = doc.getElementsByTagName("application").item(0)
appTag
?.childNodes
?.toList()
?.filter {
it.nodeType == Node.ELEMENT_NODE
}?.map {
it as Element
}?.filter {
it.tagName == "meta-data"
}?.map {
putString(
it.attributes.getNamedItem("android:name").nodeValue,
it.attributes.getNamedItem("android:value").nodeValue,
)
}
appTag?.childNodes?.toList()?.filter {
it.nodeType == Node.ELEMENT_NODE
}?.map {
it as Element
}?.filter {
it.tagName == "meta-data"
}?.map {
putString(
it.attributes.getNamedItem("android:name").nodeValue,
it.attributes.getNamedItem("android:value").nodeValue,
)
}
}
it.signatures =
@@ -58,14 +53,12 @@ data class InstalledPackage(
parsed.apkSingers.flatMap { it.certificateMetas }
// + parsed.apkV2Singers.flatMap { it.certificateMetas }
) // Blocked by: https://github.com/hsiafan/apk-parser/issues/72
.map { Signature(it.data) }
.toTypedArray()
.map { Signature(it.data) }.toTypedArray()
}
fun verify(): Boolean {
val res =
ApkVerifier
.Builder(apk)
ApkVerifier.Builder(apk)
.build()
.verify()
@@ -77,14 +70,11 @@ data class InstalledPackage(
val icons = ApkFile(apk).allIcons
val read =
icons
.filter { it.isFile }
.map {
it.data.inputStream().use {
ImageIO.read(it)
}
}.sortedByDescending { it.width * it.height }
.firstOrNull() ?: return
icons.filter { it.isFile }.map {
it.data.inputStream().use {
ImageIO.read(it)
}
}.sortedByDescending { it.width * it.height }.firstOrNull() ?: return
ImageIO.write(read, "png", icon)
} catch (e: Exception) {
@@ -104,9 +94,8 @@ data class InstalledPackage(
fun NodeList.toList(): List<Node> {
val out = mutableListOf<Node>()
for (i in 0 until length) {
for (i in 0 until length)
out += item(i)
}
return out
}

View File

@@ -1,12 +1,14 @@
package xyz.nulldev.androidcompat.pm
import net.dongliu.apk.parser.ApkParsers
import org.koin.mp.KoinPlatformTools
import org.kodein.di.DI
import org.kodein.di.conf.global
import org.kodein.di.instance
import xyz.nulldev.androidcompat.io.AndroidFiles
import java.io.File
class PackageController {
private val androidFiles: AndroidFiles by KoinPlatformTools.defaultContext().get().inject()
private val androidFiles by DI.global.instance<AndroidFiles>()
private val uninstallListeners = mutableListOf<(String) -> Unit>()
fun registerUninstallListener(listener: (String) -> Unit) {
@@ -55,15 +57,13 @@ class PackageController {
}
}
fun listInstalled(): List<InstalledPackage> =
androidFiles.packagesDir
.listFiles()
.orEmpty()
.filter {
it.isDirectory
}.map {
InstalledPackage(it)
}
fun listInstalled(): List<InstalledPackage> {
return androidFiles.packagesDir.listFiles().orEmpty().filter {
it.isDirectory
}.map {
InstalledPackage(it)
}
}
fun deletePackage(pack: InstalledPackage) {
if (!pack.root.exists()) error("Package was never installed!")

View File

@@ -6,19 +6,18 @@ import android.content.pm.PackageInfo
import net.dongliu.apk.parser.bean.ApkMeta
import java.io.File
fun ApkMeta.toPackageInfo(apk: File): PackageInfo =
PackageInfo().also {
fun ApkMeta.toPackageInfo(apk: File): PackageInfo {
return PackageInfo().also {
it.packageName = packageName
it.versionCode = versionCode.toInt()
it.versionName = versionName
it.reqFeatures =
usesFeatures
.map {
FeatureInfo().apply {
name = it.name
}
}.toTypedArray()
usesFeatures.map {
FeatureInfo().apply {
name = it.name
}
}.toTypedArray()
it.applicationInfo =
ApplicationInfo().apply {
@@ -27,3 +26,4 @@ fun ApkMeta.toPackageInfo(apk: File): PackageInfo =
sourceDir = apk.absolutePath
}
}
}

View File

@@ -1,7 +1,7 @@
package xyz.nulldev.androidcompat.res;
import xyz.nulldev.androidcompat.info.ApplicationInfoImpl;
import xyz.nulldev.androidcompat.util.KoinGlobalHelper;
import xyz.nulldev.androidcompat.util.KodeinGlobalHelper;
import java.text.SimpleDateFormat;
import java.util.Calendar;
@@ -10,7 +10,7 @@ import java.util.Calendar;
* BuildConfig compat class.
*/
public class BuildConfigCompat {
private static ApplicationInfoImpl applicationInfo = KoinGlobalHelper.instance(ApplicationInfoImpl.class);
private static ApplicationInfoImpl applicationInfo = KodeinGlobalHelper.instance(ApplicationInfoImpl.class);
public static final boolean DEBUG = applicationInfo.getDebug();

View File

@@ -1,8 +1,6 @@
package xyz.nulldev.androidcompat.res
class DrawableResource(
val location: String,
) : Resource {
class DrawableResource(val location: String) : Resource {
override fun getType() = DrawableResource::class.java
override fun getValue() = javaClass.getResourceAsStream(location)

View File

@@ -19,9 +19,7 @@ package xyz.nulldev.androidcompat.res
/**
* String resource.
*/
class StringResource(
val string: String,
) : Resource {
class StringResource(val string: String) : Resource {
override fun getValue() = string
override fun getType() = StringResource::class.java

View File

@@ -3,7 +3,7 @@ package xyz.nulldev.androidcompat.service
import android.app.Service
import android.content.Context
import android.content.Intent
import io.github.oshai.kotlinlogging.KotlinLogging
import mu.KotlinLogging
import java.util.concurrent.ConcurrentHashMap
import kotlin.concurrent.thread

View File

@@ -0,0 +1,70 @@
package xyz.nulldev.androidcompat.util
import android.content.Context
import org.kodein.di.DI
import org.kodein.di.conf.global
import org.kodein.di.instance
import xyz.nulldev.androidcompat.androidimpl.CustomContext
import xyz.nulldev.androidcompat.androidimpl.FakePackageManager
import xyz.nulldev.androidcompat.info.ApplicationInfoImpl
import xyz.nulldev.androidcompat.io.AndroidFiles
import xyz.nulldev.androidcompat.pm.PackageController
import xyz.nulldev.androidcompat.service.ServiceSupport
/**
* Helper class to allow access to Kodein from Java
*/
object KodeinGlobalHelper {
/**
* Get the Kodein object
*/
@JvmStatic
fun kodein() = DI.global
/**
* Get a dependency
*/
@JvmStatic
@Suppress("UNCHECKED_CAST")
fun <T : Any> instance(
type: Class<T>,
kodein: DI? = null,
): T {
return when (type) {
AndroidFiles::class.java -> {
val instance: AndroidFiles by (kodein ?: kodein()).instance()
instance as T
}
ApplicationInfoImpl::class.java -> {
val instance: ApplicationInfoImpl by (kodein ?: kodein()).instance()
instance as T
}
ServiceSupport::class.java -> {
val instance: ServiceSupport by (kodein ?: kodein()).instance()
instance as T
}
FakePackageManager::class.java -> {
val instance: FakePackageManager by (kodein ?: kodein()).instance()
instance as T
}
PackageController::class.java -> {
val instance: PackageController by (kodein ?: kodein()).instance()
instance as T
}
CustomContext::class.java -> {
val instance: CustomContext by (kodein ?: kodein()).instance()
instance as T
}
Context::class.java -> {
val instance: Context by (kodein ?: kodein()).instance()
instance as T
}
else -> throw IllegalArgumentException("Kodein instance not found")
}
}
@JvmStatic
fun <T : Any> instance(type: Class<T>): T {
return instance(type, null)
}
}

View File

@@ -1,27 +0,0 @@
package xyz.nulldev.androidcompat.util
import org.koin.core.Koin
import org.koin.mp.KoinPlatformTools
/**
* Helper class to allow access to Kodein from Java
*/
object KoinGlobalHelper {
/**
* Get the Kodein object
*/
@JvmStatic
fun koin() = KoinPlatformTools.defaultContext().get()
/**
* Get a dependency
*/
@JvmStatic
fun <T : Any> instance(
type: Class<T>,
koin: Koin? = null,
): T = (koin ?: koin()).get(type.kotlin)
@JvmStatic
fun <T : Any> instance(type: Class<T>): T = instance(type, null)
}

View File

@@ -18,7 +18,9 @@ class CookieManagerImpl : CookieManager() {
acceptCookie = accept
}
override fun acceptCookie(): Boolean = acceptCookie
override fun acceptCookie(): Boolean {
return acceptCookie
}
override fun setAcceptThirdPartyCookies(
webview: WebView?,
@@ -27,7 +29,9 @@ class CookieManagerImpl : CookieManager() {
acceptThirdPartyCookies = accept
}
override fun acceptThirdPartyCookies(webview: WebView?): Boolean = acceptThirdPartyCookies
override fun acceptThirdPartyCookies(webview: WebView?): Boolean {
return acceptThirdPartyCookies
}
override fun setCookie(
url: String,
@@ -61,8 +65,7 @@ class CookieManagerImpl : CookieManager() {
} else {
URI("http://$url")
}
return cookieHandler.cookieStore
.get(uri)
return cookieHandler.cookieStore.get(uri)
.joinToString("; ") { "${it.name}=${it.value}" }
}
@@ -84,11 +87,15 @@ class CookieManagerImpl : CookieManager() {
callback?.onReceiveValue(removedCookies)
}
override fun hasCookies(): Boolean = cookieHandler.cookieStore.cookies.isNotEmpty()
override fun hasCookies(): Boolean {
return cookieHandler.cookieStore.cookies.isNotEmpty()
}
override fun flush() {}
override fun allowFileSchemeCookiesImpl(): Boolean = allowFileSchemeCookies
override fun allowFileSchemeCookiesImpl(): Boolean {
return allowFileSchemeCookies
}
override fun setAcceptFileSchemeCookiesImpl(accept: Boolean) {
allowFileSchemeCookies = acceptCookie

View File

@@ -1,207 +1,3 @@
# Server: v2.0.1727 + WevUI: v1.5.1
## TL;DR
-
## Suwayomi-Server Changelog
- ([r1726](https://github.com/Suwayomi/Suwayomi-Server/commit/1d5323a477528649f81fa4bd2e1e9e4a28da6402)) [skip ci] Add link to discord in issue templates ([#1347](https://github.com/Suwayomi/Suwayomi-Server/pull/1347) by @schroda)
- ([r1725](https://github.com/Suwayomi/Suwayomi-Server/commit/f8d73819eaae86e4de10c8795e44d2347f1ce03e)) [Feature] Support Bangumi Tracker ([#1343](https://github.com/Suwayomi/Suwayomi-Server/pull/1343) by @kaaass, @Syer10)
- ([r1724](https://github.com/Suwayomi/Suwayomi-Server/commit/cbe26b7291a41c6a9a9207ab97c3b86db8978e4e)) Chmod in build script (by @Syer10)
- ([r1723](https://github.com/Suwayomi/Suwayomi-Server/commit/93477f60c2204fc10c42c8f62832543696d8e112)) Fix release name (by @Syer10)
- ([r1722](https://github.com/Suwayomi/Suwayomi-Server/commit/9feebbfe177a3ec3044f5c30607c73e9bae7c60c)) Use tar for MacOS (by @Syer10)
- ([r1721](https://github.com/Suwayomi/Suwayomi-Server/commit/6e365491a91962061f806e0debc878df89f46881)) Add permissions to jspawnhelper ([#1339](https://github.com/Suwayomi/Suwayomi-Server/pull/1339) by @Syer10)
- ([r1720](https://github.com/Suwayomi/Suwayomi-Server/commit/2e586581297ce4955b3e25f0ba822b681f2683ed)) Fix MacOS builds ([#1338](https://github.com/Suwayomi/Suwayomi-Server/pull/1338) by @Syer10)
- ([r1719](https://github.com/Suwayomi/Suwayomi-Server/commit/256c564b91a9154d3d8883d2a052095aa581edf5)) Fix release version extraction from jar name ([#1335](https://github.com/Suwayomi/Suwayomi-Server/pull/1335) by @schroda)
- ([r1718](https://github.com/Suwayomi/Suwayomi-Server/commit/96b50f52ec917e8a521c5f87ae85704d188ec9f6)) Ensure webui "channel" is always of corresponding enum ([#1334](https://github.com/Suwayomi/Suwayomi-Server/pull/1334) by @schroda)
- ([r1717](https://github.com/Suwayomi/Suwayomi-Server/commit/3167d8aa15cc5c7b7064a61eccb0e28336808241)) Fix/startup jvm error after installation update via msi ([#1229](https://github.com/Suwayomi/Suwayomi-Server/pull/1229) by @schroda, @Syer10)
- ([r1716](https://github.com/Suwayomi/Suwayomi-Server/commit/78fd09c7286cfbd0db9dd308ebbb41e6e7fbe7c5)) Prevent IndexOutOfBoundsException in "libraryUpdate" subscription ([#1320](https://github.com/Suwayomi/Suwayomi-Server/pull/1320) by @schroda)
- ([r1715](https://github.com/Suwayomi/Suwayomi-Server/commit/4c5598cedfcd40c2dffab31e45f38d692b2c3741)) Feature/graphql log execution exceptions ([#1319](https://github.com/Suwayomi/Suwayomi-Server/pull/1319) by @schroda)
- ([r1714](https://github.com/Suwayomi/Suwayomi-Server/commit/c3347d94abe131f4e75ef3d9736184715f471812)) Feature/use GitHub issue yml format ([#1314](https://github.com/Suwayomi/Suwayomi-Server/pull/1314) by @schroda)
- ([r1713](https://github.com/Suwayomi/Suwayomi-Server/commit/7ca4aa75a898212616b3bbfbbbc864b8d64b08a5)) Fix checkbox preference title nullability ([#1313](https://github.com/Suwayomi/Suwayomi-Server/pull/1313) by @Syer10)
- ([r1712](https://github.com/Suwayomi/Suwayomi-Server/commit/226fad5594871d24c0f670d2bac741076e048376)) Remove "default" category from backups ([#1307](https://github.com/Suwayomi/Suwayomi-Server/pull/1307) by @schroda)
- ([r1711](https://github.com/Suwayomi/Suwayomi-Server/commit/d0ee1ba5af8b4a560722ca5e6a29f58b46a84577)) Align kitsu icon with icons of other trackers ([#1303](https://github.com/Suwayomi/Suwayomi-Server/pull/1303) by @schroda)
- ([r1710](https://github.com/Suwayomi/Suwayomi-Server/commit/439e0c8284308a731b5cbd4255cd4b08f75660f0)) Emit only updater job changes instead of full status ([#1302](https://github.com/Suwayomi/Suwayomi-Server/pull/1302) by @schroda)
- ([r1709](https://github.com/Suwayomi/Suwayomi-Server/commit/7d079a8728d82af3082cde3db70114b946972a18)) Update kotlin monorepo to v2.1.20 ([#1315](https://github.com/Suwayomi/Suwayomi-Server/pull/1315) by @renovate[bot])
- ([r1708](https://github.com/Suwayomi/Suwayomi-Server/commit/945a52653e78b239d6288bb983099d4b76505ace)) Update graphqlkotlin to v8.4.0 ([#1311](https://github.com/Suwayomi/Suwayomi-Server/pull/1311) by @renovate[bot])
- ([r1707](https://github.com/Suwayomi/Suwayomi-Server/commit/bdafc86990f47d22325ace0170d5dbe721be374c)) Update polyglot to v24.2.0 ([#1310](https://github.com/Suwayomi/Suwayomi-Server/pull/1310) by @renovate[bot])
- ([r1706](https://github.com/Suwayomi/Suwayomi-Server/commit/57d425ab9f01917d238fd61d2ecb2d3ce966c73f)) Update dependency ch.qos.logback:logback-classic to v1.5.18 ([#1309](https://github.com/Suwayomi/Suwayomi-Server/pull/1309) by @renovate[bot])
- ([r1705](https://github.com/Suwayomi/Suwayomi-Server/commit/395ac8e944785504d524a0a2a1f36f96975eb602)) Update dependency com.ibm.icu:icu4j to v77 ([#1305](https://github.com/Suwayomi/Suwayomi-Server/pull/1305) by @renovate[bot])
- ([r1704](https://github.com/Suwayomi/Suwayomi-Server/commit/2f801e7571300e0504edb6a46ff83382358bda88)) Update plugin buildconfig to v5.5.4 ([#1304](https://github.com/Suwayomi/Suwayomi-Server/pull/1304) by @renovate[bot])
- ([r1703](https://github.com/Suwayomi/Suwayomi-Server/commit/d7636045fee34e3da8d66d81f59704c8922d0eea)) Update dependency io.javalin:javalin to v6.5.0 ([#1301](https://github.com/Suwayomi/Suwayomi-Server/pull/1301) by @renovate[bot])
- ([r1702](https://github.com/Suwayomi/Suwayomi-Server/commit/b745f108704185882f2bdccf1a69e707548eb89e)) Update dependency org.jsoup:jsoup to v1.19.1 ([#1292](https://github.com/Suwayomi/Suwayomi-Server/pull/1292) by @renovate[bot])
- ([r1701](https://github.com/Suwayomi/Suwayomi-Server/commit/d76849942c412d1b8f04d20747cfce34cf50f553)) Update plugin buildconfig to v5.5.2 ([#1299](https://github.com/Suwayomi/Suwayomi-Server/pull/1299) by @renovate[bot])
- ([r1700](https://github.com/Suwayomi/Suwayomi-Server/commit/b7a8a3ffe84527d25982d0a0be866b3cf6a08214)) Update dependency io.github.oshai:kotlin-logging-jvm to v7.0.5 ([#1298](https://github.com/Suwayomi/Suwayomi-Server/pull/1298) by @renovate[bot])
- ([r1699](https://github.com/Suwayomi/Suwayomi-Server/commit/95d9293fe013d5c553924521bca430c7d7768090)) Add support for opds-pse for undownloaded chapters ([#1278](https://github.com/Suwayomi/Suwayomi-Server/pull/1278) by @ShirishSaxena, @showyee)
- ([r1698](https://github.com/Suwayomi/Suwayomi-Server/commit/3be165a5514af306398d542118da40fab16da411)) Initial import of Kitsu tracker ([#1297](https://github.com/Suwayomi/Suwayomi-Server/pull/1297) by @cpiber)
- ([r1697](https://github.com/Suwayomi/Suwayomi-Server/commit/cb498e2128549fc462d815cf70c89689a3ca5090)) Update dependency org.slf4j:slf4j-api to v2.0.17 ([#1284](https://github.com/Suwayomi/Suwayomi-Server/pull/1284) by @renovate[bot])
- ([r1696](https://github.com/Suwayomi/Suwayomi-Server/commit/973f4d66e2b569d712452633d1be7913f4a958fc)) Update jackson monorepo to v2.18.3 ([#1289](https://github.com/Suwayomi/Suwayomi-Server/pull/1289) by @renovate[bot])
- ([r1695](https://github.com/Suwayomi/Suwayomi-Server/commit/2c80672f6e81071febfa30d8b18740b31def31ad)) Update plugin ktlint to v12.2.0 ([#1288](https://github.com/Suwayomi/Suwayomi-Server/pull/1288) by @renovate[bot])
- ([r1694](https://github.com/Suwayomi/Suwayomi-Server/commit/2599813ef1c7e2c06e8dec2acc3ac449e723fa91)) Update dependency io.mockk:mockk to v1.13.17 ([#1287](https://github.com/Suwayomi/Suwayomi-Server/pull/1287) by @renovate[bot])
- ([r1693](https://github.com/Suwayomi/Suwayomi-Server/commit/86f849a185d69f3e3b3c5085355985f38f42ff7a)) Update dependency net.harawata:appdirs to v1.4.0 ([#1286](https://github.com/Suwayomi/Suwayomi-Server/pull/1286) by @renovate[bot])
- ([r1692](https://github.com/Suwayomi/Suwayomi-Server/commit/875f1f1506c76cf97b6a61ecc800f8aa4eb9c029)) Update dependency com.android.tools.build:apksig to v8.9.0 ([#1285](https://github.com/Suwayomi/Suwayomi-Server/pull/1285) by @renovate[bot])
- ([r1691](https://github.com/Suwayomi/Suwayomi-Server/commit/e418375963a254d8eb7a35b982c8e7078e493bea)) Update dependency ch.qos.logback:logback-classic to v1.5.17 ([#1283](https://github.com/Suwayomi/Suwayomi-Server/pull/1283) by @renovate[bot])
- ([r1690](https://github.com/Suwayomi/Suwayomi-Server/commit/d528fc7f9e6aeddece689e45fb0633508c5751b2)) Update dependency gradle to v8.13 ([#1282](https://github.com/Suwayomi/Suwayomi-Server/pull/1282) by @renovate[bot])
- ([r1689](https://github.com/Suwayomi/Suwayomi-Server/commit/633ea97848ad851103c23b5c0196d8786c8e90cd)) Feature/optimize backup import ([#1270](https://github.com/Suwayomi/Suwayomi-Server/pull/1270) by @schroda)
- ([r1688](https://github.com/Suwayomi/Suwayomi-Server/commit/36cb899b91b3bfcfb94a8baf85e9db7a6e752d6c)) Prevent chapter lastReadPage coerceIn error ([#1272](https://github.com/Suwayomi/Suwayomi-Server/pull/1272) by @schroda)
- ([r1687](https://github.com/Suwayomi/Suwayomi-Server/commit/c4d849d6a33a2ada5320c7910683556e204498e9)) Fix invalid chapter download state in database ([#1271](https://github.com/Suwayomi/Suwayomi-Server/pull/1271) by @schroda)
- ([r1686](https://github.com/Suwayomi/Suwayomi-Server/commit/c8bd39b4bfee44005614df5b647e803279e5218e)) Prevent negative lastPageRead values ([#1267](https://github.com/Suwayomi/Suwayomi-Server/pull/1267) by @schroda, @Syer10)
- ([r1685](https://github.com/Suwayomi/Suwayomi-Server/commit/733ba16af2130013eec6b29c90fea181eb9b7c5e)) Fix/backup with duplicated chapters failure ([#1269](https://github.com/Suwayomi/Suwayomi-Server/pull/1269) by @schroda)
- ([r1684](https://github.com/Suwayomi/Suwayomi-Server/commit/37f57c0c55586d89ecb4985d5e3ce38baf16ae62)) Prevent marking chapter as downloaded without pages ([#1268](https://github.com/Suwayomi/Suwayomi-Server/pull/1268) by @schroda)
- ([r1683](https://github.com/Suwayomi/Suwayomi-Server/commit/013dbd79b4ab6c399db43e3d975e7ed9fbcea517)) Update dependency com.android.tools.build:apksig to v8.8.1 ([#1264](https://github.com/Suwayomi/Suwayomi-Server/pull/1264) by @renovate[bot])
- ([r1682](https://github.com/Suwayomi/Suwayomi-Server/commit/f76d0b3258c51fed1d8f657fdd039c76f3fab40b)) Remove redundant code and add next/prev links ([#1263](https://github.com/Suwayomi/Suwayomi-Server/pull/1263) by @zeedif)
- ([r1681](https://github.com/Suwayomi/Suwayomi-Server/commit/c2f7cdd72e3787fe6451371181da20bc61bd0d19)) Refactoring OPDS API for a more versatile root, allowing selection of manga listing by: all, source, genre, category, language, status. ([#1262](https://github.com/Suwayomi/Suwayomi-Server/pull/1262) by @zeedif)
- ([r1680](https://github.com/Suwayomi/Suwayomi-Server/commit/01c37cb0ba08835e173243db265c98d2219e1c25)) Ignore authentication for preflight requests ([#1261](https://github.com/Suwayomi/Suwayomi-Server/pull/1261) by @schroda)
- ([r1679](https://github.com/Suwayomi/Suwayomi-Server/commit/0dd0af1b8462396749661673598d0650ed8129ad)) Update dependency io.github.oshai:kotlin-logging-jvm to v7.0.4 ([#1260](https://github.com/Suwayomi/Suwayomi-Server/pull/1260) by @renovate[bot])
- ([r1678](https://github.com/Suwayomi/Suwayomi-Server/commit/26aa68430099cadce0b8edffe9bf7cd59c0cec4f)) Add support for OPDS v1.2 to browse stored CBZ files ([#1257](https://github.com/Suwayomi/Suwayomi-Server/pull/1257) by @zeedif)
- ([r1677](https://github.com/Suwayomi/Suwayomi-Server/commit/9669cdfb761e31a764ec31d9bbe2cfe04df8a0ef)) Update kotlin monorepo to v2.1.10 ([#1251](https://github.com/Suwayomi/Suwayomi-Server/pull/1251) by @renovate[bot])
- ([r1676](https://github.com/Suwayomi/Suwayomi-Server/commit/2c5e5e283e7d3b3bb3019dd2d448347ae118d6ee)) Update dependency com.github.Suwayomi:exposed-migrations to v3.7.0 ([#1248](https://github.com/Suwayomi/Suwayomi-Server/pull/1248) by @renovate[bot])
- ([r1675](https://github.com/Suwayomi/Suwayomi-Server/commit/303921c6ea5e2c828b91f2bbea35c3a55e1b5c41)) Update dependency gradle to v8.12.1 ([#1247](https://github.com/Suwayomi/Suwayomi-Server/pull/1247) by @renovate[bot])
- ([r1674](https://github.com/Suwayomi/Suwayomi-Server/commit/593291a60f10da3aa97b4180436ebe8af77d84a0)) Update exposed to v0.59.0 ([#1228](https://github.com/Suwayomi/Suwayomi-Server/pull/1228) by @renovate[bot])
- ([r1673](https://github.com/Suwayomi/Suwayomi-Server/commit/3af8e395bd6f48ee7ed39011115dc190fa99a7e0)) Check if file exists ([#1246](https://github.com/Suwayomi/Suwayomi-Server/pull/1246) by @Syer10)
- ([r1672](https://github.com/Suwayomi/Suwayomi-Server/commit/0b192cfa5243584d734b541c2c5451a50edd577e)) Normalize Paths ([#1245](https://github.com/Suwayomi/Suwayomi-Server/pull/1245) by @Syer10)
- ([r1671](https://github.com/Suwayomi/Suwayomi-Server/commit/fb8f20f31a823b018af704e14a35bc55be05c177)) Fix MacOS .command file with new JRE ([#1243](https://github.com/Suwayomi/Suwayomi-Server/pull/1243) by @Syer10)
- ([r1670](https://github.com/Suwayomi/Suwayomi-Server/commit/e53386cf7278055bd3a74e5534a73f9d42b0b373)) Update dependency adoptium/temurin21-binaries to jdk-21.0.6+7 ([#1241](https://github.com/Suwayomi/Suwayomi-Server/pull/1241) by @renovate[bot])
- ([r1669](https://github.com/Suwayomi/Suwayomi-Server/commit/b0e9b9b307fa879b224f3cb4b046aa279ad8658e)) Update polyglot to v24.1.2 ([#1240](https://github.com/Suwayomi/Suwayomi-Server/pull/1240) by @renovate[bot])
- ([r1668](https://github.com/Suwayomi/Suwayomi-Server/commit/789678a45d02bd23fe6cb9983350fbf9fbea3066)) Update dependency io.insert-koin:koin-core to v4.0.2 ([#1239](https://github.com/Suwayomi/Suwayomi-Server/pull/1239) by @renovate[bot])
- ([r1667](https://github.com/Suwayomi/Suwayomi-Server/commit/4ad01d345175904b2e64617c5c5a5f577218f315)) Update graphqlkotlin to v8.3.0 ([#1235](https://github.com/Suwayomi/Suwayomi-Server/pull/1235) by @renovate[bot])
- ([r1666](https://github.com/Suwayomi/Suwayomi-Server/commit/6a87daa0b3a44cd5b7896cacf5a2feed37d907ab)) Update dependency org.bouncycastle:bcprov-jdk18on to v1.80 ([#1231](https://github.com/Suwayomi/Suwayomi-Server/pull/1231) by @renovate[bot])
- ([r1665](https://github.com/Suwayomi/Suwayomi-Server/commit/aebef87076f33eabbee134acc77dadad5f1209c3)) Update dependency io.github.reactivecircus.cache4k:cache4k to v0.14.0 ([#1230](https://github.com/Suwayomi/Suwayomi-Server/pull/1230) by @renovate[bot])
- ([r1664](https://github.com/Suwayomi/Suwayomi-Server/commit/32ff58598f4537662b7e33671025e889d63a3eae)) Merge Service Files during build to fix GraalJS ([#1242](https://github.com/Suwayomi/Suwayomi-Server/pull/1242) by @Syer10)
- ([r1663](https://github.com/Suwayomi/Suwayomi-Server/commit/2d56cbe227e2955f722d1f1e1961f1b106c31045)) Update serialization to v1.8.0 ([#1220](https://github.com/Suwayomi/Suwayomi-Server/pull/1220) by @renovate[bot])
- ([r1662](https://github.com/Suwayomi/Suwayomi-Server/commit/fbef5f592b5994ebc5ed32f0146b18a3a4b9674c)) Update dependency com.android.tools.build:apksig to v8.8.0 ([#1227](https://github.com/Suwayomi/Suwayomi-Server/pull/1227) by @renovate[bot])
- ([r1661](https://github.com/Suwayomi/Suwayomi-Server/commit/090af36f5e6c320bd409af7eb2aab37707ef4898)) Update dependency com.squareup.okio:okio to v3.10.2 ([#1223](https://github.com/Suwayomi/Suwayomi-Server/pull/1223) by @renovate[bot])
- ([r1660](https://github.com/Suwayomi/Suwayomi-Server/commit/885f27fcf12a30b8294947e9ccbe36bf47c45181)) Update dependency net.harawata:appdirs to v1.3.0 ([#1219](https://github.com/Suwayomi/Suwayomi-Server/pull/1219) by @renovate[bot])
- ([r1659](https://github.com/Suwayomi/Suwayomi-Server/commit/8dc4eaf77abf578f5f0bfcb1683ef50dcfe5657f)) Update dependency io.insert-koin:koin-core to v4.0.1 ([#1213](https://github.com/Suwayomi/Suwayomi-Server/pull/1213) by @renovate[bot])
- ([r1658](https://github.com/Suwayomi/Suwayomi-Server/commit/a55bef08a820f92bdaaea373aadff6d5c89fde94)) Update kotlinx-coroutines monorepo to v1.10.1 ([#1212](https://github.com/Suwayomi/Suwayomi-Server/pull/1212) by @renovate[bot])
- ([r1657](https://github.com/Suwayomi/Suwayomi-Server/commit/0dc308973949c06eda5643e3a91694ffb02d7296)) Update dependency gradle to v8.12 ([#1211](https://github.com/Suwayomi/Suwayomi-Server/pull/1211) by @renovate[bot])
- ([r1656](https://github.com/Suwayomi/Suwayomi-Server/commit/789ef0d7838f2d7790ca06caa31659340c1575e8)) Update dependency io.mockk:mockk to v1.13.16 ([#1210](https://github.com/Suwayomi/Suwayomi-Server/pull/1210) by @renovate[bot])
- ([r1655](https://github.com/Suwayomi/Suwayomi-Server/commit/092db1106d1841ebf15ea01812102985aade11bc)) Update dependency ch.qos.logback:logback-classic to v1.5.16 ([#1209](https://github.com/Suwayomi/Suwayomi-Server/pull/1209) by @renovate[bot])
- ([r1654](https://github.com/Suwayomi/Suwayomi-Server/commit/5f4b5bc57078a20e8badc129205a7ef80b57a047)) Update dependency io.javalin:javalin to v6.4.0 ([#1205](https://github.com/Suwayomi/Suwayomi-Server/pull/1205) by @renovate[bot])
- ([r1653](https://github.com/Suwayomi/Suwayomi-Server/commit/32581fcd5a6d89f095de02800ab4f80ba7da1349)) [WIP] Customize JRE ([#1177](https://github.com/Suwayomi/Suwayomi-Server/pull/1177) by @Syer10)
- ([r1652](https://github.com/Suwayomi/Suwayomi-Server/commit/b14d28c4068e7ee99b13c1228cfe74031a901381)) new icons ([#1222](https://github.com/Suwayomi/Suwayomi-Server/pull/1222) by @Robonau)
- ([r1651](https://github.com/Suwayomi/Suwayomi-Server/commit/1d1535dc55f76ebeb4a90412f2f1e2e4cb69f82d)) Send dequeue download mutation response ([#1218](https://github.com/Suwayomi/Suwayomi-Server/pull/1218) by @schroda)
- ([r1650](https://github.com/Suwayomi/Suwayomi-Server/commit/de942440e3dbd81207f048a1d13a5fbdb4a1c73c)) Handle missing track search results on bind ([#1196](https://github.com/Suwayomi/Suwayomi-Server/pull/1196) by @schroda)
- ([r1649](https://github.com/Suwayomi/Suwayomi-Server/commit/b58fc39cf157a922c85de966d16b536c447217e5)) Update dependency io.github.oshai:kotlin-logging-jvm to v7.0.3 ([#1191](https://github.com/Suwayomi/Suwayomi-Server/pull/1191) by @renovate[bot])
- ([r1648](https://github.com/Suwayomi/Suwayomi-Server/commit/2e3af25dd443bd7c880c47403bd2419414cc879b)) Fix usage of deprecated functions ([#1192](https://github.com/Suwayomi/Suwayomi-Server/pull/1192) by @Syer10)
- ([r1647](https://github.com/Suwayomi/Suwayomi-Server/commit/1d541a30ae03fd69ba049d0611eab41c92785146)) Feature/update to exposed v0.57.0 ([#1150](https://github.com/Suwayomi/Suwayomi-Server/pull/1150) by @schroda)
- ([r1646](https://github.com/Suwayomi/Suwayomi-Server/commit/f926714544ebeb21229ceace7f3e79462d38f5f9)) Update dependency com.pinterest.ktlint:ktlint-cli to v1.5.0 ([#1182](https://github.com/Suwayomi/Suwayomi-Server/pull/1182) by @renovate[bot])
- ([r1645](https://github.com/Suwayomi/Suwayomi-Server/commit/f68849d3a5445519cb0d35cff8792d1c466a8c5b)) Update dependency com.russhwolf:multiplatform-settings-jvm to v1.3.0 ([#1176](https://github.com/Suwayomi/Suwayomi-Server/pull/1176) by @renovate[bot])
- ([r1644](https://github.com/Suwayomi/Suwayomi-Server/commit/2111232f42a84935320da2e2771ea171c48fd754)) Update kotlin monorepo to v2.1.0 ([#1170](https://github.com/Suwayomi/Suwayomi-Server/pull/1170) by @renovate[bot], @Syer10)
- ([r1643](https://github.com/Suwayomi/Suwayomi-Server/commit/088552bf56dbf9053442050137af2eae3b554bf3)) Fix Deprecation Warnings ([#1187](https://github.com/Suwayomi/Suwayomi-Server/pull/1187) by @Syer10)
- ([r1642](https://github.com/Suwayomi/Suwayomi-Server/commit/3eabbc977007ec0c64f0fadb974470ec437cc39a)) Manually update GraphQL-Java to fix subscription data loaders ([#1186](https://github.com/Suwayomi/Suwayomi-Server/pull/1186) by @Syer10)
- ([r1641](https://github.com/Suwayomi/Suwayomi-Server/commit/8e3b8df4978c838cfb93ad6061dec97b00112aab)) Update dependency com.android.tools.build:apksig to v8.7.3 ([#1179](https://github.com/Suwayomi/Suwayomi-Server/pull/1179) by @renovate[bot])
- ([r1640](https://github.com/Suwayomi/Suwayomi-Server/commit/06a5aaaa729f29e83045691b57f8f74fb5e849ff)) Update dependency com.fasterxml.jackson.core:jackson-databind to v2.18.2 ([#1175](https://github.com/Suwayomi/Suwayomi-Server/pull/1175) by @renovate[bot])
- ([r1639](https://github.com/Suwayomi/Suwayomi-Server/commit/97c4f14094964dce7446199652dd2ed448ef1d5d)) Update dependency org.jsoup:jsoup to v1.18.3 ([#1169](https://github.com/Suwayomi/Suwayomi-Server/pull/1169) by @renovate[bot])
- ([r1638](https://github.com/Suwayomi/Suwayomi-Server/commit/1fa7f182356b530ddfc46c987b7345c34449327a)) Update plugin ktlint to v12.1.2 ([#1166](https://github.com/Suwayomi/Suwayomi-Server/pull/1166) by @renovate[bot])
- ([r1637](https://github.com/Suwayomi/Suwayomi-Server/commit/b309d2fd4af31e424f103d8c4a6fb372f1420c78)) Update dependency gradle to v8.11.1 ([#1161](https://github.com/Suwayomi/Suwayomi-Server/pull/1161) by @renovate[bot])
- ([r1636](https://github.com/Suwayomi/Suwayomi-Server/commit/372b56bb1bb1ee9e81b7081c1b40e52cb2f0065c)) add manga description ([#1165](https://github.com/Suwayomi/Suwayomi-Server/pull/1165) by @dejavui)
- ([r1635](https://github.com/Suwayomi/Suwayomi-Server/commit/3325a36caefc6423b02cb4fd35685a61556bd946)) Allow cors with credentials ([#1163](https://github.com/Suwayomi/Suwayomi-Server/pull/1163) by @schroda)
- ([r1634](https://github.com/Suwayomi/Suwayomi-Server/commit/38673bbff4fd0f5f3eb17e9d52ef20a9f91ddd28)) Handle missing credentials as being invalid ([#1164](https://github.com/Suwayomi/Suwayomi-Server/pull/1164) by @schroda)
- ([r1633](https://github.com/Suwayomi/Suwayomi-Server/commit/fb51834153b33060779e93d3c460f55878a712cb)) Fix RealUrl ([#1162](https://github.com/Suwayomi/Suwayomi-Server/pull/1162) by @Syer10)
- ([r1632](https://github.com/Suwayomi/Suwayomi-Server/commit/3a932a1e8acbc0242f5ae75a12e6bf404b101dd3)) [skip ci] Update plugin buildconfig to v5.5.1 ([#1157](https://github.com/Suwayomi/Suwayomi-Server/pull/1157) by @renovate[bot])
- ([r1631](https://github.com/Suwayomi/Suwayomi-Server/commit/53c61bcb1784d702191d20e2f9239bab19b9ab33)) Serve webui on all unmatched routes ([#1156](https://github.com/Suwayomi/Suwayomi-Server/pull/1156) by @schroda)
- ([r1630](https://github.com/Suwayomi/Suwayomi-Server/commit/6951b4b20d6993c4fada4cf50d745281b7cc3f03)) Remove "grapqhl log level" setting ([#1155](https://github.com/Suwayomi/Suwayomi-Server/pull/1155) by @schroda)
- ([r1629](https://github.com/Suwayomi/Suwayomi-Server/commit/746f9f1a11e2691e88f1b21bfaa7b63c1aa0d66f)) [WIP] Switch to GraalJS Engine ([#793](https://github.com/Suwayomi/Suwayomi-Server/pull/793) by @Syer10)
- ([r1628](https://github.com/Suwayomi/Suwayomi-Server/commit/9a7344ccbe9307fe056b542a5cdebd9540d75982)) Update dependency ch.qos.logback:logback-classic to v1.5.12 ([#1151](https://github.com/Suwayomi/Suwayomi-Server/pull/1151) by @renovate[bot])
- ([r1627](https://github.com/Suwayomi/Suwayomi-Server/commit/ab2fb8747f1744301a4c098eee8fcb1c3b4f8a6f)) Update jackson monorepo to v2.18.1 ([#1148](https://github.com/Suwayomi/Suwayomi-Server/pull/1148) by @renovate[bot])
- ([r1626](https://github.com/Suwayomi/Suwayomi-Server/commit/9cd8cb3d54635b10dadf4ec252b6e5ec95ac023c)) Update dependency io.javalin:javalin to v6 ([#1152](https://github.com/Suwayomi/Suwayomi-Server/pull/1152) by @renovate[bot], @Syer10)
- ([r1625](https://github.com/Suwayomi/Suwayomi-Server/commit/ba1c2845b6d40dea32a148febdc59fe540e4e032)) chore(deps): update plugin buildconfig to v5 ([#1135](https://github.com/Suwayomi/Suwayomi-Server/pull/1135) by @renovate[bot])
- ([r1624](https://github.com/Suwayomi/Suwayomi-Server/commit/065aa19e9eaed26709b5516bc9da95001101c9bd)) Update graphqlkotlin to v8 (major) ([#1143](https://github.com/Suwayomi/Suwayomi-Server/pull/1143) by @renovate[bot], @Syer10)
- ([r1623](https://github.com/Suwayomi/Suwayomi-Server/commit/4c2a05c3a6943b37834e3ac29372c74d527b34df)) Update Java to 21 ([#1149](https://github.com/Suwayomi/Suwayomi-Server/pull/1149) by @Syer10)
- ([r1622](https://github.com/Suwayomi/Suwayomi-Server/commit/fd45c0740c87c959d4a074f45fab77ac07478ea8)) [skip ci] Update Renovate config (by @Syer10)
- ([r1621](https://github.com/Suwayomi/Suwayomi-Server/commit/e44bf920fad7042ed11499a0a014b94888331e09)) [skip ci] Update Renovate config (by @Syer10)
- ([r1620](https://github.com/Suwayomi/Suwayomi-Server/commit/8a327b2dffc361e7ece397ea690c294c77b5a715)) [skip ci] chore(config): migrate config renovate.json ([#1144](https://github.com/Suwayomi/Suwayomi-Server/pull/1144) by @renovate[bot])
- ([r1619](https://github.com/Suwayomi/Suwayomi-Server/commit/6ece7e2596dda11bfd4a97a391e5e5d60f8ecff9)) Update Renovate config (by @Syer10)
- ([r1618](https://github.com/Suwayomi/Suwayomi-Server/commit/2e2ce98be3715dc1063dc07ed0114cbc87b051e3)) fix(deps): update dependency com.pinterest.ktlint:ktlint-cli to v1.4.1 ([#1126](https://github.com/Suwayomi/Suwayomi-Server/pull/1126) by @renovate[bot])
- ([r1617](https://github.com/Suwayomi/Suwayomi-Server/commit/fe4c2392dbc83ba810d2279359b84cc95ac67848)) chore(deps): update plugin ktlint to v12 ([#1136](https://github.com/Suwayomi/Suwayomi-Server/pull/1136) by @renovate[bot])
- ([r1616](https://github.com/Suwayomi/Suwayomi-Server/commit/320a0971b4aaac057ab33c0afa2942dc004680fc)) Fix/gql download subscription ([#1137](https://github.com/Suwayomi/Suwayomi-Server/pull/1137) by @schroda)
- ([r1615](https://github.com/Suwayomi/Suwayomi-Server/commit/bfb70b6a055b48dbe5e02cda661add860b596db0)) fix(deps): update dependency com.ibm.icu:icu4j to v76 ([#1140](https://github.com/Suwayomi/Suwayomi-Server/pull/1140) by @renovate[bot])
- ([r1614](https://github.com/Suwayomi/Suwayomi-Server/commit/a68af62748e3c3e0d210868d2fe98ae561093b01)) fix(deps): update twelvemonkeys to v3.12.0 ([#1133](https://github.com/Suwayomi/Suwayomi-Server/pull/1133) by @renovate[bot])
- ([r1613](https://github.com/Suwayomi/Suwayomi-Server/commit/52bd5ce5cc8758930d1b39deaa15be2cae12ebb4)) fix(deps): update kotlinx-coroutines monorepo to v1.9.0 ([#1132](https://github.com/Suwayomi/Suwayomi-Server/pull/1132) by @renovate[bot])
- ([r1612](https://github.com/Suwayomi/Suwayomi-Server/commit/b93d4863483d3d57690279b5c1e2343ea585f008)) fix(deps): update dependency org.bouncycastle:bcprov-jdk18on to v1.79 ([#1127](https://github.com/Suwayomi/Suwayomi-Server/pull/1127) by @renovate[bot])
- ([r1611](https://github.com/Suwayomi/Suwayomi-Server/commit/d193c58e5f86f74d3dbf67aca8b16694a68dc119)) fix(deps): update dependency androidx.annotation:annotation to v1.9.1 ([#1121](https://github.com/Suwayomi/Suwayomi-Server/pull/1121) by @renovate[bot])
- ([r1610](https://github.com/Suwayomi/Suwayomi-Server/commit/6ac2a61793eea51ceccd663d2c8a2570709489b9)) Update serialization to v1.7.3 ([#1119](https://github.com/Suwayomi/Suwayomi-Server/pull/1119) by @renovate[bot])
- ([r1609](https://github.com/Suwayomi/Suwayomi-Server/commit/a45c6f2197c204b498d0d5f669b2872edd26d426)) Update kotlin monorepo to v2.0.21 ([#1117](https://github.com/Suwayomi/Suwayomi-Server/pull/1117) by @renovate[bot])
- ([r1608](https://github.com/Suwayomi/Suwayomi-Server/commit/71d639bf19f7d25f885a29b16dcf776a9ea2c6ee)) Update dependency io.mockk:mockk to v1.13.13 ([#1116](https://github.com/Suwayomi/Suwayomi-Server/pull/1116) by @renovate[bot])
- ([r1607](https://github.com/Suwayomi/Suwayomi-Server/commit/9a51472726079b30f73eef8c19716061328c715b)) Update Titles from the Source ([#1115](https://github.com/Suwayomi/Suwayomi-Server/pull/1115) by @Syer10)
- ([r1606](https://github.com/Suwayomi/Suwayomi-Server/commit/0670f298cd70535c8b719af6bd86600a5124369c)) Switch from Kodein to Koin ([#1112](https://github.com/Suwayomi/Suwayomi-Server/pull/1112) by @Syer10)
- ([r1605](https://github.com/Suwayomi/Suwayomi-Server/commit/aa1e98544b8a11812675a27b4d95db6c7e33a283)) Fix/invalid server settings gql mutation request ([#1092](https://github.com/Suwayomi/Suwayomi-Server/pull/1092) by @schroda)
- ([r1604](https://github.com/Suwayomi/Suwayomi-Server/commit/fa4607e232fd94a8b299797cb23d0e630ccb8c04)) Update dependency gradle to v8.11 ([#1091](https://github.com/Suwayomi/Suwayomi-Server/pull/1091) by @renovate[bot])
- ([r1603](https://github.com/Suwayomi/Suwayomi-Server/commit/cb46420c09d08f9c7337830e2ea2e53bb0c8a711)) Update dependency com.android.tools.build:apksig to v8 ([#1076](https://github.com/Suwayomi/Suwayomi-Server/pull/1076) by @renovate[bot])
- ([r1602](https://github.com/Suwayomi/Suwayomi-Server/commit/d88014fa904421c213ceab0b59fe5139fa52d1e8)) Update xmlserialization ([#1075](https://github.com/Suwayomi/Suwayomi-Server/pull/1075) by @renovate[bot], @Syer10)
- ([r1601](https://github.com/Suwayomi/Suwayomi-Server/commit/168b76cb0c2c424f41380379b3da534c2ebcdd01)) Feature/graphql download queue subscription send only updates ([#1011](https://github.com/Suwayomi/Suwayomi-Server/pull/1011) by @schroda)
- ([r1600](https://github.com/Suwayomi/Suwayomi-Server/commit/f5680c6d695ddd58c2cf38538ec54cf44db3b857)) Switch to Koin from Injekt ([#1109](https://github.com/Suwayomi/Suwayomi-Server/pull/1109) by @schroda)
- ([r1599](https://github.com/Suwayomi/Suwayomi-Server/commit/654a3cc7ed6134afc76be529924317ff5c1f0a9b)) Use Backup.serializer() ([#1088](https://github.com/Suwayomi/Suwayomi-Server/pull/1088) by @Syer10)
- ([r1598](https://github.com/Suwayomi/Suwayomi-Server/commit/841cdc474f393433f33c928eb5e20b7bc35a1a01)) Remove Broken Sources and Broken History ([#1084](https://github.com/Suwayomi/Suwayomi-Server/pull/1084) by @Syer10)
- ([r1597](https://github.com/Suwayomi/Suwayomi-Server/commit/0adbea3a43c021c072e276b0bfe867527006a5bd)) Remove manga artist, author length limit ([#1080](https://github.com/Suwayomi/Suwayomi-Server/pull/1080) by @schroda)
- ([r1596](https://github.com/Suwayomi/Suwayomi-Server/commit/e12bada052d1622f68b9061a174afb9b273dbf9c)) Use correct sync id ([#1079](https://github.com/Suwayomi/Suwayomi-Server/pull/1079) by @schroda)
- ([r1595](https://github.com/Suwayomi/Suwayomi-Server/commit/68dbefc46f1313a306404e6780c4e987c8018bf1)) Update dependency gradle to v8.10.1 ([#1072](https://github.com/Suwayomi/Suwayomi-Server/pull/1072) by @renovate[bot])
- ([r1594](https://github.com/Suwayomi/Suwayomi-Server/commit/9d71e9b177cb88bfe1ec954af57593ce9f366651)) Update twelvemonkeys to v3.11.0 ([#1069](https://github.com/Suwayomi/Suwayomi-Server/pull/1069) by @renovate[bot])
- ([r1593](https://github.com/Suwayomi/Suwayomi-Server/commit/5b5801c2cfd096886871305cfbb915565ae9ec4f)) Update dependency com.squareup.okio:okio to v3.9.1 ([#1071](https://github.com/Suwayomi/Suwayomi-Server/pull/1071) by @renovate[bot])
- ([r1592](https://github.com/Suwayomi/Suwayomi-Server/commit/df1cc2b8e993f542719dfab23d0edcaf34228024)) fix(flaresolverr): fix cookie expiry for flaresolverr ([#1070](https://github.com/Suwayomi/Suwayomi-Server/pull/1070) by @Belphemur)
- ([r1591](https://github.com/Suwayomi/Suwayomi-Server/commit/18d399b3f748ca0863177a791befdc0096bf7610)) Update settings to v1.2.0 ([#1068](https://github.com/Suwayomi/Suwayomi-Server/pull/1068) by @renovate[bot])
- ([r1590](https://github.com/Suwayomi/Suwayomi-Server/commit/e9687fd18207941261d89f4b49f0d92b6e353a0f)) Update serialization to v1.7.2 ([#1067](https://github.com/Suwayomi/Suwayomi-Server/pull/1067) by @renovate[bot])
- ([r1589](https://github.com/Suwayomi/Suwayomi-Server/commit/79137a074cba1d07a3db46a7920a340e26118b0e)) Update plugin download to v5.6.0 ([#1066](https://github.com/Suwayomi/Suwayomi-Server/pull/1066) by @renovate[bot])
- ([r1588](https://github.com/Suwayomi/Suwayomi-Server/commit/dae55ca386a1313cb8f7b5aa6ee065de51d25a95)) Update graphqlkotlin to v6.8.5 ([#1064](https://github.com/Suwayomi/Suwayomi-Server/pull/1064) by @renovate[bot], @Syer10)
- ([r1587](https://github.com/Suwayomi/Suwayomi-Server/commit/b7f040d89aada015f381ab0ab69c8fb88d026407)) Update dependency org.jsoup:jsoup to v1.18.1 ([#1060](https://github.com/Suwayomi/Suwayomi-Server/pull/1060) by @renovate[bot])
- ([r1586](https://github.com/Suwayomi/Suwayomi-Server/commit/dc69df9f4fcb7895c80514630d0b1ead38362784)) Update dependency com.squareup.okio:okio to v3.9.0 ([#1056](https://github.com/Suwayomi/Suwayomi-Server/pull/1056) by @renovate[bot])
- ([r1585](https://github.com/Suwayomi/Suwayomi-Server/commit/6c1fbfa63b6b1dfebac59b0e59e153338cce654a)) [skip ci] Formatting (by @Syer10)
- ([r1584](https://github.com/Suwayomi/Suwayomi-Server/commit/e968a2195a325988fd1b05e7343d8bff08be0e37)) [skip ci] Update dependency com.pinterest.ktlint:ktlint-cli to v1.3.1 ([#1055](https://github.com/Suwayomi/Suwayomi-Server/pull/1055) by @renovate[bot])
- ([r1583](https://github.com/Suwayomi/Suwayomi-Server/commit/000bcea181c69a259a51901884596ee336704dbc)) [skip ci] Update SystemTray (by @Syer10)
- ([r1582](https://github.com/Suwayomi/Suwayomi-Server/commit/8edf508453949be7054d3b9c15922b79b7882463)) [skip ci] Update dependency org.apache.commons:commons-compress to v1.27.1 ([#1058](https://github.com/Suwayomi/Suwayomi-Server/pull/1058) by @renovate[bot])
- ([r1581](https://github.com/Suwayomi/Suwayomi-Server/commit/bc9cc50130c9b1a49aa35fff8c1867eb682ed604)) [skip ci] Update dependency org.bouncycastle:bcprov-jdk18on to v1.78.1 ([#1059](https://github.com/Suwayomi/Suwayomi-Server/pull/1059) by @renovate[bot])
- ([r1580](https://github.com/Suwayomi/Suwayomi-Server/commit/71091d88fc6f441d818a235bac7f436bc4ce857f)) [skip ci] Update dependency com.android.tools.build:apksig to v7.4.2 ([#1049](https://github.com/Suwayomi/Suwayomi-Server/pull/1049) by @renovate[bot])
- ([r1579](https://github.com/Suwayomi/Suwayomi-Server/commit/70c1d7e21f52b7beb12a70794b396522f1c44247)) [skip ci] Update dependency io.github.config4k:config4k to v0.7.0 ([#1057](https://github.com/Suwayomi/Suwayomi-Server/pull/1057) by @renovate[bot])
- ([r1578](https://github.com/Suwayomi/Suwayomi-Server/commit/c07920978ed33e5399a6116766a931f0f62a049f)) Update tachiyomiorg/issue-moderator-action action to v2 ([#1032](https://github.com/Suwayomi/Suwayomi-Server/pull/1032) by @renovate[bot], @Syer10)
- ([r1577](https://github.com/Suwayomi/Suwayomi-Server/commit/954b2919ac7b9e1d4dccfca68768c293cfd09095)) [skip ci] Update plugin ktlint to v11.6.1 ([#1043](https://github.com/Suwayomi/Suwayomi-Server/pull/1043) by @renovate[bot])
- ([r1576](https://github.com/Suwayomi/Suwayomi-Server/commit/c630f731ed90ba6581487ac7f295315b405593c2)) [skip ci] Update dependency androidx.annotation:annotation to v1.8.2 ([#1046](https://github.com/Suwayomi/Suwayomi-Server/pull/1046) by @renovate[bot])
- ([r1575](https://github.com/Suwayomi/Suwayomi-Server/commit/aaefa7f74e5daa96315ef10e8beead3d16c58acd)) [skip ci] Update coroutines to v1.8.1 ([#1045](https://github.com/Suwayomi/Suwayomi-Server/pull/1045) by @renovate[bot])
- ([r1574](https://github.com/Suwayomi/Suwayomi-Server/commit/2ec6b471f15aa08d204c81b217e786ad04f5e378)) [skip ci] Update dependency gradle to v8.10 ([#1047](https://github.com/Suwayomi/Suwayomi-Server/pull/1047) by @renovate[bot])
- ([r1573](https://github.com/Suwayomi/Suwayomi-Server/commit/a5c5ab68d278d6527731bdffb43a2830396de4dd)) [skip ci] Ktlint (by @Syer10)
- ([r1572](https://github.com/Suwayomi/Suwayomi-Server/commit/fb045c501af783e18bc05622bd207f37e5c25ae5)) Update Kotlin to 2.0.20 (by @Syer10)
- ([r1571](https://github.com/Suwayomi/Suwayomi-Server/commit/6a6e411492cd9c9c7a31a420124c74da89f74c0f)) [skip ci] Update dependency net.harawata:appdirs to v1.2.2 ([#1033](https://github.com/Suwayomi/Suwayomi-Server/pull/1033) by @renovate[bot])
- ([r1570](https://github.com/Suwayomi/Suwayomi-Server/commit/76aac330fc111c3748185e0605c7ef321aafd5fb)) [skip ci] Update dependency org.slf4j:slf4j-api to v2.0.16 ([#1034](https://github.com/Suwayomi/Suwayomi-Server/pull/1034) by @renovate[bot])
- ([r1569](https://github.com/Suwayomi/Suwayomi-Server/commit/07bdf31f66805dd09c962e4e0a542dce541371de)) [skip ci] Update okhttp monorepo to v5.0.0-alpha.14 ([#1039](https://github.com/Suwayomi/Suwayomi-Server/pull/1039) by @renovate[bot])
- ([r1568](https://github.com/Suwayomi/Suwayomi-Server/commit/fe14928af6b7ff7cbf339806f0880c6b64f56eb6)) [skip ci] Update rhino to v1.7.15 ([#1044](https://github.com/Suwayomi/Suwayomi-Server/pull/1044) by @renovate[bot])
- ([r1567](https://github.com/Suwayomi/Suwayomi-Server/commit/ad0c1033a4b087b6f2c13be3c8f9a5a3ad275002)) [skip ci] Update GitHub Artifact Actions to v4 ([#1040](https://github.com/Suwayomi/Suwayomi-Server/pull/1040) by @renovate[bot])
- ([r1566](https://github.com/Suwayomi/Suwayomi-Server/commit/0c2448fb99ca7cf77a6a02dd0a7a277ee7cfa2f9)) Update gradle build action ([#1035](https://github.com/Suwayomi/Suwayomi-Server/pull/1035) by @Syer10)
- ([r1565](https://github.com/Suwayomi/Suwayomi-Server/commit/cedda145a5caa16b89d767cc393efe52d40d7bee)) Update gradle/gradle-build-action action to v3 ([#1029](https://github.com/Suwayomi/Suwayomi-Server/pull/1029) by @renovate[bot])
- ([r1564](https://github.com/Suwayomi/Suwayomi-Server/commit/ee73187f1a092db69136a2a11daa40d2cf9df581)) [skip ci] Update gradle/wrapper-validation-action action to v3 ([#1030](https://github.com/Suwayomi/Suwayomi-Server/pull/1030) by @renovate[bot])
- ([r1563](https://github.com/Suwayomi/Suwayomi-Server/commit/89f91d680064e6c3330bdc83d2f34b0c6f1f92cd)) [skip ci] Update actions/checkout action to v4 ([#1028](https://github.com/Suwayomi/Suwayomi-Server/pull/1028) by @renovate[bot])
- ([r1562](https://github.com/Suwayomi/Suwayomi-Server/commit/6714827694842ff55085495d016049936d74e99e)) [skip ci] Update softprops/action-gh-release action to v2 ([#1031](https://github.com/Suwayomi/Suwayomi-Server/pull/1031) by @renovate[bot])
- ([r1561](https://github.com/Suwayomi/Suwayomi-Server/commit/aad73f7d199173d4c75dab3759753e8250c25f91)) [skip ci] Update dependency io.mockk:mockk to v1.13.12 ([#1026](https://github.com/Suwayomi/Suwayomi-Server/pull/1026) by @renovate[bot])
- ([r1560](https://github.com/Suwayomi/Suwayomi-Server/commit/c5985de1c34839a561c421b978e90f11825c39e5)) [skip ci] Update dependency com.typesafe:config to v1.4.3 ([#1025](https://github.com/Suwayomi/Suwayomi-Server/pull/1025) by @renovate[bot])
- ([r1559](https://github.com/Suwayomi/Suwayomi-Server/commit/86a5b0879a9d65589dfe0b241c8a2539a30e9a20)) Add renovate.json ([#1024](https://github.com/Suwayomi/Suwayomi-Server/pull/1024) by @renovate[bot])
- ([r1558](https://github.com/Suwayomi/Suwayomi-Server/commit/414972d54588b7d034902292b5b188b248e1fe27)) Feature/update log file rotation ([#1023](https://github.com/Suwayomi/Suwayomi-Server/pull/1023) by @schroda)
- ([r1557](https://github.com/Suwayomi/Suwayomi-Server/commit/9a74ae5844fd059bf4e8e921a36e48348aa635ca)) feat(comicinfo): add date fields to comic info ([#1021](https://github.com/Suwayomi/Suwayomi-Server/pull/1021) by @Belphemur, @Syer10)
- ([r1556](https://github.com/Suwayomi/Suwayomi-Server/commit/301980ab1439605a69c47a04425d34e1c07cfffd)) fix(flaresolverr): support possible rewrite flaresolverr ([#1020](https://github.com/Suwayomi/Suwayomi-Server/pull/1020) by @Belphemur)
- ([r1555](https://github.com/Suwayomi/Suwayomi-Server/commit/5dced82e5a51929b1e6d65ee8a669564c21d02c9)) Fix/missed automated task execution failure crashes server on startup ([#1019](https://github.com/Suwayomi/Suwayomi-Server/pull/1019) by @schroda)
- ([r1554](https://github.com/Suwayomi/Suwayomi-Server/commit/9a1e4df4084987744ffee37347542c5bbc83a1c9)) Fix/server startup blocked by synchronous tasks ([#1018](https://github.com/Suwayomi/Suwayomi-Server/pull/1018) by @schroda)
- ([r1553](https://github.com/Suwayomi/Suwayomi-Server/commit/5b08b8123939e6befe739056569bc7afc50c665b)) Initialize manga on add to library ([#1016](https://github.com/Suwayomi/Suwayomi-Server/pull/1016) by @schroda)
- ([r1552](https://github.com/Suwayomi/Suwayomi-Server/commit/7fac538ba3c873d1ba69de40ab6c1111f77d3714)) Initialize uninitialized manga during global update ([#1015](https://github.com/Suwayomi/Suwayomi-Server/pull/1015) by @schroda)
- ([r1551](https://github.com/Suwayomi/Suwayomi-Server/commit/ef6be74ec25d0b76db9dc55c986605a90916af36)) Fix/chapter downloaded check ([#1012](https://github.com/Suwayomi/Suwayomi-Server/pull/1012) by @schroda)
- ([r1550](https://github.com/Suwayomi/Suwayomi-Server/commit/9f4958724552441bf4587d11e4f5cbca9f29c858)) [skip ci] Update readme ([#1008](https://github.com/Suwayomi/Suwayomi-Server/pull/1008) by @schroda)
- ([r1549](https://github.com/Suwayomi/Suwayomi-Server/commit/b7b733f351ef3ab0ad69d21812c4d3834ca84185)) [skip ci] Feature/update readme ([#1005](https://github.com/Suwayomi/Suwayomi-Server/pull/1005) by @schroda)
- ([r1548](https://github.com/Suwayomi/Suwayomi-Server/commit/06bfc33e72de3879b3b5da75ada6d98aa0ad18af)) Always update uninitialized manga in global update ([#997](https://github.com/Suwayomi/Suwayomi-Server/pull/997) by @schroda)
- ([r1547](https://github.com/Suwayomi/Suwayomi-Server/commit/fbcd55d6c53d1b7be1ec282ed61648d8fa8467b8)) Add "hasDuplicatedChapters" field to gql MangaType ([#995](https://github.com/Suwayomi/Suwayomi-Server/pull/995) by @schroda)
- ([r1546](https://github.com/Suwayomi/Suwayomi-Server/commit/2484b5f14b0ca14e36f89a798810302825aa9bfa)) Fix/downloaded chapters with lost page count ([#994](https://github.com/Suwayomi/Suwayomi-Server/pull/994) by @schroda)
- ([r1545](https://github.com/Suwayomi/Suwayomi-Server/commit/69826596580768e4927c09c9d3f45d4135e9e56c)) Fix/inserting duplicated chapters into database ([#991](https://github.com/Suwayomi/Suwayomi-Server/pull/991) by @schroda)
- ([r1544](https://github.com/Suwayomi/Suwayomi-Server/commit/9e006166a8af73d7c281f0cb3d51cfabc1b39822)) Add setting to use the flaresolverr response ([#990](https://github.com/Suwayomi/Suwayomi-Server/pull/990) by @AeonLucid)
- ([r1543](https://github.com/Suwayomi/Suwayomi-Server/commit/25a62e33a1893b07689ff5741f7e14051ba202b4)) Fix PersistentCookieStore for domains with an underscore ([#989](https://github.com/Suwayomi/Suwayomi-Server/pull/989) by @AeonLucid)
- ([r1542](https://github.com/Suwayomi/Suwayomi-Server/commit/d05ed0a56c60e4c49d4160b15a30ab713653b55a)) Fix SOCKS5 authentication by setting a default Authenticator ([#988](https://github.com/Suwayomi/Suwayomi-Server/pull/988) by @AeonLucid)
- ([r1541](https://github.com/Suwayomi/Suwayomi-Server/commit/eaffb2755cfae436f4c2b1affe9c125046c2cc55)) Cleanup only created auto backup files ([#984](https://github.com/Suwayomi/Suwayomi-Server/pull/984) by @schroda)
- ([r1540](https://github.com/Suwayomi/Suwayomi-Server/commit/e0fcae2ae35c3d6d4702041b6f489f208f6533ef)) Handle deprecated gql sort again ([#983](https://github.com/Suwayomi/Suwayomi-Server/pull/983) by @schroda)
- ([r1539](https://github.com/Suwayomi/Suwayomi-Server/commit/af9ad611744ced0766cdae980888afec5697201f)) Feature/gql simpilify filtering for multiple values ([#960](https://github.com/Suwayomi/Suwayomi-Server/pull/960) by @schroda)
- ([r1538](https://github.com/Suwayomi/Suwayomi-Server/commit/7c54ad54fc96d37c9d8b59d0dcf2347ebffb7a98)) Sort gql queries by multiple columns ([#963](https://github.com/Suwayomi/Suwayomi-Server/pull/963) by @schroda)
- ([r1537](https://github.com/Suwayomi/Suwayomi-Server/commit/aee9f1032c05e3493124d078ac8a4c0acc5778e9)) Exit early in case of empty chapter id list ([#980](https://github.com/Suwayomi/Suwayomi-Server/pull/980) by @schroda)
- ([r1536](https://github.com/Suwayomi/Suwayomi-Server/commit/f7d0605e0a8b3fb296c8ef186bc67a7ee650a567)) [skip ci] Remove docker commands ([#972](https://github.com/Suwayomi/Suwayomi-Server/pull/972) by @Robonau)
Contributors:
@schroda, @kaaass, @Syer10, @renovate[bot], @ShirishSaxena, @showyee, @cpiber, @zeedif, @Robonau, @dejavui, @Belphemur, @AeonLucid
## [Suwayomi-WebUI Changelog](https://github.com/Suwayomi/Suwayomi-WebUI/blob/master/CHANGELOG.md#v151-r2467)
# Server: v1.1.1 + WevUI: v1.1.0
## TL;DR
- WebUI update bugfixes

View File

@@ -79,7 +79,7 @@ To facilitate the use of Suwayomi we provide bundle releases that include The Ja
If a bundle for your operating system or cpu architecture is not provided then refer to [Advanced Methods](#advanced-methods)
### Windows
Download the latest `win64`(Windows 64-bit) release from [the releases section](https://github.com/Suwayomi/Suwayomi-Server/releases) or a preview one from [the preview repository](https://github.com/Suwayomi/Suwayomi-Server-preview/releases).
Download the latest `win32`(Windows 32-bit) or `win64`(Windows 64-bit) release from [the releases section](https://github.com/Suwayomi/Suwayomi-Server/releases) or a preview one from [the preview repository](https://github.com/Suwayomi/Suwayomi-Server-preview/releases).
Unzip the downloaded file and double-click on one of the launcher scripts.
@@ -137,7 +137,7 @@ You can also directly use the package from [nixpkgs](https://search.nixos.org/pa
### Running the jar release directly
In order to run the app you need the following:
- The jar release of Suwayomi-Server
- The Java Runtime Environment(JRE) 21 or newer
- The Java Runtime Environment(JRE) 8 or newer
- A Browser like Google Chrome, Firefox, Edge, etc.
- ElectronJS (optional)

View File

@@ -1,4 +1,3 @@
import org.jetbrains.kotlin.gradle.dsl.JvmTarget
import org.jetbrains.kotlin.gradle.tasks.KotlinJvmCompile
import org.jlleitschuh.gradle.ktlint.KtlintExtension
import org.jlleitschuh.gradle.ktlint.KtlintPlugin
@@ -27,8 +26,8 @@ allprojects {
subprojects {
plugins.withType<JavaPlugin> {
extensions.configure<JavaPluginExtension> {
sourceCompatibility = JavaVersion.VERSION_21
targetCompatibility = JavaVersion.VERSION_21
sourceCompatibility = JavaVersion.VERSION_1_8
targetCompatibility = JavaVersion.VERSION_1_8
}
}
@@ -44,9 +43,12 @@ subprojects {
tasks {
withType<KotlinJvmCompile> {
dependsOn("ktlintFormat")
compilerOptions {
jvmTarget = JvmTarget.JVM_21
freeCompilerArgs.add("-Xcontext-receivers")
kotlinOptions {
jvmTarget = JavaVersion.VERSION_1_8.toString()
freeCompilerArgs += listOf(
"-Xcontext-receivers",
)
}
}
}

View File

@@ -10,13 +10,14 @@ import java.io.BufferedReader
const val MainClass = "suwayomi.tachidesk.MainKt"
// should be bumped with each stable release
val getTachideskVersion = { "v2.0.${getCommitCount()}" }
val tachideskVersion = System.getenv("ProductVersion") ?: "v1.1.1"
val webUIRevisionTag = "r2467"
val webUIRevisionTag = System.getenv("WebUIRevision") ?: "r1689"
private val getCommitCount = {
// counts commits on the current checked out branch
val getTachideskRevision = {
runCatching {
ProcessBuilder()
System.getenv("ProductRevision") ?: ProcessBuilder()
.command("git", "rev-list", "HEAD", "--count")
.start()
.let { process ->
@@ -25,11 +26,8 @@ private val getCommitCount = {
it.bufferedReader().use(BufferedReader::readText)
}
process.destroy()
output.trim()
"r" + output.trim()
}
}.getOrDefault("0")
}.getOrDefault("r0")
}
// counts commits on the current checked out branch
val getTachideskRevision = { "r${getCommitCount()}" }

View File

@@ -1,19 +1,18 @@
[versions]
kotlin = "2.1.20"
coroutines = "1.10.1"
serialization = "1.8.0"
okhttp = "5.0.0-alpha.14" # Major version is locked by Tachiyomi extensions
javalin = "6.5.0"
jackson = "2.18.3" # jackson version locked by javalin, ref: `io.javalin.core.util.OptionalDependency`
exposed = "0.59.0"
kotlin = "1.9.10"
coroutines = "1.7.3"
serialization = "1.6.0"
okhttp = "5.0.0-alpha.11" # Major version is locked by Tachiyomi extensions
javalin = "4.6.8" # Javalin 5.0.0+ requires Java 11
jackson = "2.13.3" # jackson version locked by javalin, ref: `io.javalin.core.util.OptionalDependency`
exposed = "0.40.1"
dex2jar = "v64" # Stuck until https://github.com/ThexXTURBOXx/dex2jar/issues/27 is fixed
polyglot = "24.2.0"
settings = "1.3.0"
twelvemonkeys = "3.12.0"
graphqlkotlin = "8.4.0"
xmlserialization = "0.90.3"
ktlint = "1.5.0"
koin = "4.0.2"
rhino = "1.7.14"
settings = "1.0.0-RC"
twelvemonkeys = "3.9.4"
graphqlkotlin = "6.5.6"
xmlserialization = "0.86.2"
ktlint = "1.0.0"
[libraries]
# Kotlin
@@ -30,20 +29,20 @@ coroutines-test = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-test", ve
serialization-json = { module = "org.jetbrains.kotlinx:kotlinx-serialization-json", version.ref = "serialization" }
serialization-json-okio = { module = "org.jetbrains.kotlinx:kotlinx-serialization-json-okio", version.ref = "serialization" }
serialization-protobuf = { module = "org.jetbrains.kotlinx:kotlinx-serialization-protobuf", version.ref = "serialization" }
serialization-xml-core = { module = "io.github.pdvrieze.xmlutil:core", version.ref = "xmlserialization" }
serialization-xml-core = { module = "io.github.pdvrieze.xmlutil:core-jvm", version.ref = "xmlserialization" }
serialization-xml = { module = "io.github.pdvrieze.xmlutil:serialization-jvm", version.ref = "xmlserialization" }
# Logging
slf4japi = "org.slf4j:slf4j-api:2.0.17"
logback = "ch.qos.logback:logback-classic:1.5.18"
kotlinlogging = "io.github.oshai:kotlin-logging-jvm:7.0.5"
slf4japi = "org.slf4j:slf4j-api:2.0.9"
logback = "ch.qos.logback:logback-classic:1.3.11"
kotlinlogging = "io.github.microutils:kotlin-logging:3.0.5"
# OkHttp
okhttp-core = { module = "com.squareup.okhttp3:okhttp", version.ref = "okhttp" }
okhttp-logging = { module = "com.squareup.okhttp3:logging-interceptor", version.ref = "okhttp" }
okhttp-dnsoverhttps = { module = "com.squareup.okhttp3:okhttp-dnsoverhttps", version.ref = "okhttp" }
okhttp-brotli = { module = "com.squareup.okhttp3:okhttp-brotli", version.ref = "okhttp" }
okio = "com.squareup.okio:okio:3.10.2"
okio = "com.squareup.okio:okio:3.3.0"
# Javalin api
javalin-core = { module = "io.javalin:javalin", version.ref = "javalin" }
@@ -55,8 +54,7 @@ jackson-annotations = { module = "com.fasterxml.jackson.core:jackson-annotations
# GraphQL
graphql-kotlin-server = { module = "com.expediagroup:graphql-kotlin-server", version.ref = "graphqlkotlin" }
graphql-kotlin-scheme = { module = "com.expediagroup:graphql-kotlin-schema-generator", version.ref = "graphqlkotlin" }
graphql-java-core = "com.graphql-java:graphql-java:22.3" # Major version locked by graphql-kotlin
graphql-java-scalars = "com.graphql-java:graphql-java-extended-scalars:22.0"
graphql-scalars = "com.graphql-java:graphql-java-extended-scalars:20.2"
# Exposed ORM
exposed-core = { module = "org.jetbrains.exposed:exposed-core", version.ref = "exposed" }
@@ -66,24 +64,24 @@ exposed-javatime = { module = "org.jetbrains.exposed:exposed-java-time", version
h2 = "com.h2database:h2:1.4.200" # current database driver, can't update to h2 v2 without sql migration
# Exposed Migrations
exposed-migrations = "com.github.Suwayomi:exposed-migrations:3.7.0"
exposed-migrations = "com.github.Suwayomi:exposed-migrations:3.2.0"
# Dependency Injection
koin-core = { module = "io.insert-koin:koin-core", version.ref = "koin" }
kodein = "org.kodein.di:kodein-di-conf-jvm:7.20.2"
# tray icon
systemtray-core = "com.dorkbox:SystemTray:4.4"
systemtray-utils = "com.dorkbox:Utilities:1.46" # version locked by SystemTray
systemtray-desktop = "com.dorkbox:Desktop:1.1" # version locked by SystemTray
systemtray-core = "com.dorkbox:SystemTray:4.2.1"
systemtray-utils = "com.dorkbox:Utilities:1.39" # version locked by SystemTray
systemtray-desktop = "com.dorkbox:Desktop:1.0"
# dependencies of Tachiyomi extensions
injekt = "com.github.null2264:injekt-koin:ee267b2e27"
injekt = "com.github.inorichi.injekt:injekt-core:65b0440"
rxjava = "io.reactivex:rxjava:1.3.8"
jsoup = "org.jsoup:jsoup:1.19.1"
jsoup = "org.jsoup:jsoup:1.16.1"
# Config
config = "com.typesafe:config:1.4.3"
config4k = "io.github.config4k:config4k:0.7.0"
config4k = "io.github.config4k:config4k:0.5.0"
# Sort
sort = "com.github.gpanther:java-nat-sort:natural-comparator-1.1"
@@ -98,34 +96,33 @@ dex2jar-tools = { module = "com.github.ThexXTURBOXx.dex2jar:dex-tools", version.
# APK
apk-parser = "net.dongliu:apk-parser:2.6.10"
apksig = "com.android.tools.build:apksig:8.9.0"
apksig = "com.android.tools.build:apksig:7.2.1"
# Xml
xmlpull = "xmlpull:xmlpull:1.1.3.4a"
# Disk & File
appdirs = "net.harawata:appdirs:1.4.0"
cache4k = "io.github.reactivecircus.cache4k:cache4k:0.14.0"
appdirs = "net.harawata:appdirs:1.2.1"
zip4j = "net.lingala.zip4j:zip4j:2.11.5"
commonscompress = "org.apache.commons:commons-compress:1.27.1"
commonscompress = "org.apache.commons:commons-compress:1.24.0"
junrar = "com.github.junrar:junrar:7.5.5"
# AES/CBC/PKCS7Padding Cypher provider
bouncycastle = "org.bouncycastle:bcprov-jdk18on:1.80"
bouncycastle = "org.bouncycastle:bcprov-jdk18on:1.76"
# AndroidX annotations
android-annotations = "androidx.annotation:annotation:1.9.1"
android-annotations = "androidx.annotation:annotation:1.7.0"
# Substitute for duktape-android
polyglot-core = { module = "org.graalvm.polyglot:polyglot", version.ref = "polyglot" }
polyglot-graaljs = { module = "org.graalvm.polyglot:js-community", version.ref = "polyglot" }
rhino-runtime = { module = "org.mozilla:rhino-runtime", version.ref = "rhino" } # slimmer version of 'org.mozilla:rhino'
rhino-engine = { module = "org.mozilla:rhino-engine", version.ref = "rhino" } # provides the same interface as 'javax.script' a.k.a Nashorn
# Settings
settings-core = { module = "com.russhwolf:multiplatform-settings-jvm", version.ref = "settings" }
settings-serialization = { module = "com.russhwolf:multiplatform-settings-serialization-jvm", version.ref = "settings" }
# ICU4J
icu4j = "com.ibm.icu:icu4j:77.1"
icu4j = "com.ibm.icu:icu4j:73.2"
# Image Decoding implementation provider
twelvemonkeys-common-lang = { module = "com.twelvemonkeys.common:common-lang", version.ref = "twelvemonkeys" }
@@ -137,7 +134,7 @@ twelvemonkeys-imageio-jpeg = { module = "com.twelvemonkeys.imageio:imageio-jpeg"
twelvemonkeys-imageio-webp = { module = "com.twelvemonkeys.imageio:imageio-webp", version.ref = "twelvemonkeys" }
# Testing
mockk = "io.mockk:mockk:1.13.17"
mockk = "io.mockk:mockk:1.13.12"
# cron scheduler
cron4j = "it.sauronsoftware.cron4j:cron4j:2.2.5"
@@ -145,22 +142,19 @@ cron4j = "it.sauronsoftware.cron4j:cron4j:2.2.5"
# cron-utils
cronUtils = "com.cronutils:cron-utils:9.2.1"
# lint - used for renovate to update ktlint version
ktlint = { module = "com.pinterest.ktlint:ktlint-cli", version.ref = "ktlint" }
[plugins]
# Kotlin
kotlin-jvm = { id = "org.jetbrains.kotlin.jvm", version.ref = "kotlin"}
kotlin-serialization = { id = "org.jetbrains.kotlin.plugin.serialization", version.ref = "kotlin"}
# Linter
ktlint = { id = "org.jlleitschuh.gradle.ktlint", version = "12.2.0"}
ktlint = { id = "org.jlleitschuh.gradle.ktlint", version = "11.6.0"}
# Build config
buildconfig = { id = "com.github.gmazzo.buildconfig", version = "5.5.4"}
buildconfig = { id = "com.github.gmazzo.buildconfig", version = "3.1.0"}
# Download
download = { id = "de.undercouch.download", version = "5.6.0"}
download = { id = "de.undercouch.download", version = "5.4.0"}
# ShadowJar
shadowjar = { id = "com.github.johnrengelman.shadow", version = "8.1.1"}
@@ -174,7 +168,7 @@ shared = [
"serialization-json",
"serialization-json-okio",
"serialization-protobuf",
"koin-core",
"kodein",
"slf4japi",
"logback",
"kotlinlogging",
@@ -202,7 +196,7 @@ okhttp = [
]
javalin = [
"javalin-core",
#"javalin-openapi",
"javalin-openapi",
]
jackson = [
"jackson-databind",
@@ -220,9 +214,9 @@ systemtray = [
"systemtray-utils",
"systemtray-desktop"
]
polyglot = [
"polyglot-core",
"polyglot-graaljs",
rhino = [
"rhino-runtime",
"rhino-engine",
]
settings = [
"settings-core",

Binary file not shown.

View File

@@ -1,7 +1,5 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-8.13-bin.zip
networkTimeout=10000
validateDistributionUrl=true
distributionUrl=https\://services.gradle.org/distributions/gradle-8.2-bin.zip
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists

37
gradlew vendored
View File

@@ -15,8 +15,6 @@
# See the License for the specific language governing permissions and
# limitations under the License.
#
# SPDX-License-Identifier: Apache-2.0
#
##############################################################################
#
@@ -57,7 +55,7 @@
# Darwin, MinGW, and NonStop.
#
# (3) This script is generated from the Groovy template
# https://github.com/gradle/gradle/blob/HEAD/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
# https://github.com/gradle/gradle/blob/master/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
# within the Gradle project.
#
# You can find Gradle at https://github.com/gradle/gradle/.
@@ -82,11 +80,13 @@ do
esac
done
# This is normally unused
# shellcheck disable=SC2034
APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit
APP_NAME="Gradle"
APP_BASE_NAME=${0##*/}
# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036)
APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s\n' "$PWD" ) || exit
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
# Use the maximum available, or set MAX_FD != -1 to use that value.
MAX_FD=maximum
@@ -133,29 +133,22 @@ location of your Java installation."
fi
else
JAVACMD=java
if ! command -v java >/dev/null 2>&1
then
die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi
fi
# Increase the maximum file descriptors if we can.
if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then
case $MAX_FD in #(
max*)
# In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked.
# shellcheck disable=SC2039,SC3045
MAX_FD=$( ulimit -H -n ) ||
warn "Could not query maximum file descriptor limit"
esac
case $MAX_FD in #(
'' | soft) :;; #(
*)
# In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked.
# shellcheck disable=SC2039,SC3045
ulimit -n "$MAX_FD" ||
warn "Could not set maximum file descriptor limit to $MAX_FD"
esac
@@ -200,15 +193,11 @@ if "$cygwin" || "$msys" ; then
done
fi
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
# Collect all arguments for the java command:
# * DEFAULT_JVM_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments,
# and any embedded shellness will be escaped.
# * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be
# treated as '${Hostname}' itself on the command line.
# Collect all arguments for the java command;
# * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of
# shell script including quotes and variable substitutions, so put them in
# double quotes to make sure that they get re-expanded; and
# * put everything else in single quotes, so that it's not re-expanded.
set -- \
"-Dorg.gradle.appname=$APP_BASE_NAME" \

23
gradlew.bat vendored
View File

@@ -13,8 +13,6 @@
@rem See the License for the specific language governing permissions and
@rem limitations under the License.
@rem
@rem SPDX-License-Identifier: Apache-2.0
@rem
@if "%DEBUG%"=="" @echo off
@rem ##########################################################################
@@ -28,7 +26,6 @@ if "%OS%"=="Windows_NT" setlocal
set DIRNAME=%~dp0
if "%DIRNAME%"=="" set DIRNAME=.
@rem This is normally unused
set APP_BASE_NAME=%~n0
set APP_HOME=%DIRNAME%
@@ -45,11 +42,11 @@ set JAVA_EXE=java.exe
%JAVA_EXE% -version >NUL 2>&1
if %ERRORLEVEL% equ 0 goto execute
echo. 1>&2
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2
echo. 1>&2
echo Please set the JAVA_HOME variable in your environment to match the 1>&2
echo location of your Java installation. 1>&2
echo.
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
echo.
echo Please set the JAVA_HOME variable in your environment to match the
echo location of your Java installation.
goto fail
@@ -59,11 +56,11 @@ set JAVA_EXE=%JAVA_HOME%/bin/java.exe
if exist "%JAVA_EXE%" goto execute
echo. 1>&2
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2
echo. 1>&2
echo Please set the JAVA_HOME variable in your environment to match the 1>&2
echo location of your Java installation. 1>&2
echo.
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
echo.
echo Please set the JAVA_HOME variable in your environment to match the
echo location of your Java installation.
goto fail

View File

@@ -2,20 +2,5 @@
"$schema": "https://docs.renovatebot.com/renovate-schema.json",
"extends": [
"config:recommended"
],
"semanticCommits": "disabled",
"customManagers": [
{
"customType": "regex",
"fileMatch": [
"scripts/bundler.sh"
],
"matchStrings": [
"JRE_RELEASE=[\"'](?<currentValue>.+?)[\"']\\s+"
],
"datasourceTemplate": "github-releases",
"depNameTemplate": "adoptium/temurin21-binaries",
"versioningTemplate": "regex:^jdk-?(?<major>\\d+).(?<minor>\\d+).+?(?<patch>[\\d+]+)$"
}
]
}

View File

@@ -28,7 +28,7 @@ main() {
OS="$1"
JAR="$(ls server/build/*.jar | tail -n1)"
RELEASE_NAME="$(echo "${JAR%.*}" | xargs basename)-$OS"
RELEASE_VERSION=$(echo "$JAR" | grep -oP "v\K[0-9]+\.[0-9]+\.[0-9]+")
RELEASE_VERSION="$(tmp="${JAR%-*}"; echo "${tmp##*-}" | tr -d v)"
#RELEASE_REVISION_NUMBER="$(tmp="${JAR%.*}" && echo "${tmp##*-}" | tr -d r)"
local electron_version="v28.1.3"
@@ -51,64 +51,74 @@ main() {
move_release_to_output_dir
;;
linux-x64)
# https://github.com/adoptium/temurin21-binaries/releases/
JRE_RELEASE="jdk-21.0.6+7"
JRE="OpenJDK21U-jre_x64_linux_hotspot_$(echo "$JRE_RELEASE" | sed 's/jdk//;s/-//g;s/+/_/g').tar.gz"
# https://github.com/adoptium/temurin8-binaries/releases/
JRE_RELEASE="jdk8u392-b08"
JRE="OpenJDK8U-jre_x64_linux_hotspot_$(echo "$JRE_RELEASE" | sed 's/jdk//;s/-//g').tar.gz"
JRE_DIR="$JRE_RELEASE-jre"
JRE_URL="https://github.com/adoptium/temurin21-binaries/releases/download/$JRE_RELEASE/$JRE"
JRE_URL="https://github.com/adoptium/temurin8-binaries/releases/download/$JRE_RELEASE/$JRE"
ELECTRON="electron-$electron_version-linux-x64.zip"
ELECTRON_URL="https://github.com/electron/electron/releases/download/$electron_version/$ELECTRON"
download_electron
setup_jre
tree "$RELEASE_NAME"
download_jre_and_electron
RELEASE="$RELEASE_NAME.tar.gz"
make_linux_bundle
move_release_to_output_dir
;;
macOS-x64)
# https://github.com/adoptium/temurin21-binaries/releases/
JRE_RELEASE="jdk-21.0.6+7"
JRE="OpenJDK21U-jre_x64_mac_hotspot_$(echo "$JRE_RELEASE" | sed 's/jdk//;s/-//g;s/+/_/g').tar.gz"
# https://github.com/adoptium/temurin8-binaries/releases/
JRE_RELEASE="jdk8u392-b08"
JRE="OpenJDK8U-jre_x64_mac_hotspot_$(echo "$JRE_RELEASE" | sed 's/jdk//;s/-//g').tar.gz"
JRE_DIR="$JRE_RELEASE-jre"
JRE_URL="https://github.com/adoptium/temurin21-binaries/releases/download/$JRE_RELEASE/$JRE"
JRE_URL="https://github.com/adoptium/temurin8-binaries/releases/download/$JRE_RELEASE/$JRE"
ELECTRON="electron-$electron_version-darwin-x64.zip"
ELECTRON_URL="https://github.com/electron/electron/releases/download/$electron_version/$ELECTRON"
download_electron
setup_jre
tree "$RELEASE_NAME"
download_jre_and_electron
RELEASE="$RELEASE_NAME.tar.gz"
RELEASE="$RELEASE_NAME.zip"
make_macos_bundle
move_release_to_output_dir
;;
macOS-arm64)
# https://github.com/adoptium/temurin21-binaries/releases/
JRE_RELEASE="jdk-21.0.6+7"
JRE="OpenJDK21U-jre_aarch64_mac_hotspot_$(echo "$JRE_RELEASE" | sed 's/jdk//;s/-//g;s/+/_/g').tar.gz"
JRE_DIR="$JRE_RELEASE-jre"
JRE_URL="https://github.com/adoptium/temurin21-binaries/releases/download/$JRE_RELEASE/$JRE"
# https://cdn.azul.com/zulu/bin/
JRE="zulu8.74.0.17-ca-jre8.0.392-macosx_aarch64.tar.gz"
JRE_RELEASE="zulu8.74.0.17-ca-jre8.0.392-macosx_aarch64"
JRE_DIR="$JRE_RELEASE/zulu-8.jre"
JRE_URL="https://cdn.azul.com/zulu/bin/$JRE"
ELECTRON="electron-$electron_version-darwin-arm64.zip"
ELECTRON_URL="https://github.com/electron/electron/releases/download/$electron_version/$ELECTRON"
download_electron
setup_jre
tree "$RELEASE_NAME"
download_jre_and_electron
RELEASE="$RELEASE_NAME.tar.gz"
RELEASE="$RELEASE_NAME.zip"
make_macos_bundle
move_release_to_output_dir
;;
windows-x64)
# https://github.com/adoptium/temurin21-binaries/releases/
JRE_RELEASE="jdk-21.0.6+7"
JRE="OpenJDK21U-jre_x64_windows_hotspot_$(echo "$JRE_RELEASE" | sed 's/jdk//;s/-//g;s/+/_/g').zip"
windows-x86)
# https://github.com/adoptium/temurin8-binaries/releases/
JRE_RELEASE="jdk8u392-b08"
JRE="OpenJDK8U-jre_x86-32_windows_hotspot_$(echo "$JRE_RELEASE" | sed 's/jdk//;s/-//g').zip"
JRE_DIR="$JRE_RELEASE-jre"
JRE_URL="https://github.com/adoptium/temurin21-binaries/releases/download/$JRE_RELEASE/$JRE"
JRE_URL="https://github.com/adoptium/temurin8-binaries/releases/download/$JRE_RELEASE/$JRE"
ELECTRON="electron-$electron_version-win32-ia32.zip"
ELECTRON_URL="https://github.com/electron/electron/releases/download/$electron_version/$ELECTRON"
download_jre_and_electron
RELEASE="$RELEASE_NAME.zip"
make_windows_bundle
move_release_to_output_dir
RELEASE="$RELEASE_NAME.msi"
make_windows_package
move_release_to_output_dir
;;
windows-x64)
# https://github.com/adoptium/temurin8-binaries/releases/
JRE_RELEASE="jdk8u392-b08"
JRE="OpenJDK8U-jre_x64_windows_hotspot_$(echo "$JRE_RELEASE" | sed 's/jdk//;s/-//g').zip"
JRE_DIR="$JRE_RELEASE-jre"
JRE_URL="https://github.com/adoptium/temurin8-binaries/releases/download/$JRE_RELEASE/$JRE"
ELECTRON="electron-$electron_version-win32-x64.zip"
ELECTRON_URL="https://github.com/electron/electron/releases/download/$electron_version/$ELECTRON"
download_electron
setup_jre
tree "$RELEASE_NAME"
download_jre_and_electron
RELEASE="$RELEASE_NAME.zip"
make_windows_bundle
@@ -138,32 +148,26 @@ download_launcher() {
mv "Suwayomi-Launcher.jar" "$RELEASE_NAME/Suwayomi-Launcher.jar"
}
download_electron() {
download_jre_and_electron() {
if [ ! -f "$JRE" ]; then
curl -L "$JRE_URL" -o "$JRE"
fi
if [ ! -f "$ELECTRON" ]; then
curl -L "$ELECTRON_URL" -o "$ELECTRON"
fi
unzip "$ELECTRON" -d "$RELEASE_NAME/electron/"
}
setup_jre() {
if [ -d "jre" ]; then
chmod +x ./jre/bin/java
chmod +x ./jre/lib/jspawnhelper
mv "jre" "$RELEASE_NAME/jre"
local ext="${JRE##*.}"
if [ "$ext" = "zip" ]; then
unzip "$JRE"
else
if [ ! -f "$JRE" ]; then
curl -L "$JRE_URL" -o "$JRE"
fi
local ext="${JRE##*.}"
if [ "$ext" = "zip" ]; then
unzip "$JRE"
else
tar xvf "$JRE"
fi
mv "$JRE_DIR" "$RELEASE_NAME/jre"
tar xvf "$JRE"
fi
mv "$JRE_DIR" "$RELEASE_NAME/jre"
unzip "$ELECTRON" -d "$RELEASE_NAME/electron/"
mkdir "$RELEASE_NAME/bin"
tree
}
copy_linux_package_assets_to() {
@@ -180,7 +184,6 @@ copy_linux_package_assets_to() {
}
make_linux_bundle() {
mkdir "$RELEASE_NAME/bin"
cp "$JAR" "$RELEASE_NAME/bin/Suwayomi-Server.jar"
cp "scripts/resources/suwayomi-launcher.sh" "$RELEASE_NAME/"
cp "scripts/resources/suwayomi-server.sh" "$RELEASE_NAME/"
@@ -189,11 +192,10 @@ make_linux_bundle() {
}
make_macos_bundle() {
mkdir "$RELEASE_NAME/bin"
cp "$JAR" "$RELEASE_NAME/bin/Suwayomi-Server.jar"
cp "scripts/resources/Suwayomi Launcher.command" "$RELEASE_NAME/"
tar -I "gzip -9" -cvf "$RELEASE" "$RELEASE_NAME/"
zip -9 -r "$RELEASE" "$RELEASE_NAME/"
}
# https://wiki.debian.org/SimplePackagingTutorial
@@ -253,7 +255,6 @@ make_windows_bundle() {
#WINEARCH=win32 wine "$rcedit" "$RELEASE_NAME/electron/electron.exe" \
# --set-icon "$icon"
mkdir "$RELEASE_NAME/bin"
cp "$JAR" "$RELEASE_NAME/bin/Suwayomi-Server.jar"
cp "scripts/resources/Suwayomi Launcher.bat" "$RELEASE_NAME"

View File

@@ -1,3 +1,3 @@
cd "`dirname "$0"`"
./jre/bin/java -jar Suwayomi-Launcher.jar
./jre/Contents/Home/bin/java -jar Suwayomi-Launcher.jar

View File

@@ -8,7 +8,7 @@ Homepage: https://github.com/Suwayomi/Suwayomi-Server
Package: suwayomi-server
Architecture: all
Depends: ${misc:Depends}, openjdk-21-jre, libc++-dev
Depends: ${misc:Depends}, java8-runtime, libc++-dev
Description: Manga Reader
A free and open source manga reader server that runs extensions built for Tachiyomi.
Suwayomi is an independent Tachiyomi compatible software and is not a Fork of Tachiyomi.

View File

@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<Wix xmlns="http://schemas.microsoft.com/wix/2006/wi">
<Product Id="*" UpgradeCode="174c8f36-0bec-4585-9ddd-469c3d889dc1"
<Product Id="*" UpgradeCode="*"
Version="$(var.ProductVersion)" Language="1033" Name="Suwayomi Server" Manufacturer="Suwayomi">
<Package InstallerVersion="300" Compressed="yes" />
<Media Id="1" Cabinet="Suwayomi_Server.cab" EmbedCab="yes" />
@@ -9,8 +9,6 @@
VersionNT64
</Condition>
<MajorUpgrade DowngradeErrorMessage="A newer version of [ProductName] is already installed." />
<!-- Directory -->
<Directory Id="TARGETDIR" Name="SourceDir">
<Directory Id="ProgramFiles64Folder">
@@ -50,10 +48,6 @@
</Component>
</DirectoryRef>
<InstallExecuteSequence>
<RemoveExistingProducts After="InstallValidate" />
</InstallExecuteSequence>
<!-- Feature -->
<Feature Id="Suwayomi_Server" Title="Suwayomi-Server" Level="1">
<ComponentGroupRef Id="jre" />

View File

@@ -0,0 +1,60 @@
<?xml version="1.0" encoding="utf-8"?>
<Wix xmlns="http://schemas.microsoft.com/wix/2006/wi">
<Product Id="*" UpgradeCode="*"
Version="$(var.ProductVersion)" Language="1033" Name="Suwayomi Server" Manufacturer="Suwayomi">
<Package InstallerVersion="300" Compressed="yes" />
<Media Id="1" Cabinet="Suwayomi_Server.cab" EmbedCab="yes" />
<!-- Directory -->
<Directory Id="TARGETDIR" Name="SourceDir">
<Directory Id="ProgramFilesFolder">
<Directory Id="INSTALLDIR" Name="Suwayomi-Server" >
<Directory Id="jre"/>
<Directory Id="electron"/>
<Directory Id="bin"/>
</Directory>
</Directory>
<Directory Id="ProgramMenuFolder">
<Directory Id="ProgramMenuDir" Name="Suwayomi-Server">
<Component Id="ProgramMenuDir" Guid="*">
<RemoveFolder Id="ProgramMenuDir" On="uninstall"/>
<RegistryValue Root="HKCU" Key="Software\[Manufacturer]\[ProductName]" Type="string" Value="" KeyPath="yes"/>
</Component>
</Directory>
</Directory>
<Directory Id="DesktopFolder" />
</Directory>
<!-- Component -->
<DirectoryRef Id="INSTALLDIR">
<Component Id="SuwayomiJAR" Guid="*">
<File Id="Suwayomi-Launcher.jar" Source="$(var.SourceDir)/Suwayomi-Launcher.jar" KeyPath="yes" />
</Component>
<Component Id="SuwayomiLauncherBAT" Guid="*" Win64="yes">
<File Id="SuwayomiLauncher.bat" Source="$(var.SourceDir)/Suwayomi Launcher.bat" KeyPath="yes" >
<Shortcut Id="SuwayomiLauncher.lnk" Name="Suwayomi Launcher" Directory="INSTALLDIR"
WorkingDirectory="INSTALLDIR" Icon="Suwayomi.ico" IconIndex="0" Advertise="yes" />
<Shortcut Id="DesktopSuwayomiLauncher.lnk" Name="Suwayomi Launcher" Directory="DesktopFolder"
WorkingDirectory="INSTALLDIR" Icon="Suwayomi.ico" IconIndex="0" Advertise="yes" />
<Shortcut Id="ProgramMenuSuwayomiLauncher.lnk" Name="Suwayomi Launcher" Directory="ProgramMenuDir"
WorkingDirectory="INSTALLDIR" Icon="Suwayomi.ico" IconIndex="0" Advertise="yes"
Description="A free and open source manga reader that runs extensions built for Tachiyomi." />
</File>
</Component>
</DirectoryRef>
<!-- Feature -->
<Feature Id="Suwayomi_Server" Title="Suwayomi-Server" Level="1">
<ComponentGroupRef Id="jre" />
<ComponentGroupRef Id="bin" />
<ComponentRef Id="SuwayomiJAR" />
<ComponentRef Id="SuwayomiLauncherBAT" />
<ComponentRef Id="ProgramMenuDir" />
<ComponentGroupRef Id="electron" />
</Feature>
<Icon Id="Suwayomi.ico" SourceFile="$(var.Icon)" />
<Property Id="ARPPRODUCTICON" Value="Suwayomi.ico" /> <!-- Icon in Add/Remove Programs -->
</Product>
</Wix>

View File

@@ -3,28 +3,12 @@ import org.jetbrains.kotlin.gradle.tasks.KotlinJvmCompile
import java.time.Instant
plugins {
id(
libs.plugins.kotlin.jvm
.get()
.pluginId,
)
id(
libs.plugins.kotlin.serialization
.get()
.pluginId,
)
id(
libs.plugins.ktlint
.get()
.pluginId,
)
id(libs.plugins.kotlin.jvm.get().pluginId)
id(libs.plugins.kotlin.serialization.get().pluginId)
id(libs.plugins.ktlint.get().pluginId)
application
alias(libs.plugins.shadowjar)
id(
libs.plugins.buildconfig
.get()
.pluginId,
)
id(libs.plugins.buildconfig.get().pluginId)
}
dependencies {
@@ -43,8 +27,7 @@ dependencies {
// GraphQL
implementation(libs.graphql.kotlin.server)
implementation(libs.graphql.kotlin.scheme)
implementation(libs.graphql.java.core)
implementation(libs.graphql.java.scalars)
implementation(libs.graphql.scalars)
// Exposed ORM
implementation(libs.bundles.exposed)
@@ -73,7 +56,6 @@ dependencies {
implementation(libs.asm)
// Disk & File
implementation(libs.cache4k)
implementation(libs.zip4j)
implementation(libs.commonscompress)
implementation(libs.junrar)
@@ -121,7 +103,7 @@ buildConfig {
fun quoteWrap(obj: Any): String = """"$obj""""
buildConfigField("String", "NAME", quoteWrap(rootProject.name))
buildConfigField("String", "VERSION", quoteWrap(getTachideskVersion()))
buildConfigField("String", "VERSION", quoteWrap(tachideskVersion))
buildConfigField("String", "REVISION", quoteWrap(getTachideskRevision()))
buildConfigField("String", "BUILD_TYPE", quoteWrap(if (System.getenv("ProductBuildType") == "Stable") "Stable" else "Preview"))
buildConfigField("long", "BUILD_TIME", Instant.now().epochSecond.toString())
@@ -140,15 +122,14 @@ tasks {
"Main-Class" to MainClass,
"Implementation-Title" to rootProject.name,
"Implementation-Vendor" to "The Suwayomi Project",
"Specification-Version" to getTachideskVersion(),
"Specification-Version" to tachideskVersion,
"Implementation-Version" to getTachideskRevision(),
)
}
archiveBaseName.set(rootProject.name)
archiveVersion.set(getTachideskVersion())
archiveClassifier.set("")
archiveVersion.set(tachideskVersion)
archiveClassifier.set(getTachideskRevision())
destinationDirectory.set(File("$rootDir/server/build"))
mergeServiceFiles()
}
test {
@@ -160,10 +141,11 @@ tasks {
}
withType<KotlinJvmCompile> {
compilerOptions {
freeCompilerArgs.add(
"-opt-in=kotlinx.serialization.ExperimentalSerializationApi",
)
kotlinOptions {
freeCompilerArgs +=
listOf(
"-opt-in=kotlinx.serialization.ExperimentalSerializationApi",
)
}
}

View File

@@ -9,10 +9,16 @@ package eu.kanade.tachiyomi
import android.app.Application
import android.content.Context
import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.InjektScope
import uy.kohesive.injekt.registry.default.DefaultRegistrar
open class App : Application() {
override fun onCreate() {
super.onCreate()
Injekt = InjektScope(DefaultRegistrar())
Injekt.importModule(AppModule(this))
// if (BuildConfig.DEBUG) Timber.plant(Timber.DebugTree())
}

View File

@@ -15,12 +15,7 @@ object AppInfo {
*
* @since extension-lib 1.3
*/
fun getVersionCode() =
BuildConfig.VERSION
.replace("v", "")
.split('.')
.joinToString("")
.toInt()
fun getVersionCode() = BuildConfig.REVISION.substring(1).toInt()
/**
* should be something like "0.13.1"

View File

@@ -20,15 +20,21 @@ import eu.kanade.tachiyomi.network.JavaScriptEngine
import eu.kanade.tachiyomi.network.NetworkHelper
import kotlinx.serialization.json.Json
import kotlinx.serialization.protobuf.ProtoBuf
import nl.adaptivity.xmlutil.XmlDeclMode
import nl.adaptivity.xmlutil.core.XmlVersion
import nl.adaptivity.xmlutil.serialization.XML
import org.koin.core.module.Module
import org.koin.dsl.module
import org.kodein.di.DI
import org.kodein.di.conf.global
import org.kodein.di.instance
import rx.Observable
import rx.schedulers.Schedulers
import uy.kohesive.injekt.api.InjektModule
import uy.kohesive.injekt.api.InjektRegistrar
import uy.kohesive.injekt.api.addSingleton
import uy.kohesive.injekt.api.addSingletonFactory
import uy.kohesive.injekt.api.get
fun createAppModule(app: Application): Module {
return module {
single { app }
class AppModule(val app: Application) : InjektModule {
override fun InjektRegistrar.registerInjectables() {
addSingleton(app)
// addSingletonFactory { PreferencesHelper(app) }
//
@@ -38,9 +44,9 @@ fun createAppModule(app: Application): Module {
//
// addSingletonFactory { CoverCache(app) }
single { NetworkHelper(app) }
addSingletonFactory { NetworkHelper(app) }
single { JavaScriptEngine(app) }
addSingletonFactory { JavaScriptEngine(app) }
// addSingletonFactory { SourceManager(app).also { get<ExtensionManager>().init(it) } }
//
@@ -52,38 +58,36 @@ fun createAppModule(app: Application): Module {
//
// addSingletonFactory { LibrarySyncManager(app) }
single {
Json {
ignoreUnknownKeys = true
explicitNulls = false
}
addSingletonFactory {
val json by DI.global.instance<Json>()
json
}
single {
XML {
defaultPolicy {
ignoreUnknownChildren()
}
autoPolymorphic = true
xmlDeclMode = XmlDeclMode.Charset
indent = 2
xmlVersion = XmlVersion.XML10
}
addSingletonFactory {
val xml by DI.global.instance<XML>()
xml
}
single {
ProtoBuf
addSingletonFactory {
val protobuf by DI.global.instance<ProtoBuf>()
protobuf
}
}
// Asynchronously init expensive components for a faster cold start
// Asynchronously init expensive components for a faster cold start
// rxAsync { get<PreferencesHelper>() }
// rxAsync {
rxAsync { get<NetworkHelper>() }
rxAsync {
// get<SourceManager>()
// get<DownloadManager>()
// }
}
// rxAsync { get<DatabaseHelper>() }
}
private fun rxAsync(block: () -> Unit) {
Observable.fromCallable { block() }.subscribeOn(Schedulers.computation()).subscribe()
}
}

View File

@@ -49,9 +49,7 @@ class MemoryCookieJar : CookieJar {
}
}
class WrappedCookie private constructor(
val cookie: Cookie,
) {
class WrappedCookie private constructor(val cookie: Cookie) {
fun unwrap() = cookie
fun isExpired() = cookie.expiresAt < System.currentTimeMillis()

View File

@@ -12,13 +12,13 @@ import eu.kanade.tachiyomi.network.interceptor.CloudflareInterceptor
import eu.kanade.tachiyomi.network.interceptor.IgnoreGzipInterceptor
import eu.kanade.tachiyomi.network.interceptor.UncaughtExceptionInterceptor
import eu.kanade.tachiyomi.network.interceptor.UserAgentInterceptor
import io.github.oshai.kotlinlogging.KotlinLogging
import kotlinx.coroutines.DelicateCoroutinesApi
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.drop
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach
import mu.KotlinLogging
import okhttp3.Cache
import okhttp3.OkHttpClient
import okhttp3.brotli.BrotliInterceptor
@@ -30,9 +30,7 @@ import java.net.CookieManager
import java.net.CookiePolicy
import java.util.concurrent.TimeUnit
class NetworkHelper(
context: Context,
) {
class NetworkHelper(context: Context) {
// private val preferences: PreferencesHelper by injectLazy()
// private val cacheDir = File(context.cacheDir, "network_cache")
@@ -55,7 +53,9 @@ class NetworkHelper(
"AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36",
)
fun defaultUserAgentProvider(): String = userAgent.value
fun defaultUserAgentProvider(): String {
return userAgent.value
}
init {
@OptIn(DelicateCoroutinesApi::class)
@@ -63,14 +63,14 @@ class NetworkHelper(
.drop(1)
.onEach {
GetCatalogueSource.unregisterAllCatalogueSources() // need to reset the headers
}.launchIn(GlobalScope)
}
.launchIn(GlobalScope)
}
private val baseClientBuilder: OkHttpClient.Builder
get() {
val builder =
OkHttpClient
.Builder()
OkHttpClient.Builder()
.cookieJar(PersistentCookieJar(cookieStore))
.connectTimeout(30, TimeUnit.SECONDS)
.readTimeout(30, TimeUnit.SECONDS)
@@ -80,7 +80,8 @@ class NetworkHelper(
directory = File.createTempFile("tachidesk_network_cache", null),
maxSize = 5L * 1024 * 1024, // 5 MiB
),
).addInterceptor(UncaughtExceptionInterceptor())
)
.addInterceptor(UncaughtExceptionInterceptor())
.addInterceptor(UserAgentInterceptor(::defaultUserAgentProvider))
.addNetworkInterceptor(IgnoreGzipInterceptor())
.addNetworkInterceptor(BrotliInterceptor)

View File

@@ -1,5 +1,6 @@
package eu.kanade.tachiyomi.network
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.suspendCancellableCoroutine
import kotlinx.serialization.DeserializationStrategy
import kotlinx.serialization.ExperimentalSerializationApi
@@ -49,7 +50,9 @@ fun Call.asObservable(): Observable<Response> {
// call.cancel()
}
override fun isUnsubscribed(): Boolean = call.isCanceled()
override fun isUnsubscribed(): Boolean {
return call.isCanceled()
}
}
subscriber.add(requestArbiter)
@@ -57,16 +60,18 @@ fun Call.asObservable(): Observable<Response> {
}
}
fun Call.asObservableSuccess(): Observable<Response> =
asObservable()
fun Call.asObservableSuccess(): Observable<Response> {
return asObservable()
.doOnNext { response ->
if (!response.isSuccessful) {
response.close()
throw HttpException(response.code)
}
}
}
// Based on https://github.com/gildor/kotlin-coroutines-okhttp
@OptIn(ExperimentalCoroutinesApi::class)
private suspend fun Call.await(callStack: Array<StackTraceElement>): Response {
return suspendCancellableCoroutine { continuation ->
val callback =
@@ -75,9 +80,8 @@ private suspend fun Call.await(callStack: Array<StackTraceElement>): Response {
call: Call,
response: Response,
) {
continuation.resume(response) { _, resourceToClose, _ ->
continuation.resume(response) {
response.body.close()
resourceToClose.close()
}
}
@@ -131,28 +135,29 @@ fun OkHttpClient.newCachelessCallWithProgress(
.cache(null)
.addNetworkInterceptor { chain ->
val originalResponse = chain.proceed(chain.request())
originalResponse
.newBuilder()
originalResponse.newBuilder()
.body(ProgressResponseBody(originalResponse.body, listener))
.build()
}.build()
}
.build()
return progressClient.newCall(request)
}
context(Json)
inline fun <reified T> Response.parseAs(): T = decodeFromJsonResponse(serializer(), this)
inline fun <reified T> Response.parseAs(): T {
return decodeFromJsonResponse(serializer(), this)
}
context(Json)
@OptIn(ExperimentalSerializationApi::class)
fun <T> decodeFromJsonResponse(
deserializer: DeserializationStrategy<T>,
response: Response,
): T =
response.body.source().use {
): T {
return response.body.source().use {
decodeFromBufferedSource(deserializer, it)
}
}
class HttpException(
val code: Int,
) : IllegalStateException("HTTP error $code")
class HttpException(val code: Int) : IllegalStateException("HTTP error $code")

View File

@@ -5,9 +5,7 @@ import okhttp3.CookieJar
import okhttp3.HttpUrl
// from TachiWeb-Server
class PersistentCookieJar(
private val store: PersistentCookieStore,
) : CookieJar {
class PersistentCookieJar(private val store: PersistentCookieStore) : CookieJar {
override fun saveFromResponse(
url: HttpUrl,
cookies: List<Cookie>,
@@ -15,5 +13,7 @@ class PersistentCookieJar(
store.addAll(url, cookies)
}
override fun loadForRequest(url: HttpUrl): List<Cookie> = store.get(url)
override fun loadForRequest(url: HttpUrl): List<Cookie> {
return store.get(url)
}
}

View File

@@ -15,9 +15,7 @@ import kotlin.time.Duration.Companion.milliseconds
import kotlin.time.Duration.Companion.seconds
// from TachiWeb-Server
class PersistentCookieStore(
context: Context,
) : CookieStore {
class PersistentCookieStore(context: Context) : CookieStore {
private val cookieMap = ConcurrentHashMap<String, List<Cookie>>()
private val prefs = context.getSharedPreferences("cookie_store", Context.MODE_PRIVATE)
@@ -25,8 +23,7 @@ class PersistentCookieStore(
init {
val domains =
prefs.all.keys
.map { it.substringBeforeLast(".") }
prefs.all.keys.map { it.substringBeforeLast(".") }
.toSet()
domains.forEach { domain ->
val cookies = prefs.getStringSet(domain, emptySet())
@@ -34,8 +31,7 @@ class PersistentCookieStore(
try {
val url = "http://$domain".toHttpUrlOrNull() ?: return@forEach
val nonExpiredCookies =
cookies
.mapNotNull { Cookie.parse(url, it) }
cookies.mapNotNull { Cookie.parse(url, it) }
.filter { !it.hasExpired() }
cookieMap[domain] = nonExpiredCookies
} catch (e: Exception) {
@@ -67,13 +63,14 @@ class PersistentCookieStore(
}
}
override fun removeAll(): Boolean =
lock.withLock {
override fun removeAll(): Boolean {
return lock.withLock {
val wasNotEmpty = cookieMap.isEmpty()
prefs.edit().clear().apply()
cookieMap.clear()
wasNotEmpty
}
}
fun remove(uri: URI) {
val url = uri.toURL()
@@ -90,7 +87,9 @@ class PersistentCookieStore(
}
}
fun get(url: HttpUrl): List<Cookie> = get(url.host)
fun get(url: HttpUrl): List<Cookie> {
return get(url.host)
}
override fun add(
uri: URI?,
@@ -106,17 +105,19 @@ class PersistentCookieStore(
}
}
override fun getCookies(): List<HttpCookie> =
cookieMap.values.flatMap {
override fun getCookies(): List<HttpCookie> {
return cookieMap.values.flatMap {
it.map {
it.toHttpCookie()
}
}
}
override fun getURIs(): List<URI> =
cookieMap.keys().toList().map {
override fun getURIs(): List<URI> {
return cookieMap.keys().toList().map {
URI("http://$it")
}
}
override fun remove(
uri: URI?,
@@ -144,7 +145,9 @@ class PersistentCookieStore(
}
}
private fun get(url: String): List<Cookie> = cookieMap[url].orEmpty().filter { !it.hasExpired() }
private fun get(url: String): List<Cookie> {
return cookieMap[url].orEmpty().filter { !it.hasExpired() }
}
private fun saveToDisk(url: URL) {
// Get cookies to be stored in disk
@@ -162,8 +165,7 @@ class PersistentCookieStore(
private fun Cookie.hasExpired() = System.currentTimeMillis() >= expiresAt
private fun HttpCookie.toCookie(uri: URI) =
Cookie
.Builder()
Cookie.Builder()
.name(name)
.value(value)
.domain(uri.toURL().host)
@@ -174,19 +176,22 @@ class PersistentCookieStore(
} else {
it.expiresAt(Long.MAX_VALUE)
}
}.let {
}
.let {
if (secure) {
it.secure()
} else {
it
}
}.let {
}
.let {
if (isHttpOnly) {
it.httpOnly()
} else {
it
}
}.build()
}
.build()
private fun Cookie.toHttpCookie(): HttpCookie {
val it = this

View File

@@ -9,19 +9,22 @@ import okio.Source
import okio.buffer
import java.io.IOException
class ProgressResponseBody(
private val responseBody: ResponseBody,
private val progressListener: ProgressListener,
) : ResponseBody() {
class ProgressResponseBody(private val responseBody: ResponseBody, private val progressListener: ProgressListener) : ResponseBody() {
private val bufferedSource: BufferedSource by lazy {
source(responseBody.source()).buffer()
}
override fun contentType(): MediaType? = responseBody.contentType()
override fun contentType(): MediaType? {
return responseBody.contentType()
}
override fun contentLength(): Long = responseBody.contentLength()
override fun contentLength(): Long {
return responseBody.contentLength()
}
override fun source(): BufferedSource = bufferedSource
override fun source(): BufferedSource {
return bufferedSource
}
private fun source(source: Source): Source {
return object : ForwardingSource(source) {

View File

@@ -18,13 +18,13 @@ fun GET(
url: String,
headers: Headers = DEFAULT_HEADERS,
cache: CacheControl = DEFAULT_CACHE_CONTROL,
): Request =
Request
.Builder()
): Request {
return Request.Builder()
.url(url)
.headers(headers)
.cacheControl(cache)
.build()
}
/**
* @since extensions-lib 1.4
@@ -33,52 +33,52 @@ fun GET(
url: HttpUrl,
headers: Headers = DEFAULT_HEADERS,
cache: CacheControl = DEFAULT_CACHE_CONTROL,
): Request =
Request
.Builder()
): Request {
return Request.Builder()
.url(url)
.headers(headers)
.cacheControl(cache)
.build()
}
fun POST(
url: String,
headers: Headers = DEFAULT_HEADERS,
body: RequestBody = DEFAULT_BODY,
cache: CacheControl = DEFAULT_CACHE_CONTROL,
): Request =
Request
.Builder()
): Request {
return Request.Builder()
.url(url)
.post(body)
.headers(headers)
.cacheControl(cache)
.build()
}
fun PUT(
url: String,
headers: Headers = DEFAULT_HEADERS,
body: RequestBody = DEFAULT_BODY,
cache: CacheControl = DEFAULT_CACHE_CONTROL,
): Request =
Request
.Builder()
): Request {
return Request.Builder()
.url(url)
.put(body)
.headers(headers)
.cacheControl(cache)
.build()
}
fun DELETE(
url: String,
headers: Headers = DEFAULT_HEADERS,
body: RequestBody = DEFAULT_BODY,
cache: CacheControl = DEFAULT_CACHE_CONTROL,
): Request =
Request
.Builder()
): Request {
return Request.Builder()
.url(url)
.delete(body)
.headers(headers)
.cacheControl(cache)
.build()
}

View File

@@ -4,7 +4,6 @@ import eu.kanade.tachiyomi.network.NetworkHelper
import eu.kanade.tachiyomi.network.POST
import eu.kanade.tachiyomi.network.awaitSuccess
import eu.kanade.tachiyomi.network.parseAs
import io.github.oshai.kotlinlogging.KotlinLogging
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.map
@@ -16,6 +15,7 @@ import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable
import kotlinx.serialization.encodeToString
import kotlinx.serialization.json.Json
import mu.KotlinLogging
import okhttp3.Cookie
import okhttp3.HttpUrl
import okhttp3.Interceptor
@@ -117,12 +117,12 @@ object CFClearance {
serverConfig.flareSolverrTimeout
.map { timeoutInt ->
val timeout = timeoutInt.seconds
network.client
.newBuilder()
network.client.newBuilder()
.callTimeout(timeout.plus(10.seconds).toJavaDuration())
.readTimeout(timeout.plus(5.seconds).toJavaDuration())
.build()
}.stateIn(GlobalScope, SharingStarted.Eagerly, network.client)
}
.stateIn(GlobalScope, SharingStarted.Eagerly, network.client)
}
private val json: Json by injectLazy()
private val jsonMediaType = "application/json".toMediaType()
@@ -190,29 +190,26 @@ object CFClearance {
return with(json) {
mutex.withLock {
client.value
.newCall(
POST(
url = serverConfig.flareSolverrUrl.value.removeSuffix("/") + "/v1",
body =
Json
.encodeToString(
FlareSolverRequest(
"request.get",
originalRequest.url.toString(),
session = serverConfig.flareSolverrSessionName.value,
sessionTtlMinutes = serverConfig.flareSolverrSessionTtl.value,
cookies =
network.cookieStore.get(originalRequest.url).map {
FlareSolverCookie(it.name, it.value)
},
returnOnlyCookies = onlyCookies,
maxTimeout = timeout.inWholeMilliseconds.toInt(),
),
).toRequestBody(jsonMediaType),
),
).awaitSuccess()
.parseAs<FlareSolverResponse>()
client.value.newCall(
POST(
url = serverConfig.flareSolverrUrl.value.removeSuffix("/") + "/v1",
body =
Json.encodeToString(
FlareSolverRequest(
"request.get",
originalRequest.url.toString(),
session = serverConfig.flareSolverrSessionName.value,
sessionTtlMinutes = serverConfig.flareSolverrSessionTtl.value,
cookies =
network.cookieStore.get(originalRequest.url).map {
FlareSolverCookie(it.name, it.value)
},
returnOnlyCookies = onlyCookies,
maxTimeout = timeout.inWholeMilliseconds.toInt(),
),
).toRequestBody(jsonMediaType),
),
).awaitSuccess().parseAs<FlareSolverResponse>()
}
}
}
@@ -227,23 +224,22 @@ object CFClearance {
val cookies =
flareSolverResponse.solution.cookies
.map { cookie ->
Cookie
.Builder()
Cookie.Builder()
.name(cookie.name)
.value(cookie.value)
.domain(cookie.domain.removePrefix("."))
.expiresAt(cookie.expires?.takeUnless { it < 0.0 }?.toLong() ?: Long.MAX_VALUE)
.also {
if (cookie.httpOnly != null && cookie.httpOnly) it.httpOnly()
if (cookie.secure != null && cookie.secure) it.secure()
if (!cookie.path.isNullOrEmpty()) it.path(cookie.path)
// We need to convert the expires time to milliseconds for the persistent cookie store
if (cookie.expires != null && cookie.expires > 0) it.expiresAt((cookie.expires * 1000).toLong())
}.build()
}.groupBy { it.domain }
}
.build()
}
.groupBy { it.domain }
.flatMap { (domain, cookies) ->
network.cookieStore.addAll(
HttpUrl
.Builder()
HttpUrl.Builder()
.scheme("http")
.host(domain.removePrefix("."))
.build(),
@@ -258,8 +254,7 @@ object CFClearance {
"${it.name}=${it.value}"
}
logger.trace { "Final cookies\n$finalCookies" }
return originalRequest
.newBuilder()
return originalRequest.newBuilder()
.header("Cookie", finalCookies)
.header("User-Agent", flareSolverResponse.solution.userAgent)
.build()

View File

@@ -13,8 +13,8 @@ import java.io.IOException
* See https://square.github.io/okhttp/4.x/okhttp/okhttp3/-interceptor/
*/
class UncaughtExceptionInterceptor : Interceptor {
override fun intercept(chain: Interceptor.Chain): Response =
try {
override fun intercept(chain: Interceptor.Chain): Response {
return try {
chain.proceed(chain.request())
} catch (e: Exception) {
if (e is IOException) {
@@ -23,4 +23,5 @@ class UncaughtExceptionInterceptor : Interceptor {
throw IOException(e)
}
}
}
}

View File

@@ -3,9 +3,7 @@ package eu.kanade.tachiyomi.network.interceptor
import okhttp3.Interceptor
import okhttp3.Response
class UserAgentInterceptor(
private val userAgentProvider: () -> String,
) : Interceptor {
class UserAgentInterceptor(private val userAgentProvider: () -> String) : Interceptor {
override fun intercept(chain: Interceptor.Chain): Response {
val originalRequest = chain.request()

View File

@@ -23,7 +23,9 @@ interface CatalogueSource : Source {
* @param page the page number to retrieve.
*/
@Suppress("DEPRECATION")
suspend fun getPopularManga(page: Int): MangasPage = fetchPopularManga(page).awaitSingle()
suspend fun getPopularManga(page: Int): MangasPage {
return fetchPopularManga(page).awaitSingle()
}
/**
* Get a page with a list of manga.
@@ -38,7 +40,9 @@ interface CatalogueSource : Source {
page: Int,
query: String,
filters: FilterList,
): MangasPage = fetchSearchManga(page, query, filters).awaitSingle()
): MangasPage {
return fetchSearchManga(page, query, filters).awaitSingle()
}
/**
* Get a page with a list of latest manga updates.
@@ -47,7 +51,9 @@ interface CatalogueSource : Source {
* @param page the page number to retrieve.
*/
@Suppress("DEPRECATION")
suspend fun getLatestUpdates(page: Int): MangasPage = fetchLatestUpdates(page).awaitSingle()
suspend fun getLatestUpdates(page: Int): MangasPage {
return fetchLatestUpdates(page).awaitSingle()
}
/**
* Returns the list of filters for the source.

View File

@@ -31,7 +31,9 @@ interface Source {
* @return the updated manga.
*/
@Suppress("DEPRECATION")
suspend fun getMangaDetails(manga: SManga): SManga = fetchMangaDetails(manga).awaitSingle()
suspend fun getMangaDetails(manga: SManga): SManga {
return fetchMangaDetails(manga).awaitSingle()
}
/**
* Get all the available chapters for a manga.
@@ -41,7 +43,9 @@ interface Source {
* @return the chapters for the manga.
*/
@Suppress("DEPRECATION")
suspend fun getChapterList(manga: SManga): List<SChapter> = fetchChapterList(manga).awaitSingle()
suspend fun getChapterList(manga: SManga): List<SChapter> {
return fetchChapterList(manga).awaitSingle()
}
/**
* Get the list of pages a chapter has. Pages should be returned
@@ -52,7 +56,9 @@ interface Source {
* @return the pages for the chapter.
*/
@Suppress("DEPRECATION")
suspend fun getPageList(chapter: SChapter): List<Page> = fetchPageList(chapter).awaitSingle()
suspend fun getPageList(chapter: SChapter): List<Page> {
return fetchPageList(chapter).awaitSingle()
}
@Deprecated(
"Use the non-RxJava API instead",

View File

@@ -26,20 +26,23 @@ import eu.kanade.tachiyomi.source.model.SManga
import eu.kanade.tachiyomi.util.chapter.ChapterRecognition
import eu.kanade.tachiyomi.util.lang.compareToCaseInsensitiveNaturalOrder
import eu.kanade.tachiyomi.util.storage.EpubFile
import io.github.oshai.kotlinlogging.KotlinLogging
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.runBlocking
import kotlinx.coroutines.withContext
import kotlinx.serialization.json.Json
import kotlinx.serialization.json.decodeFromStream
import mu.KotlinLogging
import nl.adaptivity.xmlutil.ExperimentalXmlUtilApi
import nl.adaptivity.xmlutil.core.KtXmlReader
import nl.adaptivity.xmlutil.serialization.XML
import org.apache.commons.compress.archivers.zip.ZipFile
import org.jetbrains.exposed.sql.insert
import org.jetbrains.exposed.sql.insertAndGetId
import org.jetbrains.exposed.sql.selectAll
import org.jetbrains.exposed.sql.select
import org.jetbrains.exposed.sql.transactions.transaction
import org.kodein.di.DI
import org.kodein.di.conf.global
import org.kodein.di.instance
import suwayomi.tachidesk.manga.impl.util.source.GetCatalogueSource.registerCatalogueSource
import suwayomi.tachidesk.manga.impl.util.storage.ImageUtil
import suwayomi.tachidesk.manga.model.table.ExtensionTable
@@ -56,8 +59,7 @@ import com.github.junrar.Archive as JunrarArchive
class LocalSource(
private val fileSystem: LocalSourceFileSystem,
private val coverManager: LocalCoverManager,
) : CatalogueSource,
UnmeteredSource {
) : CatalogueSource, UnmeteredSource {
private val json: Json by injectLazy()
private val xml: XML by injectLazy()
@@ -91,8 +93,7 @@ class LocalSource(
// Filter out files that are hidden and is not a folder
.filter { it.isDirectory && !it.name.startsWith('.') }
.distinctBy { it.name }
.filter {
// Filter by query or last modified
.filter { // Filter by query or last modified
if (lastModifiedLimit == 0L) {
it.name.contains(query, ignoreCase = true)
} else {
@@ -133,8 +134,7 @@ class LocalSource(
url = mangaDir.name
// Try to find the cover
coverManager
.find(mangaDir.name)
coverManager.find(mangaDir.name)
?.takeIf(File::exists)
?.let { thumbnail_url = it.absolutePath }
}
@@ -238,7 +238,7 @@ class LocalSource(
for (chapter in chapterArchives) {
when (Format.valueOf(chapter)) {
is Format.Zip -> {
ZipFile.builder().setFile(chapter).get().use { zip: ZipFile ->
ZipFile(chapter).use { zip: ZipFile ->
zip.getEntry(COMIC_INFO_FILE)?.let { comicInfoFile ->
zip.getInputStream(comicInfoFile).buffered().use { stream ->
return copyComicInfoFile(stream, folderPath)
@@ -264,12 +264,13 @@ class LocalSource(
private fun copyComicInfoFile(
comicInfoFileStream: InputStream,
folderPath: String?,
): File =
File("$folderPath/$COMIC_INFO_FILE").apply {
): File {
return File("$folderPath/$COMIC_INFO_FILE").apply {
outputStream().use { outputStream ->
comicInfoFileStream.use { it.copyTo(outputStream) }
}
}
}
@OptIn(ExperimentalXmlUtilApi::class)
private fun setMangaDetailsFromComicInfoFile(
@@ -285,9 +286,8 @@ class LocalSource(
}
// Chapters
override suspend fun getChapterList(manga: SManga): List<SChapter> =
fileSystem
.getFilesInMangaDirectory(manga.url)
override suspend fun getChapterList(manga: SManga): List<SChapter> {
return fileSystem.getFilesInMangaDirectory(manga.url)
// Only keep supported formats
.filter { it.isDirectory || Archive.isSupported(it) }
.map { chapterFile ->
@@ -312,21 +312,22 @@ class LocalSource(
}
}
}
}.sortedWith { c1, c2 ->
}
.sortedWith { c1, c2 ->
val c = c2.chapter_number.compareTo(c1.chapter_number)
if (c == 0) c2.name.compareToCaseInsensitiveNaturalOrder(c1.name) else c
}.toList()
}
.toList()
}
// Filters
override fun getFilterList() = FilterList(OrderBy.Popular())
// TODO Fix Memory Leak
override suspend fun getPageList(chapter: SChapter): List<Page> =
when (val format = getFormat(chapter)) {
override suspend fun getPageList(chapter: SChapter): List<Page> {
return when (val format = getFormat(chapter)) {
is Format.Directory -> {
format.file
.listFiles()
.orEmpty()
format.file.listFiles().orEmpty()
.filter { !it.isDirectory && ImageUtil.isImage(it.name, it::inputStream) }
.sortedWith { f1, f2 -> f1.name.compareToCaseInsensitiveNaturalOrder(f2.name) }
.mapIndexed { index, page ->
@@ -358,11 +359,11 @@ class LocalSource(
pages
}
}
}
fun getFormat(chapter: SChapter): Format {
try {
return fileSystem
.getBaseDirectories()
return fileSystem.getBaseDirectories()
.map { dir -> File(dir, chapter.url) }
.find { it.exists() }
?.let(Format.Companion::valueOf)
@@ -377,23 +378,21 @@ class LocalSource(
private fun updateCover(
chapter: SChapter,
manga: SManga,
): File? =
try {
): File? {
return try {
when (val format = getFormat(chapter)) {
is Format.Directory -> {
val entry =
format.file
.listFiles()
format.file.listFiles()
?.sortedWith { f1, f2 -> f1.name.compareToCaseInsensitiveNaturalOrder(f2.name) }
?.find { !it.isDirectory && ImageUtil.isImage(it.name) { FileInputStream(it) } }
entry?.let { coverManager.update(manga, it.inputStream()) }
}
is Format.Zip -> {
ZipFile.builder().setFile(format.file).get().use { zip ->
ZipFile(format.file).use { zip ->
val entry =
zip.entries
.toList()
zip.entries.toList()
.sortedWith { f1, f2 -> f1.name.compareToCaseInsensitiveNaturalOrder(f2.name) }
.find { !it.isDirectory && ImageUtil.isImage(it.name) { zip.getInputStream(it) } }
@@ -413,8 +412,7 @@ class LocalSource(
is Format.Epub -> {
EpubFile(format.file).use { epub ->
val entry =
epub
.getImagesFromPages()
epub.getImagesFromPages()
.firstOrNull()
?.let { epub.getEntry(it) }
@@ -426,6 +424,7 @@ class LocalSource(
logger.error(e) { "Error updating cover for ${manga.title}" }
null
}
}
companion object {
const val ID = 0L
@@ -438,13 +437,13 @@ class LocalSource(
private val logger = KotlinLogging.logger {}
private val applicationDirs: ApplicationDirs by injectLazy()
private val applicationDirs by DI.global.instance<ApplicationDirs>()
val pageCache: MutableMap<String, List<() -> InputStream>> = mutableMapOf()
fun register() {
transaction {
val sourceRecord = SourceTable.selectAll().where { SourceTable.id eq ID }.firstOrNull()
val sourceRecord = SourceTable.select { SourceTable.id eq ID }.firstOrNull()
if (sourceRecord == null) {
// must do this to avoid database integrity errors

View File

@@ -2,14 +2,12 @@ package eu.kanade.tachiyomi.source.local.filter
import eu.kanade.tachiyomi.source.model.Filter
sealed class OrderBy(
selection: Selection,
) : Filter.Sort(
"Order by",
arrayOf("Title", "Date"),
selection,
) {
class Popular : OrderBy(Selection(0, true))
sealed class OrderBy(selection: Selection) : Filter.Sort(
"Order by",
arrayOf("Title", "Date"),
selection,
) {
class Popular() : OrderBy(Selection(0, true))
class Latest : OrderBy(Selection(1, false))
class Latest() : OrderBy(Selection(1, false))
}

View File

@@ -11,15 +11,15 @@ private const val DEFAULT_COVER_NAME = "cover.jpg"
class LocalCoverManager(
private val fileSystem: LocalSourceFileSystem,
) {
fun find(mangaUrl: String): File? =
fileSystem
.getFilesInMangaDirectory(mangaUrl)
fun find(mangaUrl: String): File? {
return fileSystem.getFilesInMangaDirectory(mangaUrl)
// Get all file whose names start with 'cover'
.filter { it.isFile && it.nameWithoutExtension.equals("cover", ignoreCase = true) }
// Get the first actual image
.firstOrNull {
ImageUtil.isImage(it.name) { it.inputStream() }
}
}
fun update(
manga: SManga,

View File

@@ -3,21 +3,13 @@ package eu.kanade.tachiyomi.source.local.io
import java.io.File
sealed interface Format {
data class Directory(
val file: File,
) : Format
data class Directory(val file: File) : Format
data class Zip(
val file: File,
) : Format
data class Zip(val file: File) : Format
data class Rar(
val file: File,
) : Format
data class Rar(val file: File) : Format
data class Epub(
val file: File,
) : Format
data class Epub(val file: File) : Format
class UnknownFormatException : Exception()

View File

@@ -6,22 +6,27 @@ import java.io.File
class LocalSourceFileSystem(
private val applicationDirs: ApplicationDirs,
) {
fun getBaseDirectories(): Sequence<File> = sequenceOf(File(applicationDirs.localMangaRoot))
fun getBaseDirectories(): Sequence<File> {
return sequenceOf(File(applicationDirs.localMangaRoot))
}
fun getFilesInBaseDirectories(): Sequence<File> =
getBaseDirectories()
fun getFilesInBaseDirectories(): Sequence<File> {
return getBaseDirectories()
// Get all the files inside all baseDir
.flatMap { it.listFiles().orEmpty().toList() }
}
fun getMangaDirectory(name: String): File? =
getFilesInBaseDirectories()
fun getMangaDirectory(name: String): File? {
return getFilesInBaseDirectories()
// Get the first mangaDir or null
.firstOrNull { it.isDirectory && it.name == name }
}
fun getFilesInMangaDirectory(name: String): Sequence<File> =
getFilesInBaseDirectories()
fun getFilesInMangaDirectory(name: String): Sequence<File> {
return getFilesInBaseDirectories()
// Filter out ones that are not related to the manga and is not a directory
.filter { it.isDirectory && it.name == name }
// Get all the files inside the filtered folders
.flatMap { it.listFiles().orEmpty().toList() }
}
}

View File

@@ -6,20 +6,18 @@ import java.io.File
/**
* Loader used to load a chapter from a .epub file.
*/
class EpubPageLoader(
file: File,
) : PageLoader {
class EpubPageLoader(file: File) : PageLoader {
private val epub = EpubFile(file)
override suspend fun getPages(): List<ReaderPage> =
epub
.getImagesFromPages()
override suspend fun getPages(): List<ReaderPage> {
return epub.getImagesFromPages()
.mapIndexed { i, path ->
val streamFn = { epub.getInputStream(epub.getEntry(path)!!) }
ReaderPage(i).apply {
stream = streamFn
}
}
}
override fun recycle() {
epub.close()

View File

@@ -12,21 +12,20 @@ import java.io.PipedOutputStream
/**
* Loader used to load a chapter from a .rar or .cbr file.
*/
class RarPageLoader(
file: File,
) : PageLoader {
class RarPageLoader(file: File) : PageLoader {
private val rar = Archive(file)
override suspend fun getPages(): List<ReaderPage> =
rar.fileHeaders
.asSequence()
override suspend fun getPages(): List<ReaderPage> {
return rar.fileHeaders.asSequence()
.filter { !it.isDirectory && ImageUtil.isImage(it.fileName) { rar.getInputStream(it) } }
.sortedWith { f1, f2 -> f1.fileName.compareToCaseInsensitiveNaturalOrder(f2.fileName) }
.mapIndexed { i, header ->
ReaderPage(i).apply {
stream = { getStream(rar, header) }
}
}.toList()
}
.toList()
}
override fun recycle() {
rar.close()

View File

@@ -8,21 +8,20 @@ import java.io.File
/**
* Loader used to load a chapter from a .zip or .cbz file.
*/
class ZipPageLoader(
file: File,
) : PageLoader {
private val zip = ZipFile.builder().setFile(file).get()
class ZipPageLoader(file: File) : PageLoader {
private val zip = ZipFile(file)
override suspend fun getPages(): List<ReaderPage> =
zip.entries
.asSequence()
override suspend fun getPages(): List<ReaderPage> {
return zip.entries.asSequence()
.filter { !it.isDirectory && ImageUtil.isImage(it.name) { zip.getInputStream(it) } }
.sortedWith { f1, f2 -> f1.name.compareToCaseInsensitiveNaturalOrder(f2.name) }
.mapIndexed { i, entry ->
ReaderPage(i).apply {
stream = { zip.getInputStream(entry) }
}
}.toList()
}
.toList()
}
override fun recycle() {
zip.close()

View File

@@ -17,7 +17,8 @@ fun SManga.copyFromComicInfo(comicInfo: ComicInfo) {
comicInfo.genre?.value,
comicInfo.tags?.value,
comicInfo.categories?.value,
).distinct()
)
.distinct()
.joinToString(", ") { it.trim() }
.takeIf { it.isNotEmpty() }
?.let { genre = it }
@@ -28,7 +29,8 @@ fun SManga.copyFromComicInfo(comicInfo: ComicInfo) {
comicInfo.colorist?.value,
comicInfo.letterer?.value,
comicInfo.coverArtist?.value,
).flatMap { it.split(", ") }
)
.flatMap { it.split(", ") }
.distinct()
.joinToString(", ") { it.trim() }
.takeIf { it.isNotEmpty() }
@@ -200,12 +202,14 @@ enum class ComicInfoPublishingStatus(
;
companion object {
fun toComicInfoValue(value: Long): String =
entries.firstOrNull { it.sMangaModelValue == value.toInt() }?.comicInfoValue
fun toComicInfoValue(value: Long): String {
return entries.firstOrNull { it.sMangaModelValue == value.toInt() }?.comicInfoValue
?: UNKNOWN.comicInfoValue
}
fun toSMangaValue(value: String?): Int =
entries.firstOrNull { it.comicInfoValue == value }?.sMangaModelValue
fun toSMangaValue(value: String?): Int {
return entries.firstOrNull { it.comicInfoValue == value }?.sMangaModelValue
?: UNKNOWN.sMangaModelValue
}
}
}

View File

@@ -2,40 +2,20 @@ package eu.kanade.tachiyomi.source.model
// The class is originally sealed, Tachidesk adds new subclasses for serialization
// sealed class Filter<T>(val name: String, var state: T) {
open class Filter<T>(
val name: String,
var state: T,
) {
open class Header(
name: String,
) : Filter<Any>(name, 0)
open class Filter<T>(val name: String, var state: T) {
open class Header(name: String) : Filter<Any>(name, 0)
open class Separator(
name: String = "",
) : Filter<Any>(name, 0)
open class Separator(name: String = "") : Filter<Any>(name, 0)
abstract class Select<V>(
name: String,
val values: Array<V>,
state: Int = 0,
) : Filter<Int>(name, state) {
abstract class Select<V>(name: String, val values: Array<V>, state: Int = 0) : Filter<Int>(name, state) {
val displayValues get() = values.map { it.toString() }
}
abstract class Text(
name: String,
state: String = "",
) : Filter<String>(name, state)
abstract class Text(name: String, state: String = "") : Filter<String>(name, state)
abstract class CheckBox(
name: String,
state: Boolean = false,
) : Filter<Boolean>(name, state)
abstract class CheckBox(name: String, state: Boolean = false) : Filter<Boolean>(name, state)
abstract class TriState(
name: String,
state: Int = STATE_IGNORE,
) : Filter<Int>(name, state) {
abstract class TriState(name: String, state: Int = STATE_IGNORE) : Filter<Int>(name, state) {
fun isIgnored() = state == STATE_IGNORE
fun isIncluded() = state == STATE_INCLUDE
@@ -49,20 +29,11 @@ open class Filter<T>(
}
}
abstract class Group<V>(
name: String,
state: List<V>,
) : Filter<List<V>>(name, state)
abstract class Group<V>(name: String, state: List<V>) : Filter<List<V>>(name, state)
abstract class Sort(
name: String,
val values: Array<String>,
state: Selection? = null,
) : Filter<Sort.Selection?>(name, state) {
data class Selection(
val index: Int,
val ascending: Boolean,
)
abstract class Sort(name: String, val values: Array<String>, state: Selection? = null) :
Filter<Sort.Selection?>(name, state) {
data class Selection(val index: Int, val ascending: Boolean)
}
override fun equals(other: Any?): Boolean {

View File

@@ -1,7 +1,5 @@
package eu.kanade.tachiyomi.source.model
data class FilterList(
val list: List<Filter<*>>,
) : List<Filter<*>> by list {
data class FilterList(val list: List<Filter<*>>) : List<Filter<*>> by list {
constructor(vararg fs: Filter<*>) : this(if (fs.isNotEmpty()) fs.asList() else emptyList())
}

View File

@@ -1,6 +1,3 @@
package eu.kanade.tachiyomi.source.model
data class MangasPage(
val mangas: List<SManga>,
val hasNextPage: Boolean,
)
data class MangasPage(val mangas: List<SManga>, val hasNextPage: Boolean)

View File

@@ -24,6 +24,8 @@ interface SChapter : Serializable {
}
companion object {
fun create(): SChapter = SChapterImpl()
fun create(): SChapter {
return SChapterImpl()
}
}
}

View File

@@ -25,6 +25,34 @@ interface SManga : Serializable {
var initialized: Boolean
fun copyFrom(other: SManga) {
if (other.author != null) {
author = other.author
}
if (other.artist != null) {
artist = other.artist
}
if (other.description != null) {
description = other.description
}
if (other.genre != null) {
genre = other.genre
}
if (other.thumbnail_url != null) {
thumbnail_url = other.thumbnail_url
}
status = other.status
if (!initialized) {
initialized = other.initialized
}
}
companion object {
const val UNKNOWN = 0
const val ONGOING = 1
@@ -34,7 +62,9 @@ interface SManga : Serializable {
const val CANCELLED = 5
const val ON_HIATUS = 6
fun create(): SManga = SMangaImpl()
fun create(): SManga {
return SMangaImpl()
}
}
}

View File

@@ -66,7 +66,9 @@ abstract class HttpSource : CatalogueSource {
open val client: OkHttpClient
get() = network.client
private fun generateId(): Long = generateId("${name.lowercase()}/$lang/$versionId")
private fun generateId(): Long {
return generateId("${name.lowercase()}/$lang/$versionId")
}
/**
* Generates a unique ID for the source based on the provided [name], [lang] and
@@ -119,13 +121,13 @@ abstract class HttpSource : CatalogueSource {
* @param page the page number to retrieve.
*/
@Deprecated("Use the non-RxJava API instead", replaceWith = ReplaceWith("getPopularManga"))
override fun fetchPopularManga(page: Int): Observable<MangasPage> =
client
.newCall(popularMangaRequest(page))
override fun fetchPopularManga(page: Int): Observable<MangasPage> {
return client.newCall(popularMangaRequest(page))
.asObservableSuccess()
.map { response ->
popularMangaParse(response)
}
}
/**
* Returns the request for the popular manga given the page.
@@ -154,19 +156,20 @@ abstract class HttpSource : CatalogueSource {
page: Int,
query: String,
filters: FilterList,
): Observable<MangasPage> =
Observable
.defer {
try {
client.newCall(searchMangaRequest(page, query, filters)).asObservableSuccess()
} catch (e: NoClassDefFoundError) {
// RxJava doesn't handle Errors, which tends to happen during global searches
// if an old extension using non-existent classes is still around
throw RuntimeException(e)
}
}.map { response ->
): Observable<MangasPage> {
return Observable.defer {
try {
client.newCall(searchMangaRequest(page, query, filters)).asObservableSuccess()
} catch (e: NoClassDefFoundError) {
// RxJava doesn't handle Errors, which tends to happen during global searches
// if an old extension using non-existent classes is still around
throw RuntimeException(e)
}
}
.map { response ->
searchMangaParse(response)
}
}
/**
* Returns the request for the search manga given the page.
@@ -194,13 +197,13 @@ abstract class HttpSource : CatalogueSource {
* @param page the page number to retrieve.
*/
@Deprecated("Use the non-RxJava API instead", replaceWith = ReplaceWith("getLatestUpdates"))
override fun fetchLatestUpdates(page: Int): Observable<MangasPage> =
client
.newCall(latestUpdatesRequest(page))
override fun fetchLatestUpdates(page: Int): Observable<MangasPage> {
return client.newCall(latestUpdatesRequest(page))
.asObservableSuccess()
.map { response ->
latestUpdatesParse(response)
}
}
/**
* Returns the request for latest manga given the page.
@@ -224,16 +227,18 @@ abstract class HttpSource : CatalogueSource {
* @return the updated manga.
*/
@Suppress("DEPRECATION")
override suspend fun getMangaDetails(manga: SManga): SManga = fetchMangaDetails(manga).awaitSingle()
override suspend fun getMangaDetails(manga: SManga): SManga {
return fetchMangaDetails(manga).awaitSingle()
}
@Deprecated("Use the non-RxJava API instead", replaceWith = ReplaceWith("getMangaDetails"))
override fun fetchMangaDetails(manga: SManga): Observable<SManga> =
client
.newCall(mangaDetailsRequest(manga))
override fun fetchMangaDetails(manga: SManga): Observable<SManga> {
return client.newCall(mangaDetailsRequest(manga))
.asObservableSuccess()
.map { response ->
mangaDetailsParse(response).apply { initialized = true }
}
}
/**
* Returns the request for the details of a manga. Override only if it's needed to change the
@@ -241,7 +246,9 @@ abstract class HttpSource : CatalogueSource {
*
* @param manga the manga to be updated.
*/
open fun mangaDetailsRequest(manga: SManga): Request = GET(baseUrl + manga.url, headers)
open fun mangaDetailsRequest(manga: SManga): Request {
return GET(baseUrl + manga.url, headers)
}
/**
* Parses the response from the site and returns the details of a manga.
@@ -268,10 +275,9 @@ abstract class HttpSource : CatalogueSource {
}
@Deprecated("Use the non-RxJava API instead", replaceWith = ReplaceWith("getChapterList"))
override fun fetchChapterList(manga: SManga): Observable<List<SChapter>> =
if (manga.status != SManga.LICENSED) {
client
.newCall(chapterListRequest(manga))
override fun fetchChapterList(manga: SManga): Observable<List<SChapter>> {
return if (manga.status != SManga.LICENSED) {
client.newCall(chapterListRequest(manga))
.asObservableSuccess()
.map { response ->
chapterListParse(response)
@@ -279,6 +285,7 @@ abstract class HttpSource : CatalogueSource {
} else {
Observable.error(LicensedMangaChaptersException())
}
}
/**
* Returns the request for updating the chapter list. Override only if it's needed to override
@@ -286,7 +293,9 @@ abstract class HttpSource : CatalogueSource {
*
* @param manga the manga to look for chapters.
*/
protected open fun chapterListRequest(manga: SManga): Request = GET(baseUrl + manga.url, headers)
protected open fun chapterListRequest(manga: SManga): Request {
return GET(baseUrl + manga.url, headers)
}
/**
* Parses the response from the site and returns a list of chapters.
@@ -303,16 +312,18 @@ abstract class HttpSource : CatalogueSource {
* @return the pages for the chapter.
*/
@Suppress("DEPRECATION")
override suspend fun getPageList(chapter: SChapter): List<Page> = fetchPageList(chapter).awaitSingle()
override suspend fun getPageList(chapter: SChapter): List<Page> {
return fetchPageList(chapter).awaitSingle()
}
@Deprecated("Use the non-RxJava API instead", replaceWith = ReplaceWith("getPageList"))
override fun fetchPageList(chapter: SChapter): Observable<List<Page>> =
client
.newCall(pageListRequest(chapter))
override fun fetchPageList(chapter: SChapter): Observable<List<Page>> {
return client.newCall(pageListRequest(chapter))
.asObservableSuccess()
.map { response ->
pageListParse(response)
}
}
/**
* Returns the request for getting the page list. Override only if it's needed to override the
@@ -320,7 +331,9 @@ abstract class HttpSource : CatalogueSource {
*
* @param chapter the chapter whose page list has to be fetched.
*/
protected open fun pageListRequest(chapter: SChapter): Request = GET(baseUrl + chapter.url, headers)
protected open fun pageListRequest(chapter: SChapter): Request {
return GET(baseUrl + chapter.url, headers)
}
/**
* Parses the response from the site and returns a list of pages.
@@ -337,14 +350,16 @@ abstract class HttpSource : CatalogueSource {
* @param page the page whose source image has to be fetched.
*/
@Suppress("DEPRECATION")
open suspend fun getImageUrl(page: Page): String = fetchImageUrl(page).awaitSingle()
open suspend fun getImageUrl(page: Page): String {
return fetchImageUrl(page).awaitSingle()
}
@Deprecated("Use the non-RxJava API instead", replaceWith = ReplaceWith("getImageUrl"))
open fun fetchImageUrl(page: Page): Observable<String> =
client
.newCall(imageUrlRequest(page))
open fun fetchImageUrl(page: Page): Observable<String> {
return client.newCall(imageUrlRequest(page))
.asObservableSuccess()
.map { imageUrlParse(it) }
}
/**
* Returns the request for getting the url to the source image. Override only if it's needed to
@@ -352,7 +367,9 @@ abstract class HttpSource : CatalogueSource {
*
* @param page the chapter whose page list has to be fetched
*/
protected open fun imageUrlRequest(page: Page): Request = GET(page.url, headers)
protected open fun imageUrlRequest(page: Page): Request {
return GET(page.url, headers)
}
/**
* Parses the response from the site and returns the absolute url to the source image.
@@ -368,10 +385,10 @@ abstract class HttpSource : CatalogueSource {
* @since extensions-lib 1.5
* @param page the page whose source image has to be downloaded.
*/
open suspend fun getImage(page: Page): Response =
client
.newCachelessCallWithProgress(imageRequest(page), page)
open suspend fun getImage(page: Page): Response {
return client.newCachelessCallWithProgress(imageRequest(page), page)
.awaitSuccess()
}
/**
* Returns the request for getting the source image. Override only if it's needed to override
@@ -379,7 +396,9 @@ abstract class HttpSource : CatalogueSource {
*
* @param page the chapter whose page list has to be fetched
*/
protected open fun imageRequest(page: Page): Request = GET(page.imageUrl!!, headers)
protected open fun imageRequest(page: Page): Request {
return GET(page.imageUrl!!, headers)
}
/**
* Assigns the url of the chapter without the scheme and domain. It saves some redundancy from
@@ -406,8 +425,8 @@ abstract class HttpSource : CatalogueSource {
*
* @param orig the full url.
*/
private fun getUrlWithoutDomain(orig: String): String =
try {
private fun getUrlWithoutDomain(orig: String): String {
return try {
val uri = URI(orig.replace(" ", "%20"))
var out = uri.path
if (uri.query != null) {
@@ -420,6 +439,7 @@ abstract class HttpSource : CatalogueSource {
} catch (e: URISyntaxException) {
orig
}
}
/**
* Returns the url of the provided manga
@@ -428,7 +448,9 @@ abstract class HttpSource : CatalogueSource {
* @param manga the manga
* @return url of the manga
*/
open fun getMangaUrl(manga: SManga): String = mangaDetailsRequest(manga).url.toString()
open fun getMangaUrl(manga: SManga): String {
return mangaDetailsRequest(manga).url.toString()
}
/**
* Returns the url of the provided chapter
@@ -437,7 +459,9 @@ abstract class HttpSource : CatalogueSource {
* @param chapter the chapter
* @return url of the chapter
*/
open fun getChapterUrl(chapter: SChapter): String = pageListRequest(chapter).url.toString()
open fun getChapterUrl(chapter: SChapter): String {
return pageListRequest(chapter).url.toString()
}
/**
* Called before inserting a new chapter into database. Use it if you need to override chapter

View File

@@ -138,7 +138,9 @@ abstract class ParsedHttpSource : HttpSource() {
*
* @param response the response from the site.
*/
override fun mangaDetailsParse(response: Response): SManga = mangaDetailsParse(response.asJsoup())
override fun mangaDetailsParse(response: Response): SManga {
return mangaDetailsParse(response.asJsoup())
}
/**
* Returns the details of the manga from the given [document].
@@ -174,7 +176,9 @@ abstract class ParsedHttpSource : HttpSource() {
*
* @param response the response from the site.
*/
override fun pageListParse(response: Response): List<Page> = pageListParse(response.asJsoup())
override fun pageListParse(response: Response): List<Page> {
return pageListParse(response.asJsoup())
}
/**
* Returns a page list from the given document.
@@ -188,7 +192,9 @@ abstract class ParsedHttpSource : HttpSource() {
*
* @param response the response from the site.
*/
override fun imageUrlParse(response: Response): String = imageUrlParse(response.asJsoup())
override fun imageUrlParse(response: Response): String {
return imageUrlParse(response.asJsoup())
}
/**
* Returns the absolute url to the source image from the document.

View File

@@ -8,17 +8,25 @@ import org.jsoup.nodes.Element
fun Element.selectText(
css: String,
defaultValue: String? = null,
): String? = select(css).first()?.text() ?: defaultValue
): String? {
return select(css).first()?.text() ?: defaultValue
}
fun Element.selectInt(
css: String,
defaultValue: Int = 0,
): Int = select(css).first()?.text()?.toInt() ?: defaultValue
): Int {
return select(css).first()?.text()?.toInt() ?: defaultValue
}
fun Element.attrOrText(css: String): String = if (css != "text") attr(css) else text()
fun Element.attrOrText(css: String): String {
return if (css != "text") attr(css) else text()
}
/**
* Returns a Jsoup document for this response.
* @param html the body of the response. Use only if the body was read before calling this method.
*/
fun Response.asJsoup(html: String? = null): Document = Jsoup.parse(html ?: body.string(), request.url.toString())
fun Response.asJsoup(html: String? = null): Document {
return Jsoup.parse(html ?: body.string(), request.url.toString())
}

View File

@@ -68,14 +68,15 @@ object ChapterRecognition {
* @param match result of regex
* @return chapter number if found else null
*/
private fun getChapterNumberFromMatch(match: MatchResult): Double =
match.let {
private fun getChapterNumberFromMatch(match: MatchResult): Double {
return match.let {
val initial = it.groups[1]?.value?.toDouble()!!
val subChapterDecimal = it.groups[2]?.value
val subChapterAlpha = it.groups[3]?.value
val addition = checkForDecimal(subChapterDecimal, subChapterAlpha)
initial.plus(addition)
}
}
/**
* Check for decimal in received strings

View File

@@ -1,10 +1,11 @@
package eu.kanade.tachiyomi.util.chapter
object ChapterSanitizer {
fun String.sanitize(title: String): String =
trim()
fun String.sanitize(title: String): String {
return trim()
.removePrefix(title)
.trim(*CHAPTER_TRIM_CHARS)
}
private val CHAPTER_TRIM_CHARS =
arrayOf(

View File

@@ -5,35 +5,29 @@ import java.security.MessageDigest
object Hash {
private val chars =
charArrayOf(
'0',
'1',
'2',
'3',
'4',
'5',
'6',
'7',
'8',
'9',
'a',
'b',
'c',
'd',
'e',
'f',
'0', '1', '2', '3', '4', '5', '6', '7', '8', '9',
'a', 'b', 'c', 'd', 'e', 'f',
)
private val MD5 get() = MessageDigest.getInstance("MD5")
private val SHA256 get() = MessageDigest.getInstance("SHA-256")
fun sha256(bytes: ByteArray): String = encodeHex(SHA256.digest(bytes))
fun sha256(bytes: ByteArray): String {
return encodeHex(SHA256.digest(bytes))
}
fun sha256(string: String): String = sha256(string.toByteArray())
fun sha256(string: String): String {
return sha256(string.toByteArray())
}
fun md5(bytes: ByteArray): String = encodeHex(MD5.digest(bytes))
fun md5(bytes: ByteArray): String {
return encodeHex(MD5.digest(bytes))
}
fun md5(string: String): String = md5(string.toByteArray())
fun md5(string: String): String {
return md5(string.toByteArray())
}
private fun encodeHex(data: ByteArray): String {
val l = data.size

View File

@@ -10,12 +10,13 @@ import kotlin.math.floor
fun String.chop(
count: Int,
replacement: String = "",
): String =
if (length > count) {
): String {
return if (length > count) {
take(count - replacement.length) + replacement
} else {
this
}
}
/**
* Replaces the given string to have at most [count] characters using [replacement] near the center.
@@ -45,7 +46,9 @@ fun String.compareToCaseInsensitiveNaturalOrder(other: String): Int {
/**
* Returns the size of the string as the number of bytes.
*/
fun String.byteSize(): Int = toByteArray(Charsets.UTF_8).size
fun String.byteSize(): Int {
return toByteArray(Charsets.UTF_8).size
}
/**
* Returns a string containing the first [n] bytes from this string, or the entire string if this

View File

@@ -11,13 +11,11 @@ import java.io.InputStream
/**
* Wrapper over ZipFile to load files in epub format.
*/
class EpubFile(
file: File,
) : Closeable {
class EpubFile(file: File) : Closeable {
/**
* Zip file of this epub.
*/
private val zip = ZipFile.builder().setFile(file).get()
private val zip = ZipFile(file)
/**
* Path separator used by this epub.
@@ -34,12 +32,16 @@ class EpubFile(
/**
* Returns an input stream for reading the contents of the specified zip file entry.
*/
fun getInputStream(entry: ZipArchiveEntry): InputStream = zip.getInputStream(entry)
fun getInputStream(entry: ZipArchiveEntry): InputStream {
return zip.getInputStream(entry)
}
/**
* Returns the zip file entry for the specified name, or null if not found.
*/
fun getEntry(name: String): ZipArchiveEntry? = zip.getEntry(name)
fun getEntry(name: String): ZipArchiveEntry? {
return zip.getEntry(name)
}
/**
* Returns the path of all the images found in the epub file.
@@ -79,8 +81,7 @@ class EpubFile(
*/
fun getPagesFromDocument(document: Document): List<String> {
val pages =
document
.select("manifest > item")
document.select("manifest > item")
.filter { node -> "application/xhtml+xml" == node.attr("media-type") }
.associateBy { it.attr("id") }

View File

@@ -7,7 +7,7 @@ package suwayomi.tachidesk.global.controller
* 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 io.javalin.http.HttpStatus
import io.javalin.http.HttpCode
import suwayomi.tachidesk.global.impl.GlobalMeta
import suwayomi.tachidesk.server.util.formParam
import suwayomi.tachidesk.server.util.handler
@@ -28,7 +28,7 @@ object GlobalMetaController {
ctx.status(200)
},
withResults = {
httpCode(HttpStatus.OK)
httpCode(HttpCode.OK)
},
)
@@ -48,8 +48,8 @@ object GlobalMetaController {
ctx.status(200)
},
withResults = {
httpCode(HttpStatus.OK)
httpCode(HttpStatus.NOT_FOUND)
httpCode(HttpCode.OK)
httpCode(HttpCode.NOT_FOUND)
},
)
}

View File

@@ -7,7 +7,7 @@ package suwayomi.tachidesk.global.controller
* 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 io.javalin.http.HttpStatus
import io.javalin.http.HttpCode
import suwayomi.tachidesk.global.impl.About
import suwayomi.tachidesk.global.impl.AboutDataClass
import suwayomi.tachidesk.global.impl.AppUpdate
@@ -31,7 +31,7 @@ object SettingsController {
ctx.json(About.getAbout())
},
withResults = {
json<AboutDataClass>(HttpStatus.OK)
json<AboutDataClass>(HttpCode.OK)
},
)
@@ -45,13 +45,12 @@ object SettingsController {
}
},
behaviorOf = { ctx ->
ctx.future {
future { AppUpdate.checkUpdate() }
.thenApply { ctx.json(it) }
}
ctx.future(
future { AppUpdate.checkUpdate() },
)
},
withResults = {
json<Array<UpdateDataClass>>(HttpStatus.OK)
json<Array<UpdateDataClass>>(HttpCode.OK)
},
)
}

View File

@@ -12,7 +12,6 @@ import suwayomi.tachidesk.server.generated.BuildConfig
data class AboutDataClass(
val name: String,
val version: String,
@Deprecated("The version includes the revision as the patch number")
val revision: String,
val buildType: String,
val buildTime: Long,
@@ -21,8 +20,8 @@ data class AboutDataClass(
)
object About {
fun getAbout(): AboutDataClass =
AboutDataClass(
fun getAbout(): AboutDataClass {
return AboutDataClass(
BuildConfig.NAME,
BuildConfig.VERSION,
BuildConfig.REVISION,
@@ -31,4 +30,5 @@ object About {
BuildConfig.GITHUB,
BuildConfig.DISCORD,
)
}
}

View File

@@ -31,26 +31,18 @@ object AppUpdate {
suspend fun checkUpdate(): List<UpdateDataClass> {
val stableJson =
json
.parseToJsonElement(
network.client
.newCall(
GET(LATEST_STABLE_CHANNEL_URL),
).await()
.body
.string(),
).jsonObject
json.parseToJsonElement(
network.client.newCall(
GET(LATEST_STABLE_CHANNEL_URL),
).await().body.string(),
).jsonObject
val previewJson =
json
.parseToJsonElement(
network.client
.newCall(
GET(LATEST_PREVIEW_CHANNEL_URL),
).await()
.body
.string(),
).jsonObject
json.parseToJsonElement(
network.client.newCall(
GET(LATEST_PREVIEW_CHANNEL_URL),
).await().body.string(),
).jsonObject
return listOf(
UpdateDataClass(

Some files were not shown because too many files have changed in this diff Show More