Compare commits

..

9 Commits

Author SHA1 Message Date
Aria Moradi
e4a3dad4e8 trying to bundle the changes 2023-06-05 01:45:22 +03:30
Aria Moradi
6934d344f0 better paths 2023-04-25 10:57:54 +03:30
Aria Moradi
62ee91ff0e fix python path 2023-04-25 10:47:21 +03:30
Aria Moradi
f37d7c841b become jep-less 2023-04-25 01:36:37 +03:30
Aria Moradi
86aaf28046 better initialization 2023-04-24 19:16:21 +03:30
Aria Moradi
34f658e5f2 remove DriverJar 2023-04-24 18:55:18 +03:30
Aria Moradi
597022f24a remove unused line 2023-04-24 18:29:10 +03:30
Aria Moradi
0458a80c17 remove unused line 2023-04-24 18:28:25 +03:30
Aria Moradi
cbefe1125d migrate webview solution to Jep 2023-04-24 18:26:04 +03:30
421 changed files with 5893 additions and 23678 deletions

View File

@@ -1,11 +0,0 @@
[*.{kt,kts}]
indent_size=4
insert_final_newline=true
ij_kotlin_allow_trailing_comma=true
ij_kotlin_allow_trailing_comma_on_call_site=true
ij_kotlin_name_count_to_use_star_import=2147483647
ij_kotlin_name_count_to_use_star_import_for_members=2147483647
ktlint_standard_discouraged-comment-location=disabled
ktlint_standard_if-else-wrapping=disabled
ktlint_standard_no-consecutive-comments=disabled

5
.gitattributes vendored
View File

@@ -25,7 +25,4 @@
*.pyc binary
*.swp binary
*.pdf binary
*.exe binary
*.avif binary
*.heif binary
*.jxl binary
*.exe binary

View File

@@ -11,7 +11,7 @@ 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 a request for adding/changing an extension it should be brought up to Tachiyomi: https://github.com/tachiyomiorg/tachiyomi-extensions/issues/new/choose
- 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
@@ -23,7 +23,7 @@ Note that the issue will be automatically closed if you do not fill out the titl
---
## Device information
- Suwayomi-Server version: (Example: v1.0.0-r1438-win32)
- Tachidesk version: (Example: v0.2.3-r255-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)

View File

@@ -11,7 +11,7 @@ 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 a request for adding/changing an extension it should be brought up to Tachiyomi: https://github.com/tachiyomiorg/tachiyomi-extensions/issues/new/choose
- 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
@@ -22,7 +22,7 @@ Note that the issue will be automatically closed if you do not fill out the titl
---
## What feature should be added to Suwayomi?
## What feature should be added to Tachidesk?
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

View File

@@ -3,10 +3,6 @@ name: CI Pull Request
on:
pull_request:
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
jobs:
check_wrapper:
name: Validate Gradle Wrapper
@@ -14,7 +10,7 @@ jobs:
steps:
- name: Clone repo
uses: actions/checkout@v4
uses: actions/checkout@v3
- name: Validate Gradle Wrapper
uses: gradle/wrapper-validation-action@v1
@@ -22,21 +18,26 @@ jobs:
build:
name: Build pull request
needs: check_wrapper
if: "!startsWith(github.event.head_commit.message, '[SKIP CI]')"
runs-on: ubuntu-latest
steps:
- name: Cancel previous runs
uses: styfle/cancel-workflow-action@0.10.0
with:
access_token: ${{ github.token }}
- name: Checkout pull request
uses: actions/checkout@v4
uses: actions/checkout@v2
with:
ref: ${{ github.event.pull_request.head.sha }}
path: master
fetch-depth: 0
- name: Set up JDK 1.8
uses: actions/setup-java@v4
uses: actions/setup-java@v1
with:
java-version: 8
distribution: 'temurin'
java-version: 1.8
- name: Copy CI gradle.properties
run: |
@@ -48,5 +49,5 @@ jobs:
uses: gradle/gradle-build-action@v2
with:
build-root-directory: master
arguments: ktlintCheck :server:shadowJar --stacktrace
arguments: :server:shadowJar --stacktrace

View File

@@ -5,10 +5,6 @@ on:
branches:
- master
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
jobs:
check_wrapper:
name: Validate Gradle Wrapper
@@ -23,20 +19,25 @@ jobs:
build:
name: Build Jar
needs: check_wrapper
if: "!startsWith(github.event.head_commit.message, '[SKIP CI]')"
runs-on: ubuntu-latest
steps:
- name: Cancel previous runs
uses: styfle/cancel-workflow-action@0.10.0
with:
access_token: ${{ github.token }}
- name: Checkout master branch
uses: actions/checkout@v4
uses: actions/checkout@v3
with:
ref: master
path: master
fetch-depth: 0
- name: Set up JDK 1.8
uses: actions/setup-java@v4
uses: actions/setup-java@v1
with:
java-version: 8
distribution: 'temurin'
java-version: 1.8
- name: Copy CI gradle.properties
run: |
@@ -68,7 +69,6 @@ jobs:
- name: Tar scripts dir to maintain file permissions
run: tar -cvzf scripts.tar.gz -C master/ scripts/
- name: Upload scripts.tar.gz
uses: actions/upload-artifact@v3
with:
@@ -163,7 +163,7 @@ jobs:
- name: Checkout Preview branch
uses: actions/checkout@v3
with:
repository: "Suwayomi/Suwayomi-Server-preview"
repository: "Suwayomi/Tachidesk-Server-preview"
ref: main
path: preview
token: ${{ secrets.DEPLOY_PREVIEW_TOKEN }}
@@ -172,9 +172,9 @@ jobs:
id: GenTagName
run: |
cd release
genTag=$(ls *.jar | sed -e's/Suwayomi-Server-\|.jar//g')
genTag=$(ls *.jar | sed -e's/Tachidesk-Server-\|.jar//g')
echo "$genTag"
echo "value=$genTag" >> $GITHUB_OUTPUT
echo "::set-output name=value::$genTag"
- name: Create Tag
run: |
@@ -196,7 +196,7 @@ jobs:
uses: softprops/action-gh-release@v1
with:
token: ${{ secrets.DEPLOY_PREVIEW_TOKEN }}
repository: "Suwayomi/Suwayomi-Server-preview"
repository: "Suwayomi/Tachidesk-Server-preview"
tag_name: ${{ steps.GenTagName.outputs.value }}
files: release/*

View File

@@ -37,7 +37,7 @@ jobs:
},
{
"type": "body",
"regex": ".*(Suwayomi-Server version|Server Operating System|Server Desktop Environment|Server JVM version|Client Operating System|Client Web Browser):.*(\\(Example:|<usually).*",
"regex": ".*(Tachidesk 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"
},
{

View File

@@ -6,17 +6,13 @@ on:
tags:
- "v*"
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
jobs:
check_wrapper:
name: Validate Gradle Wrapper
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@v1
@@ -26,18 +22,22 @@ jobs:
needs: check_wrapper
runs-on: ubuntu-latest
steps:
- name: Cancel previous runs
uses: styfle/cancel-workflow-action@0.10.0
with:
access_token: ${{ github.token }}
- name: Checkout ${{ github.ref }}
uses: actions/checkout@v4
uses: actions/checkout@v3
with:
ref: ${{ github.ref }}
path: master
fetch-depth: 0
- name: Set up JDK 1.8
uses: actions/setup-java@v4
uses: actions/setup-java@v1
with:
java-version: 8
distribution: 'temurin'
java-version: 1.8
- name: Copy CI gradle.properties
run: |
@@ -70,7 +70,6 @@ jobs:
- name: Tar scripts dir to maintain file permissions
run: tar -cvzf scripts.tar.gz -C master/ scripts/
- name: Upload scripts.tar.gz
uses: actions/upload-artifact@v3
with:

View File

@@ -1,11 +1,12 @@
@Suppress("DSL_SCOPE_VIOLATION")
plugins {
id(libs.plugins.kotlin.jvm.get().pluginId)
id(libs.plugins.kotlin.serialization.get().pluginId)
id(libs.plugins.ktlint.get().pluginId)
id(libs.plugins.kotlinter.get().pluginId)
}
dependencies {
// Shared
implementation(libs.bundles.shared)
testImplementation(libs.bundles.sharedTest)
}
}

View File

@@ -15,6 +15,6 @@ val ApplicationRootDir: String
get(): String {
return System.getProperty(
"$CONFIG_PREFIX.server.rootDir",
AppDirsFactory.getInstance().getUserDataDir("Tachidesk", null, null),
AppDirsFactory.getInstance().getUserDataDir("Tachidesk", null, null)
)
}

View File

@@ -5,9 +5,8 @@ import org.kodein.di.bind
import org.kodein.di.singleton
class ConfigKodeinModule {
fun create() =
DI.Module("ConfigManager") {
// Config module
bind<ConfigManager>() with singleton { GlobalConfigManager }
}
fun create() = DI.Module("ConfigManager") {
// Config module
bind<ConfigManager>() with singleton { GlobalConfigManager }
}
}

View File

@@ -10,12 +10,7 @@ package xyz.nulldev.ts.config
import ch.qos.logback.classic.Level
import com.typesafe.config.Config
import com.typesafe.config.ConfigFactory
import com.typesafe.config.ConfigValue
import com.typesafe.config.ConfigValueFactory
import com.typesafe.config.parser.ConfigDocument
import com.typesafe.config.parser.ConfigDocumentFactory
import kotlinx.coroutines.sync.Mutex
import kotlinx.coroutines.sync.withLock
import com.typesafe.config.ConfigRenderOptions
import mu.KotlinLogging
import java.io.File
@@ -23,18 +18,14 @@ import java.io.File
* Manages app config.
*/
open class ConfigManager {
val logger = KotlinLogging.logger {}
private val generatedModules = mutableMapOf<Class<out ConfigModule>, ConfigModule>()
private val userConfigFile = File(ApplicationRootDir, "server.conf")
private var internalConfig = loadConfigs()
val config: Config
get() = internalConfig
val config by lazy { loadConfigs() }
// Public read-only view of modules
val loadedModules: Map<Class<out ConfigModule>, ConfigModule>
get() = generatedModules
private val mutex = Mutex()
val logger = KotlinLogging.logger {}
/**
* Get a config module
@@ -47,12 +38,6 @@ open class ConfigManager {
@Suppress("UNCHECKED_CAST")
fun <T : ConfigModule> module(type: Class<T>): T = loadedModules[type] as T
private fun getUserConfig(): Config {
return userConfigFile.let {
ConfigFactory.parseFile(it)
}
}
/**
* Load configs
*/
@@ -63,25 +48,30 @@ open class ConfigManager {
val baseConfig =
ConfigFactory.parseMap(
mapOf(
// override AndroidCompat's rootDir
"androidcompat.rootDir" to "$ApplicationRootDir/android-compat",
),
"androidcompat.rootDir" to "$ApplicationRootDir/android-compat" // override AndroidCompat's rootDir
)
)
// Load user config
val userConfig = getUserConfig()
val userConfig =
File(ApplicationRootDir, "server.conf").let {
ConfigFactory.parseFile(it)
}
val config =
ConfigFactory.empty()
.withFallback(baseConfig)
.withFallback(userConfig)
.withFallback(compatConfig)
.withFallback(serverConfig)
.resolve()
val config = ConfigFactory.empty()
.withFallback(baseConfig)
.withFallback(userConfig)
.withFallback(compatConfig)
.withFallback(serverConfig)
.resolve()
// set log level early
if (debugLogsEnabled(config)) {
setLogLevelFor(BASE_LOGGER_NAME, Level.DEBUG)
setLogLevel(Level.DEBUG)
}
logger.debug {
"Loaded config:\n" + config.root().render(ConfigRenderOptions.concise().setFormatted(true))
}
return config
@@ -96,71 +86,6 @@ open class ConfigManager {
registerModule(it)
}
}
private fun updateUserConfigFile(
path: String,
value: ConfigValue,
) {
val userConfigDoc = ConfigDocumentFactory.parseFile(userConfigFile)
val updatedConfigDoc = userConfigDoc.withValue(path, value)
val newFileContent = updatedConfigDoc.render()
userConfigFile.writeText(newFileContent)
}
suspend fun updateValue(
path: String,
value: Any,
) {
mutex.withLock {
val configValue = ConfigValueFactory.fromAnyRef(value)
updateUserConfigFile(path, configValue)
internalConfig = internalConfig.withValue(path, configValue)
}
}
fun resetUserConfig(updateInternalConfig: Boolean = true): ConfigDocument {
val serverConfigFileContent = this::class.java.getResource("/server-reference.conf")?.readText()
val serverConfigDoc = ConfigDocumentFactory.parseString(serverConfigFileContent)
userConfigFile.writeText(serverConfigDoc.render())
if (updateInternalConfig) {
getUserConfig().entrySet().forEach { internalConfig = internalConfig.withValue(it.key, it.value) }
}
return serverConfigDoc
}
/**
* Makes sure the "UserConfig" is up-to-date.
*
* - adds missing settings
* - removes outdated settings
*/
fun updateUserConfig() {
val serverConfig = ConfigFactory.parseResources("server-reference.conf")
val userConfig = getUserConfig()
val hasMissingSettings = serverConfig.entrySet().any { !userConfig.hasPath(it.key) }
val hasOutdatedSettings = userConfig.entrySet().any { !serverConfig.hasPath(it.key) }
val isUserConfigOutdated = hasMissingSettings || hasOutdatedSettings
if (!isUserConfigOutdated) {
return
}
logger.debug {
"user config is out of date, updating... (missingSettings= $hasMissingSettings, outdatedSettings= $hasOutdatedSettings"
}
var newUserConfigDoc: ConfigDocument = resetUserConfig(false)
userConfig.entrySet().filter {
serverConfig.hasPath(
it.key,
)
}.forEach { newUserConfigDoc = newUserConfigDoc.withValue(it.key, it.value) }
userConfigFile.writeText(newUserConfigDoc.render())
}
}
object GlobalConfigManager : ConfigManager()

View File

@@ -8,8 +8,6 @@ package xyz.nulldev.ts.config
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
import com.typesafe.config.Config
import com.typesafe.config.ConfigFactory
import com.typesafe.config.ConfigValueFactory
import io.github.config4k.getValue
import kotlin.reflect.KProperty
@@ -17,39 +15,28 @@ import kotlin.reflect.KProperty
* Abstract config module.
*/
@Suppress("UNUSED_PARAMETER")
abstract class ConfigModule(getConfig: () -> Config)
abstract class ConfigModule(config: Config)
/**
* Abstract jvm-commandline-argument-overridable config module.
*/
abstract class SystemPropertyOverridableConfigModule(getConfig: () -> Config, moduleName: String) : ConfigModule(getConfig) {
val overridableConfig = SystemPropertyOverrideDelegate(getConfig, moduleName)
abstract class SystemPropertyOverridableConfigModule(config: Config, moduleName: String) : ConfigModule(config) {
val overridableConfig = SystemPropertyOverrideDelegate(config, 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) {
inline operator fun <R, reified T> getValue(
thisRef: R,
property: KProperty<*>,
): T {
val config = getConfig()
class SystemPropertyOverrideDelegate(val config: Config, val moduleName: String) {
inline operator fun <R, reified T> getValue(thisRef: R, property: KProperty<*>): T {
val configValue: T = config.getValue(thisRef, property)
val combined =
System.getProperty(
"$CONFIG_PREFIX.$moduleName.${property.name}",
if (T::class.simpleName == "List") {
ConfigValueFactory.fromAnyRef(configValue).render()
} else {
configValue.toString()
},
)
val combined = System.getProperty(
"$CONFIG_PREFIX.$moduleName.${property.name}",
configValue.toString()
)
return when (T::class.simpleName) {
"Int" -> combined.toInt()
"Boolean" -> combined.toBoolean()
"Double" -> combined.toDouble()
"List" -> ConfigFactory.parseString("internal=" + combined).getStringList("internal").orEmpty()
// add more types as needed
else -> combined // covers String
} as T

View File

@@ -8,88 +8,12 @@ package xyz.nulldev.ts.config
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
import ch.qos.logback.classic.Level
import ch.qos.logback.classic.LoggerContext
import ch.qos.logback.classic.encoder.PatternLayoutEncoder
import ch.qos.logback.classic.spi.ILoggingEvent
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 mu.KotlinLogging
import org.slf4j.Logger
import org.slf4j.LoggerFactory
private fun createRollingFileAppender(
logContext: LoggerContext,
logDirPath: String,
): RollingFileAppender<ILoggingEvent> {
val logFilename = "application"
val logEncoder =
PatternLayoutEncoder().apply {
pattern = "%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger - %msg%n"
context = logContext
start()
}
val appender =
RollingFileAppender<ILoggingEvent>().apply {
name = "FILE"
context = logContext
encoder = logEncoder
file = "$logDirPath/$logFilename.log"
}
val rollingPolicy =
SizeAndTimeBasedRollingPolicy<ILoggingEvent>().apply {
context = logContext
setParent(appender)
fileNamePattern = "$logDirPath/${logFilename}_%d{yyyy-MM-dd}_%i.log.gz"
setMaxFileSize(FileSize.valueOf("10mb"))
maxHistory = 14
setTotalSizeCap(FileSize.valueOf("1gb"))
start()
}
appender.rollingPolicy = rollingPolicy
appender.start()
return appender
}
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
return context.getLogger(name)
}
fun initLoggerConfig(appRootPath: String) {
val context = LoggerFactory.getILoggerFactory() as LoggerContext
val logger = getBaseLogger()
// logback logs to the console by default (at least when adding a console appender logs in the console are duplicated)
logger.addAppender(createRollingFileAppender(context, "$appRootPath/logs"))
// set "kotlin exposed" log level
setLogLevelFor("Exposed", Level.ERROR)
}
const val BASE_LOGGER_NAME = "_BaseLogger"
fun setLogLevelFor(
name: String,
level: Level,
) {
val logger =
if (name == BASE_LOGGER_NAME) {
getBaseLogger()
} else {
getLogger(name)
}
logger.level = level
fun setLogLevel(level: Level) {
(KotlinLogging.logger(Logger.ROOT_LOGGER_NAME).underlyingLogger as ch.qos.logback.classic.Logger).level = level
}
fun debugLogsEnabled(config: Config) =

View File

@@ -2,6 +2,5 @@ package xyz.nulldev.ts.config.util
import com.typesafe.config.Config
operator fun Config.get(key: String) =
getString(key)
?: throw IllegalStateException("Could not find value for config entry: $key!")
operator fun Config.get(key: String) = getString(key)
?: throw IllegalStateException("Could not find value for config entry: $key!")

View File

@@ -1,7 +1,8 @@
@Suppress("DSL_SCOPE_VIOLATION")
plugins {
id(libs.plugins.kotlin.jvm.get().pluginId)
id(libs.plugins.kotlin.serialization.get().pluginId)
id(libs.plugins.ktlint.get().pluginId)
id(libs.plugins.kotlinter.get().pluginId)
}
dependencies {

View File

@@ -1,23 +1,20 @@
package android.graphics;
import android.annotation.ColorInt;
import android.annotation.NonNull;
import java.awt.image.BufferedImage;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.util.Iterator;
import javax.imageio.IIOImage;
import javax.imageio.ImageIO;
import javax.imageio.ImageWriteParam;
import javax.imageio.ImageWriter;
import javax.imageio.stream.ImageOutputStream;
import java.awt.image.BufferedImage;
import java.awt.image.Raster;
import java.io.IOException;
import java.io.OutputStream;
import java.util.Iterator;
public final class Bitmap {
private final int width;
private final int height;
private final BufferedImage image;
private int width;
private int height;
private BufferedImage image;
public Bitmap(BufferedImage image) {
this.image = image;
@@ -62,8 +59,8 @@ public final class Bitmap {
final int nativeInt;
private static final Config[] sConfigs = {
null, ALPHA_8, null, RGB_565, ARGB_4444, ARGB_8888, RGBA_F16, HARDWARE, RGBA_1010102
private static Config sConfigs[] = {
null, ALPHA_8, null, RGB_565, ARGB_4444, ARGB_8888, RGBA_F16, HARDWARE, RGBA_1010102
};
Config(int ni) {
@@ -75,60 +72,11 @@ public final class Bitmap {
}
}
/**
* Common code for checking that x and y are >= 0
*
* @param x x coordinate to ensure is >= 0
* @param y y coordinate to ensure is >= 0
*/
private static void checkXYSign(int x, int y) {
if (x < 0) {
throw new IllegalArgumentException("x must be >= 0");
}
if (y < 0) {
throw new IllegalArgumentException("y must be >= 0");
}
}
/**
* Common code for checking that width and height are > 0
*
* @param width width to ensure is > 0
* @param height height to ensure is > 0
*/
private static void checkWidthHeight(int width, int height) {
if (width <= 0) {
throw new IllegalArgumentException("width must be > 0");
}
if (height <= 0) {
throw new IllegalArgumentException("height must be > 0");
}
}
public static Bitmap createBitmap(int width, int height, Config config) {
BufferedImage image = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
return new Bitmap(image);
}
public static Bitmap createBitmap(@NonNull Bitmap source, int x, int y, int width, int height) {
checkXYSign(x, y);
checkWidthHeight(width, height);
if (x + width > source.getWidth()) {
throw new IllegalArgumentException("x + width must be <= bitmap.width()");
}
if (y + height > source.getHeight()) {
throw new IllegalArgumentException("y + height must be <= bitmap.height()");
}
// Android will make a copy when creating a sub image,
// so we do the same here
BufferedImage subImage = source.image.getSubimage(x, y, width, height);
BufferedImage newImage = new BufferedImage(subImage.getWidth(), subImage.getHeight(), subImage.getType());
newImage.setData(subImage.getData());
return new Bitmap(newImage);
}
public boolean compress(CompressFormat format, int quality, OutputStream stream) {
if (stream == null) {
throw new NullPointerException();
@@ -139,7 +87,7 @@ public final class Bitmap {
}
float qualityFloat = ((float) quality) / 100;
String formatString;
String formatString = "";
if (format == Bitmap.CompressFormat.PNG) {
formatString = "png";
} else if (format == Bitmap.CompressFormat.JPEG) {
@@ -152,7 +100,7 @@ public final class Bitmap {
if (!writers.hasNext()) {
throw new IllegalStateException("no image writers found for this format!");
}
ImageWriter writer = writers.next();
ImageWriter writer = (ImageWriter) writers.next();
ImageOutputStream ios;
try {
@@ -163,73 +111,19 @@ public final class Bitmap {
writer.setOutput(ios);
ImageWriteParam param = writer.getDefaultWriteParam();
if ("jpg".equals(formatString)) {
if (formatString == "jpg") {
param.setCompressionMode(ImageWriteParam.MODE_EXPLICIT);
param.setCompressionQuality(qualityFloat);
}
try {
writer.write(null, new IIOImage(image, null, null), param);
ios.close();
writer.dispose();
writer.write(null, new IIOImage(image, null, null), param);
ios.close();
writer.dispose();
} catch (IOException ex) {
throw new RuntimeException(ex);
}
return true;
}
/**
* Shared code to check for illegal arguments passed to getPixels()
* or setPixels()
*
* @param x left edge of the area of pixels to access
* @param y top edge of the area of pixels to access
* @param width width of the area of pixels to access
* @param height height of the area of pixels to access
* @param offset offset into pixels[] array
* @param stride number of elements in pixels[] between each logical row
* @param pixels array to hold the area of pixels being accessed
*/
private void checkPixelsAccess(int x, int y, int width, int height,
int offset, int stride, int[] pixels) {
checkXYSign(x, y);
if (width < 0) {
throw new IllegalArgumentException("width must be >= 0");
}
if (height < 0) {
throw new IllegalArgumentException("height must be >= 0");
}
if (x + width > getWidth()) {
throw new IllegalArgumentException(
"x + width must be <= bitmap.width()");
}
if (y + height > getHeight()) {
throw new IllegalArgumentException(
"y + height must be <= bitmap.height()");
}
if (Math.abs(stride) < width) {
throw new IllegalArgumentException("abs(stride) must be >= width");
}
int lastScanline = offset + (height - 1) * stride;
int length = pixels.length;
if (offset < 0 || (offset + width > length)
|| lastScanline < 0
|| (lastScanline + width > length)) {
throw new ArrayIndexOutOfBoundsException();
}
}
public void getPixels(@ColorInt int[] pixels, int offset, int stride,
int x, int y, int width, int height) {
checkPixelsAccess(x, y, width, height, offset, stride, pixels);
Raster raster = image.getData();
int[] rasterPixels = raster.getPixels(x, y, width, height, (int[]) null);
for (int ht = 0; ht < height; ht++) {
int rowOffset = offset + stride * ht;
System.arraycopy(rasterPixels, ht * width, pixels, rowOffset, width);
}
}
}

View File

@@ -12,7 +12,7 @@ class PreferenceManager {
fun getDefaultSharedPreferences(context: Context) =
context.getSharedPreferences(
context.applicationInfo.packageName,
Context.MODE_PRIVATE,
Context.MODE_PRIVATE
)!!
}
}

View File

@@ -1,247 +0,0 @@
package android.webkit;
import android.annotation.Nullable;
import xyz.nulldev.androidcompat.webkit.CookieManagerImpl;
public abstract class CookieManager {
/**
* @deprecated This class should not be constructed by applications, use {@link #getInstance}
* instead to fetch the singleton instance.
*/
// TODO(ntfschr): mark this as @SystemApi after a year.
@Deprecated
public CookieManager() {}
@Override
protected Object clone() throws CloneNotSupportedException {
throw new CloneNotSupportedException("doesn't implement Cloneable");
}
private static CookieManager INSTANCE = null;
private static final Object lock = new Object();
/**
* Gets the singleton CookieManager instance.
*
* @return the singleton CookieManager instance
*/
public static CookieManager getInstance() {
if (INSTANCE != null) {
return INSTANCE;
} else {
synchronized (lock) {
if (INSTANCE == null) {
INSTANCE = new CookieManagerImpl();
}
return INSTANCE;
}
}
}
/**
* Sets whether the application's {@link WebView} instances should send and
* accept cookies.
* By default this is set to {@code true} and the WebView accepts cookies.
* <p>
* When this is {@code true}
* {@link CookieManager#setAcceptThirdPartyCookies setAcceptThirdPartyCookies} and
* {@link CookieManager#setAcceptFileSchemeCookies setAcceptFileSchemeCookies}
* can be used to control the policy for those specific types of cookie.
*
* @param accept whether {@link WebView} instances should send and accept
* cookies
*/
public abstract void setAcceptCookie(boolean accept);
/**
* Gets whether the application's {@link WebView} instances send and accept
* cookies.
*
* @return {@code true} if {@link WebView} instances send and accept cookies
*/
public abstract boolean acceptCookie();
/**
* Sets whether the {@link WebView} should allow third party cookies to be set.
* Allowing third party cookies is a per WebView policy and can be set
* differently on different WebView instances.
* <p>
* Apps that target {@link android.os.Build.VERSION_CODES#KITKAT} or below
* default to allowing third party cookies. Apps targeting
* {@link android.os.Build.VERSION_CODES#LOLLIPOP} or later default to disallowing
* third party cookies.
*
* @param webview the {@link WebView} instance to set the cookie policy on
* @param accept whether the {@link WebView} instance should accept
* third party cookies
*/
public abstract void setAcceptThirdPartyCookies(WebView webview, boolean accept);
/**
* Gets whether the {@link WebView} should allow third party cookies to be set.
*
* @param webview the {@link WebView} instance to get the cookie policy for
* @return {@code true} if the {@link WebView} accepts third party cookies
*/
public abstract boolean acceptThirdPartyCookies(WebView webview);
/**
* Sets a single cookie (key-value pair) for the given URL. Any existing cookie with the same
* host, path and name will be replaced with the new cookie. The cookie being set
* will be ignored if it is expired. To set multiple cookies, your application should invoke
* this method multiple times.
*
* <p>The {@code value} parameter must follow the format of the {@code Set-Cookie} HTTP
* response header defined by
* <a href="https://tools.ietf.org/html/draft-ietf-httpbis-rfc6265bis-03">RFC6265bis</a>.
* This is a key-value pair of the form {@code "key=value"}, optionally followed by a list of
* cookie attributes delimited with semicolons (ex. {@code "key=value; Max-Age=123"}). Please
* consult the RFC specification for a list of valid attributes.
*
* <p class="note"><b>Note:</b> if specifying a {@code value} containing the {@code "Secure"}
* attribute, {@code url} must use the {@code "https://"} scheme.
*
* @param url the URL for which the cookie is to be set
* @param value the cookie as a string, using the format of the 'Set-Cookie'
* HTTP response header
*/
public abstract void setCookie(String url, String value);
/**
* Sets a single cookie (key-value pair) for the given URL. Any existing cookie with the same
* host, path and name will be replaced with the new cookie. The cookie being set
* will be ignored if it is expired. To set multiple cookies, your application should invoke
* this method multiple times.
*
* <p>The {@code value} parameter must follow the format of the {@code Set-Cookie} HTTP
* response header defined by
* <a href="https://tools.ietf.org/html/draft-ietf-httpbis-rfc6265bis-03">RFC6265bis</a>.
* This is a key-value pair of the form {@code "key=value"}, optionally followed by a list of
* cookie attributes delimited with semicolons (ex. {@code "key=value; Max-Age=123"}). Please
* consult the RFC specification for a list of valid attributes.
*
* <p>This method is asynchronous. If a {@link ValueCallback} is provided,
* {@link ValueCallback#onReceiveValue} will be called on the current
* thread's {@link android.os.Looper} once the operation is complete.
* The value provided to the callback indicates whether the cookie was set successfully.
* You can pass {@code null} as the callback if you don't need to know when the operation
* completes or whether it succeeded, and in this case it is safe to call the method from a
* thread without a Looper.
*
* <p class="note"><b>Note:</b> if specifying a {@code value} containing the {@code "Secure"}
* attribute, {@code url} must use the {@code "https://"} scheme.
*
* @param url the URL for which the cookie is to be set
* @param value the cookie as a string, using the format of the 'Set-Cookie'
* HTTP response header
* @param callback a callback to be executed when the cookie has been set
*/
public abstract void setCookie(String url, String value, @Nullable ValueCallback<Boolean>
callback);
/**
* Gets all the cookies for the given URL. This may return multiple key-value pairs if multiple
* cookies are associated with this URL, in which case each cookie will be delimited by {@code
* "; "} characters (semicolon followed by a space). Each key-value pair will be of the form
* {@code "key=value"}.
*
* @param url the URL for which the cookies are requested
* @return value the cookies as a string, using the format of the 'Cookie'
* HTTP request header
*/
public abstract String getCookie(String url);
/**
* Removes all session cookies, which are cookies without an expiration
* date.
* @deprecated use {@link #removeSessionCookies(ValueCallback)} instead.
*/
@Deprecated
public abstract void removeSessionCookie();
/**
* Removes all session cookies, which are cookies without an expiration
* date.
* <p>
* This method is asynchronous.
* If a {@link ValueCallback} is provided,
* {@link ValueCallback#onReceiveValue(Object)} will be called on the current
* thread's {@link android.os.Looper} once the operation is complete.
* The value provided to the callback indicates whether any cookies were removed.
* You can pass {@code null} as the callback if you don't need to know when the operation
* completes or whether any cookie were removed, and in this case it is safe to call the
* method from a thread without a Looper.
* @param callback a callback which is executed when the session cookies have been removed
*/
public abstract void removeSessionCookies(@Nullable ValueCallback<Boolean> callback);
/**
* Removes all cookies.
* @deprecated Use {@link #removeAllCookies(ValueCallback)} instead.
*/
@Deprecated
public abstract void removeAllCookie();
/**
* Removes all cookies.
* <p>
* This method is asynchronous.
* If a {@link ValueCallback} is provided,
* {@link ValueCallback#onReceiveValue(Object)} will be called on the current
* thread's {@link android.os.Looper} once the operation is complete.
* The value provided to the callback indicates whether any cookies were removed.
* You can pass {@code null} as the callback if you don't need to know when the operation
* completes or whether any cookies were removed, and in this case it is safe to call the
* method from a thread without a Looper.
* @param callback a callback which is executed when the cookies have been removed
*/
public abstract void removeAllCookies(@Nullable ValueCallback<Boolean> callback);
/**
* Gets whether there are stored cookies.
*
* @return {@code true} if there are stored cookies
*/
public abstract boolean hasCookies();
/**
* Removes all expired cookies.
* @deprecated The WebView handles removing expired cookies automatically.
*/
@Deprecated
public abstract void removeExpiredCookie();
/**
* Ensures all cookies currently accessible through the getCookie API are
* written to persistent storage.
* This call will block the caller until it is done and may perform I/O.
*/
public abstract void flush();
/**
* Gets whether the application's {@link WebView} instances send and accept
* cookies for file scheme URLs.
*
* @return {@code true} if {@link WebView} instances send and accept cookies for
* file scheme URLs
*/
// Static for backward compatibility.
public static boolean allowFileSchemeCookies() {
return getInstance().allowFileSchemeCookiesImpl();
}
public abstract boolean allowFileSchemeCookiesImpl();
/**
* Sets whether the application's {@link WebView} instances should send and accept cookies for
* file scheme URLs.
* <p>
* Use of cookies with file scheme URLs is potentially insecure and turned off by default. All
* {@code file://} URLs share all their cookies, which may lead to leaking private app cookies
* (ex. any malicious file can access cookies previously set by other (trusted) files).
* <p class="note">
* Loading content via {@code file://} URLs is generally discouraged. See the note in
* {@link WebSettings#setAllowFileAccess}.
* Using <a href="{@docRoot}reference/androidx/webkit/WebViewAssetLoader.html">
* androidx.webkit.WebViewAssetLoader</a> to load files over {@code http(s)://} URLs allows
* the standard web security model to be used for setting and sharing cookies for local files.
* <p>
* Note that calls to this method will have no effect if made after calling other
* {@link CookieManager} APIs.
*
* @deprecated This setting is not secure, please use
* <a href="{@docRoot}reference/androidx/webkit/WebViewAssetLoader.html">
* androidx.webkit.WebViewAssetLoader</a> instead.
*/
// Static for backward compatibility.
@Deprecated
public static void setAcceptFileSchemeCookies(boolean accept) {
getInstance().setAcceptFileSchemeCookiesImpl(accept);
}
public abstract void setAcceptFileSchemeCookiesImpl(boolean accept);
}

View File

@@ -1,45 +0,0 @@
/*
* Copyright (C) 2017 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
@file:Suppress("NOTHING_TO_INLINE") // Aliases to public API.
package androidx.core.net
import android.net.Uri
import java.io.File
/**
* Creates a Uri from the given encoded URI string.
*
* @see Uri.parse
*/
public inline fun String.toUri(): Uri = Uri.parse(this)
/**
* Creates a Uri from the given file.
*
* @see Uri.fromFile
*/
public inline fun File.toUri(): Uri = Uri.fromFile(this)
/**
* Creates a [File] from the given [Uri]. Note that this will throw an
* [IllegalArgumentException] when invoked on a [Uri] that lacks `file` scheme.
*/
public fun Uri.toFile(): File {
require(scheme == "file") { "Uri lacks 'file' scheme: $this" }
return File(requireNotNull(path) { "Uri path is null: $this" })
}

View File

@@ -22,7 +22,6 @@ public class Preference {
@JsonIgnore
protected Context context;
private boolean isVisible;
private String key;
private CharSequence title;
private CharSequence summary;
@@ -101,14 +100,6 @@ public class Preference {
return sharedPreferences;
}
public void setVisible(boolean visible) {
isVisible = visible;
}
public boolean getVisible() {
return isVisible;
}
/** Tachidesk specific API */
public void setSharedPreferences(SharedPreferences sharedPreferences) {
this.sharedPreferences = sharedPreferences;

View File

@@ -7,6 +7,7 @@ import org.kodein.di.instance
import xyz.nulldev.androidcompat.androidimpl.CustomContext
class AndroidCompat {
val context: CustomContext by DI.global.instance()
fun startApp(application: Application) {

View File

@@ -18,13 +18,10 @@ class AndroidCompatInitializer {
GlobalConfigManager.registerModules(
FilesConfigModule.register(GlobalConfigManager.config),
ApplicationInfoConfigModule.register(GlobalConfigManager.config),
SystemConfigModule.register(GlobalConfigManager.config),
SystemConfigModule.register(GlobalConfigManager.config)
)
// Set some properties extensions use
System.setProperty(
"http.agent",
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36",
)
System.setProperty("http.agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36")
}
}

View File

@@ -18,24 +18,22 @@ import xyz.nulldev.androidcompat.service.ServiceSupport
*/
class AndroidCompatModule {
fun create() =
DI.Module("AndroidCompat") {
bind<AndroidFiles>() with singleton { AndroidFiles() }
fun create() = DI.Module("AndroidCompat") {
bind<AndroidFiles>() with singleton { AndroidFiles() }
bind<ApplicationInfoImpl>() with singleton { ApplicationInfoImpl() }
bind<ApplicationInfoImpl>() with singleton { ApplicationInfoImpl() }
bind<ServiceSupport>() with singleton { ServiceSupport() }
bind<ServiceSupport>() with singleton { ServiceSupport() }
bind<FakePackageManager>() with singleton { FakePackageManager() }
bind<FakePackageManager>() with singleton { FakePackageManager() }
bind<PackageController>() with singleton { PackageController() }
bind<PackageController>() with singleton { PackageController() }
// Context
bind<CustomContext>() with singleton { CustomContext() }
bind<Context>() with
singleton {
val context: Context by DI.global.instance<CustomContext>()
context
}
// Context
bind<CustomContext>() with singleton { CustomContext() }
bind<Context>() with singleton {
val context: Context by DI.global.instance<CustomContext>()
context
}
}
}

View File

@@ -8,11 +8,12 @@ import xyz.nulldev.ts.config.ConfigModule
* Application info config.
*/
class ApplicationInfoConfigModule(getConfig: () -> Config) : ConfigModule(getConfig) {
val packageName: String by getConfig()
val debug: Boolean by getConfig()
class ApplicationInfoConfigModule(config: Config) : ConfigModule(config) {
val packageName: String by config
val debug: Boolean by config
companion object {
fun register(config: Config) = ApplicationInfoConfigModule { config.getConfig("android.app") }
fun register(config: Config) =
ApplicationInfoConfigModule(config.getConfig("android.app"))
}
}

View File

@@ -8,26 +8,27 @@ import xyz.nulldev.ts.config.ConfigModule
* Files configuration modules. Specifies where to store the Android files.
*/
class FilesConfigModule(getConfig: () -> Config) : ConfigModule(getConfig) {
val dataDir: String by getConfig()
val filesDir: String by getConfig()
val noBackupFilesDir: String by getConfig()
val externalFilesDirs: MutableList<String> by getConfig()
val obbDirs: MutableList<String> by getConfig()
val cacheDir: String by getConfig()
val codeCacheDir: String by getConfig()
val externalCacheDirs: MutableList<String> by getConfig()
val externalMediaDirs: MutableList<String> by getConfig()
val rootDir: String by getConfig()
val externalStorageDir: String by getConfig()
val downloadCacheDir: String by getConfig()
val databasesDir: String by getConfig()
class FilesConfigModule(config: Config) : ConfigModule(config) {
val dataDir: String by config
val filesDir: String by config
val noBackupFilesDir: String by config
val externalFilesDirs: MutableList<String> by config
val obbDirs: MutableList<String> by config
val cacheDir: String by config
val codeCacheDir: String by config
val externalCacheDirs: MutableList<String> by config
val externalMediaDirs: MutableList<String> by config
val rootDir: String by config
val externalStorageDir: String by config
val downloadCacheDir: String by config
val databasesDir: String by config
val prefsDir: String by getConfig()
val prefsDir: String by config
val packageDir: String by getConfig()
val packageDir: String by config
companion object {
fun register(config: Config) = FilesConfigModule { config.getConfig("android.files") }
fun register(config: Config) =
FilesConfigModule(config.getConfig("android.files"))
}
}

View File

@@ -4,22 +4,19 @@ import com.typesafe.config.Config
import io.github.config4k.getValue
import xyz.nulldev.ts.config.ConfigModule
class SystemConfigModule(val getConfig: () -> Config) : ConfigModule(getConfig) {
val isDebuggable: Boolean by getConfig()
class SystemConfigModule(val config: Config) : ConfigModule(config) {
val isDebuggable: Boolean by config
val propertyPrefix = "properties."
fun getStringProperty(property: String) = getConfig().getString("$propertyPrefix$property")!!
fun getIntProperty(property: String) = getConfig().getInt("$propertyPrefix$property")
fun getLongProperty(property: String) = getConfig().getLong("$propertyPrefix$property")
fun getBooleanProperty(property: String) = getConfig().getBoolean("$propertyPrefix$property")
fun hasProperty(property: String) = getConfig().hasPath("$propertyPrefix$property")
fun getStringProperty(property: String) = config.getString("$propertyPrefix$property")!!
fun getIntProperty(property: String) = config.getInt("$propertyPrefix$property")
fun getLongProperty(property: String) = config.getLong("$propertyPrefix$property")
fun getBooleanProperty(property: String) = config.getBoolean("$propertyPrefix$property")
fun hasProperty(property: String) = config.hasPath("$propertyPrefix$property")
companion object {
fun register(config: Config) = SystemConfigModule { config.getConfig("android.system") }
fun register(config: Config) =
SystemConfigModule(config.getConfig("android.system"))
}
}

View File

@@ -20,6 +20,7 @@ import java.util.Calendar
@Suppress("UNCHECKED_CAST")
class ScrollableResultSet(val parent: ResultSet) : ResultSet by parent {
private val cachedContent = mutableListOf<ResultSetEntry>()
private val columnCache = mutableMapOf<String, Int>()
private var lastReturnWasNull = false
@@ -28,10 +29,9 @@ class ScrollableResultSet(val parent: ResultSet) : ResultSet by parent {
val parentMetadata = parent.metaData
val columnCount = parentMetadata.columnCount
val columnLabels =
(1..columnCount).map {
parentMetadata.getColumnLabel(it)
}.toTypedArray()
val columnLabels = (1..columnCount).map {
parentMetadata.getColumnLabel(it)
}.toTypedArray()
init {
val columnCount = columnCount
@@ -43,11 +43,10 @@ class ScrollableResultSet(val parent: ResultSet) : ResultSet by parent {
// Fill cache
while (parent.next()) {
cachedContent +=
ResultSetEntry().apply {
for (i in 1..columnCount)
data += parent.getObject(i)
}
cachedContent += ResultSetEntry().apply {
for (i in 1..columnCount)
data += parent.getObject(i)
}
resultSetLength++
}
}
@@ -93,121 +92,67 @@ class ScrollableResultSet(val parent: ResultSet) : ResultSet by parent {
return obj(columnLabel) as NClob
}
override fun updateNString(
columnIndex: Int,
nString: String?,
) {
override fun updateNString(columnIndex: Int, nString: String?) {
notImplemented()
}
override fun updateNString(
columnLabel: String?,
nString: String?,
) {
override fun updateNString(columnLabel: String?, nString: String?) {
notImplemented()
}
override fun updateBinaryStream(
columnIndex: Int,
x: InputStream?,
length: Int,
) {
override fun updateBinaryStream(columnIndex: Int, x: InputStream?, length: Int) {
notImplemented()
}
override fun updateBinaryStream(
columnLabel: String?,
x: InputStream?,
length: Int,
) {
override fun updateBinaryStream(columnLabel: String?, x: InputStream?, length: Int) {
notImplemented()
}
override fun updateBinaryStream(
columnIndex: Int,
x: InputStream?,
length: Long,
) {
override fun updateBinaryStream(columnIndex: Int, x: InputStream?, length: Long) {
notImplemented()
}
override fun updateBinaryStream(
columnLabel: String?,
x: InputStream?,
length: Long,
) {
override fun updateBinaryStream(columnLabel: String?, x: InputStream?, length: Long) {
notImplemented()
}
override fun updateBinaryStream(
columnIndex: Int,
x: InputStream?,
) {
override fun updateBinaryStream(columnIndex: Int, x: InputStream?) {
notImplemented()
}
override fun updateBinaryStream(
columnLabel: String?,
x: InputStream?,
) {
override fun updateBinaryStream(columnLabel: String?, x: InputStream?) {
notImplemented()
}
override fun updateTimestamp(
columnIndex: Int,
x: Timestamp?,
) {
override fun updateTimestamp(columnIndex: Int, x: Timestamp?) {
notImplemented()
}
override fun updateTimestamp(
columnLabel: String?,
x: Timestamp?,
) {
override fun updateTimestamp(columnLabel: String?, x: Timestamp?) {
notImplemented()
}
override fun updateNCharacterStream(
columnIndex: Int,
x: Reader?,
length: Long,
) {
override fun updateNCharacterStream(columnIndex: Int, x: Reader?, length: Long) {
notImplemented()
}
override fun updateNCharacterStream(
columnLabel: String?,
reader: Reader?,
length: Long,
) {
override fun updateNCharacterStream(columnLabel: String?, reader: Reader?, length: Long) {
notImplemented()
}
override fun updateNCharacterStream(
columnIndex: Int,
x: Reader?,
) {
override fun updateNCharacterStream(columnIndex: Int, x: Reader?) {
notImplemented()
}
override fun updateNCharacterStream(
columnLabel: String?,
reader: Reader?,
) {
override fun updateNCharacterStream(columnLabel: String?, reader: Reader?) {
notImplemented()
}
override fun updateInt(
columnIndex: Int,
x: Int,
) {
override fun updateInt(columnIndex: Int, x: Int) {
notImplemented()
}
override fun updateInt(
columnLabel: String?,
x: Int,
) {
override fun updateInt(columnLabel: String?, x: Int) {
notImplemented()
}
@@ -225,18 +170,12 @@ class ScrollableResultSet(val parent: ResultSet) : ResultSet by parent {
notImplemented()
}
override fun getDate(
columnIndex: Int,
cal: Calendar?,
): Date {
override fun getDate(columnIndex: Int, cal: Calendar?): Date {
// TODO Maybe?
notImplemented()
}
override fun getDate(
columnLabel: String?,
cal: Calendar?,
): Date {
override fun getDate(columnLabel: String?, cal: Calendar?): Date {
// TODO Maybe?
notImplemented()
}
@@ -246,17 +185,11 @@ class ScrollableResultSet(val parent: ResultSet) : ResultSet by parent {
notImplemented()
}
override fun updateFloat(
columnIndex: Int,
x: Float,
) {
override fun updateFloat(columnIndex: Int, x: Float) {
notImplemented()
}
override fun updateFloat(
columnLabel: String?,
x: Float,
) {
override fun updateFloat(columnLabel: String?, x: Float) {
notImplemented()
}
@@ -272,18 +205,12 @@ class ScrollableResultSet(val parent: ResultSet) : ResultSet by parent {
return cursor - 1 < resultSetLength
}
override fun getBigDecimal(
columnIndex: Int,
scale: Int,
): BigDecimal {
override fun getBigDecimal(columnIndex: Int, scale: Int): BigDecimal {
// TODO Maybe?
notImplemented()
}
override fun getBigDecimal(
columnLabel: String?,
scale: Int,
): BigDecimal {
override fun getBigDecimal(columnLabel: String?, scale: Int): BigDecimal {
// TODO Maybe?
notImplemented()
}
@@ -296,17 +223,11 @@ class ScrollableResultSet(val parent: ResultSet) : ResultSet by parent {
return obj(columnLabel) as BigDecimal
}
override fun updateBytes(
columnIndex: Int,
x: ByteArray?,
) {
override fun updateBytes(columnIndex: Int, x: ByteArray?) {
notImplemented()
}
override fun updateBytes(
columnLabel: String?,
x: ByteArray?,
) {
override fun updateBytes(columnLabel: String?, x: ByteArray?) {
notImplemented()
}
@@ -328,18 +249,12 @@ class ScrollableResultSet(val parent: ResultSet) : ResultSet by parent {
notImplemented()
}
override fun getTime(
columnIndex: Int,
cal: Calendar?,
): Time {
override fun getTime(columnIndex: Int, cal: Calendar?): Time {
// TODO Maybe?
notImplemented()
}
override fun getTime(
columnLabel: String?,
cal: Calendar?,
): Time {
override fun getTime(columnLabel: String?, cal: Calendar?): Time {
// TODO Maybe?
notImplemented()
}
@@ -413,49 +328,27 @@ class ScrollableResultSet(val parent: ResultSet) : ResultSet by parent {
return cursorValid()
}
override fun updateAsciiStream(
columnIndex: Int,
x: InputStream?,
length: Int,
) {
override fun updateAsciiStream(columnIndex: Int, x: InputStream?, length: Int) {
notImplemented()
}
override fun updateAsciiStream(
columnLabel: String?,
x: InputStream?,
length: Int,
) {
override fun updateAsciiStream(columnLabel: String?, x: InputStream?, length: Int) {
notImplemented()
}
override fun updateAsciiStream(
columnIndex: Int,
x: InputStream?,
length: Long,
) {
override fun updateAsciiStream(columnIndex: Int, x: InputStream?, length: Long) {
notImplemented()
}
override fun updateAsciiStream(
columnLabel: String?,
x: InputStream?,
length: Long,
) {
override fun updateAsciiStream(columnLabel: String?, x: InputStream?, length: Long) {
notImplemented()
}
override fun updateAsciiStream(
columnIndex: Int,
x: InputStream?,
) {
override fun updateAsciiStream(columnIndex: Int, x: InputStream?) {
notImplemented()
}
override fun updateAsciiStream(
columnLabel: String?,
x: InputStream?,
) {
override fun updateAsciiStream(columnLabel: String?, x: InputStream?) {
notImplemented()
}
@@ -467,107 +360,61 @@ class ScrollableResultSet(val parent: ResultSet) : ResultSet by parent {
return obj(columnLabel) as URL
}
override fun updateShort(
columnIndex: Int,
x: Short,
) {
override fun updateShort(columnIndex: Int, x: Short) {
notImplemented()
}
override fun updateShort(
columnLabel: String?,
x: Short,
) {
override fun updateShort(columnLabel: String?, x: Short) {
notImplemented()
}
override fun getType() = ResultSet.TYPE_SCROLL_INSENSITIVE
override fun updateNClob(
columnIndex: Int,
nClob: NClob?,
) {
override fun updateNClob(columnIndex: Int, nClob: NClob?) {
notImplemented()
}
override fun updateNClob(
columnLabel: String?,
nClob: NClob?,
) {
override fun updateNClob(columnLabel: String?, nClob: NClob?) {
notImplemented()
}
override fun updateNClob(
columnIndex: Int,
reader: Reader?,
length: Long,
) {
override fun updateNClob(columnIndex: Int, reader: Reader?, length: Long) {
notImplemented()
}
override fun updateNClob(
columnLabel: String?,
reader: Reader?,
length: Long,
) {
override fun updateNClob(columnLabel: String?, reader: Reader?, length: Long) {
notImplemented()
}
override fun updateNClob(
columnIndex: Int,
reader: Reader?,
) {
override fun updateNClob(columnIndex: Int, reader: Reader?) {
notImplemented()
}
override fun updateNClob(
columnLabel: String?,
reader: Reader?,
) {
override fun updateNClob(columnLabel: String?, reader: Reader?) {
notImplemented()
}
override fun updateRef(
columnIndex: Int,
x: Ref?,
) {
override fun updateRef(columnIndex: Int, x: Ref?) {
notImplemented()
}
override fun updateRef(
columnLabel: String?,
x: Ref?,
) {
override fun updateRef(columnLabel: String?, x: Ref?) {
notImplemented()
}
override fun updateObject(
columnIndex: Int,
x: Any?,
scaleOrLength: Int,
) {
override fun updateObject(columnIndex: Int, x: Any?, scaleOrLength: Int) {
notImplemented()
}
override fun updateObject(
columnIndex: Int,
x: Any?,
) {
override fun updateObject(columnIndex: Int, x: Any?) {
notImplemented()
}
override fun updateObject(
columnLabel: String?,
x: Any?,
scaleOrLength: Int,
) {
override fun updateObject(columnLabel: String?, x: Any?, scaleOrLength: Int) {
notImplemented()
}
override fun updateObject(
columnLabel: String?,
x: Any?,
) {
override fun updateObject(columnLabel: String?, x: Any?) {
notImplemented()
}
@@ -575,17 +422,11 @@ class ScrollableResultSet(val parent: ResultSet) : ResultSet by parent {
internalMove(resultSetLength + 1)
}
override fun updateLong(
columnIndex: Int,
x: Long,
) {
override fun updateLong(columnIndex: Int, x: Long) {
notImplemented()
}
override fun updateLong(
columnLabel: String?,
x: Long,
) {
override fun updateLong(columnLabel: String?, x: Long) {
notImplemented()
}
@@ -599,47 +440,27 @@ class ScrollableResultSet(val parent: ResultSet) : ResultSet by parent {
notImplemented()
}
override fun updateClob(
columnIndex: Int,
x: Clob?,
) {
override fun updateClob(columnIndex: Int, x: Clob?) {
notImplemented()
}
override fun updateClob(
columnLabel: String?,
x: Clob?,
) {
override fun updateClob(columnLabel: String?, x: Clob?) {
notImplemented()
}
override fun updateClob(
columnIndex: Int,
reader: Reader?,
length: Long,
) {
override fun updateClob(columnIndex: Int, reader: Reader?, length: Long) {
notImplemented()
}
override fun updateClob(
columnLabel: String?,
reader: Reader?,
length: Long,
) {
override fun updateClob(columnLabel: String?, reader: Reader?, length: Long) {
notImplemented()
}
override fun updateClob(
columnIndex: Int,
reader: Reader?,
) {
override fun updateClob(columnIndex: Int, reader: Reader?) {
notImplemented()
}
override fun updateClob(
columnLabel: String?,
reader: Reader?,
) {
override fun updateClob(columnLabel: String?, reader: Reader?) {
notImplemented()
}
@@ -659,31 +480,19 @@ class ScrollableResultSet(val parent: ResultSet) : ResultSet by parent {
return obj(columnLabel) as String?
}
override fun updateSQLXML(
columnIndex: Int,
xmlObject: SQLXML?,
) {
override fun updateSQLXML(columnIndex: Int, xmlObject: SQLXML?) {
notImplemented()
}
override fun updateSQLXML(
columnLabel: String?,
xmlObject: SQLXML?,
) {
override fun updateSQLXML(columnLabel: String?, xmlObject: SQLXML?) {
notImplemented()
}
override fun updateDate(
columnIndex: Int,
x: Date?,
) {
override fun updateDate(columnIndex: Int, x: Date?) {
notImplemented()
}
override fun updateDate(
columnLabel: String?,
x: Date?,
) {
override fun updateDate(columnLabel: String?, x: Date?) {
notImplemented()
}
@@ -695,33 +504,21 @@ class ScrollableResultSet(val parent: ResultSet) : ResultSet by parent {
return obj(columnLabel)
}
override fun getObject(
columnIndex: Int,
map: MutableMap<String, Class<*>>?,
): Any {
override fun getObject(columnIndex: Int, map: MutableMap<String, Class<*>>?): Any {
// TODO Maybe?
notImplemented()
}
override fun getObject(
columnLabel: String?,
map: MutableMap<String, Class<*>>?,
): Any {
override fun getObject(columnLabel: String?, map: MutableMap<String, Class<*>>?): Any {
// TODO Maybe?
notImplemented()
}
override fun <T : Any?> getObject(
columnIndex: Int,
type: Class<T>?,
): T {
override fun <T : Any?> getObject(columnIndex: Int, type: Class<T>?): T {
return obj(columnIndex) as T
}
override fun <T : Any?> getObject(
columnLabel: String?,
type: Class<T>?,
): T {
override fun <T : Any?> getObject(columnLabel: String?, type: Class<T>?): T {
return obj(columnLabel) as T
}
@@ -730,17 +527,11 @@ class ScrollableResultSet(val parent: ResultSet) : ResultSet by parent {
return cursorValid()
}
override fun updateDouble(
columnIndex: Int,
x: Double,
) {
override fun updateDouble(columnIndex: Int, x: Double) {
notImplemented()
}
override fun updateDouble(
columnLabel: String?,
x: Double,
) {
override fun updateDouble(columnLabel: String?, x: Double) {
notImplemented()
}
@@ -774,61 +565,35 @@ class ScrollableResultSet(val parent: ResultSet) : ResultSet by parent {
notImplemented()
}
override fun updateBlob(
columnIndex: Int,
x: Blob?,
) {
override fun updateBlob(columnIndex: Int, x: Blob?) {
notImplemented()
}
override fun updateBlob(
columnLabel: String?,
x: Blob?,
) {
override fun updateBlob(columnLabel: String?, x: Blob?) {
notImplemented()
}
override fun updateBlob(
columnIndex: Int,
inputStream: InputStream?,
length: Long,
) {
override fun updateBlob(columnIndex: Int, inputStream: InputStream?, length: Long) {
notImplemented()
}
override fun updateBlob(
columnLabel: String?,
inputStream: InputStream?,
length: Long,
) {
override fun updateBlob(columnLabel: String?, inputStream: InputStream?, length: Long) {
notImplemented()
}
override fun updateBlob(
columnIndex: Int,
inputStream: InputStream?,
) {
override fun updateBlob(columnIndex: Int, inputStream: InputStream?) {
notImplemented()
}
override fun updateBlob(
columnLabel: String?,
inputStream: InputStream?,
) {
override fun updateBlob(columnLabel: String?, inputStream: InputStream?) {
notImplemented()
}
override fun updateByte(
columnIndex: Int,
x: Byte,
) {
override fun updateByte(columnIndex: Int, x: Byte) {
notImplemented()
}
override fun updateByte(
columnLabel: String?,
x: Byte,
) {
override fun updateByte(columnLabel: String?, x: Byte) {
notImplemented()
}
@@ -862,17 +627,11 @@ class ScrollableResultSet(val parent: ResultSet) : ResultSet by parent {
notImplemented()
}
override fun updateString(
columnIndex: Int,
x: String?,
) {
override fun updateString(columnIndex: Int, x: String?) {
notImplemented()
}
override fun updateString(
columnLabel: String?,
x: String?,
) {
override fun updateString(columnLabel: String?, x: String?) {
notImplemented()
}
@@ -892,17 +651,11 @@ class ScrollableResultSet(val parent: ResultSet) : ResultSet by parent {
return cursor - 1 < resultSetLength
}
override fun updateBoolean(
columnIndex: Int,
x: Boolean,
) {
override fun updateBoolean(columnIndex: Int, x: Boolean) {
notImplemented()
}
override fun updateBoolean(
columnLabel: String?,
x: Boolean,
) {
override fun updateBoolean(columnLabel: String?, x: Boolean) {
notImplemented()
}
@@ -912,17 +665,11 @@ class ScrollableResultSet(val parent: ResultSet) : ResultSet by parent {
override fun rowUpdated() = false
override fun updateBigDecimal(
columnIndex: Int,
x: BigDecimal?,
) {
override fun updateBigDecimal(columnIndex: Int, x: BigDecimal?) {
notImplemented()
}
override fun updateBigDecimal(
columnLabel: String?,
x: BigDecimal?,
) {
override fun updateBigDecimal(columnLabel: String?, x: BigDecimal?) {
notImplemented()
}
@@ -942,17 +689,11 @@ class ScrollableResultSet(val parent: ResultSet) : ResultSet by parent {
return getBinaryStream(columnLabel)
}
override fun updateTime(
columnIndex: Int,
x: Time?,
) {
override fun updateTime(columnIndex: Int, x: Time?) {
notImplemented()
}
override fun updateTime(
columnLabel: String?,
x: Time?,
) {
override fun updateTime(columnLabel: String?, x: Time?) {
notImplemented()
}
@@ -966,18 +707,12 @@ class ScrollableResultSet(val parent: ResultSet) : ResultSet by parent {
notImplemented()
}
override fun getTimestamp(
columnIndex: Int,
cal: Calendar?,
): Timestamp {
override fun getTimestamp(columnIndex: Int, cal: Calendar?): Timestamp {
// TODO Maybe?
notImplemented()
}
override fun getTimestamp(
columnLabel: String?,
cal: Calendar?,
): Timestamp {
override fun getTimestamp(columnLabel: String?, cal: Calendar?): Timestamp {
// TODO Maybe?
notImplemented()
}
@@ -994,17 +729,11 @@ class ScrollableResultSet(val parent: ResultSet) : ResultSet by parent {
override fun getConcurrency() = ResultSet.CONCUR_READ_ONLY
override fun updateRowId(
columnIndex: Int,
x: RowId?,
) {
override fun updateRowId(columnIndex: Int, x: RowId?) {
notImplemented()
}
override fun updateRowId(
columnLabel: String?,
x: RowId?,
) {
override fun updateRowId(columnLabel: String?, x: RowId?) {
notImplemented()
}
@@ -1016,17 +745,11 @@ class ScrollableResultSet(val parent: ResultSet) : ResultSet by parent {
return getBinaryStream(columnLabel).reader()
}
override fun updateArray(
columnIndex: Int,
x: Array?,
) {
override fun updateArray(columnIndex: Int, x: Array?) {
notImplemented()
}
override fun updateArray(
columnLabel: String?,
x: Array?,
) {
override fun updateArray(columnLabel: String?, x: Array?) {
notImplemented()
}
@@ -1091,13 +814,9 @@ class ScrollableResultSet(val parent: ResultSet) : ResultSet by parent {
override fun getMetaData(): ResultSetMetaData {
return object : ResultSetMetaData by parentMetadata {
override fun isReadOnly(column: Int) = true
override fun isWritable(column: Int) = false
override fun isDefinitelyWritable(column: Int) = false
override fun getColumnCount() = this@ScrollableResultSet.columnCount
override fun getColumnLabel(column: Int): String {
return columnLabels[column - 1]
}
@@ -1112,49 +831,27 @@ class ScrollableResultSet(val parent: ResultSet) : ResultSet by parent {
return (obj(columnLabel) as ByteArray).inputStream()
}
override fun updateCharacterStream(
columnIndex: Int,
x: Reader?,
length: Int,
) {
override fun updateCharacterStream(columnIndex: Int, x: Reader?, length: Int) {
notImplemented()
}
override fun updateCharacterStream(
columnLabel: String?,
reader: Reader?,
length: Int,
) {
override fun updateCharacterStream(columnLabel: String?, reader: Reader?, length: Int) {
notImplemented()
}
override fun updateCharacterStream(
columnIndex: Int,
x: Reader?,
length: Long,
) {
override fun updateCharacterStream(columnIndex: Int, x: Reader?, length: Long) {
notImplemented()
}
override fun updateCharacterStream(
columnLabel: String?,
reader: Reader?,
length: Long,
) {
override fun updateCharacterStream(columnLabel: String?, reader: Reader?, length: Long) {
notImplemented()
}
override fun updateCharacterStream(
columnIndex: Int,
x: Reader?,
) {
override fun updateCharacterStream(columnIndex: Int, x: Reader?) {
notImplemented()
}
override fun updateCharacterStream(
columnLabel: String?,
reader: Reader?,
) {
override fun updateCharacterStream(columnLabel: String?, reader: Reader?) {
notImplemented()
}

View File

@@ -9,8 +9,7 @@ package xyz.nulldev.androidcompat.io.sharedprefs
import android.content.SharedPreferences
import com.russhwolf.settings.ExperimentalSettingsApi
import com.russhwolf.settings.PropertiesSettings
import com.russhwolf.settings.Settings
import com.russhwolf.settings.PreferencesSettings
import com.russhwolf.settings.serialization.decodeValue
import com.russhwolf.settings.serialization.decodeValueOrNull
import com.russhwolf.settings.serialization.encodeValue
@@ -18,68 +17,21 @@ 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
import kotlin.io.path.Path
import kotlin.io.path.createParentDirectories
import kotlin.io.path.deleteIfExists
import kotlin.io.path.exists
import kotlin.io.path.inputStream
import kotlin.io.path.outputStream
import java.util.prefs.PreferenceChangeListener
import java.util.prefs.Preferences
@OptIn(ExperimentalSerializationApi::class, ExperimentalSettingsApi::class)
class JavaSharedPreferences(key: String) : SharedPreferences {
companion object {
private val logger = KotlinLogging.logger {}
}
private val file =
Path(
ApplicationRootDir,
"settings",
"${SafePath.buildValidFilename(key)}.xml",
)
private val properties =
Properties().also { properties ->
try {
if (file.exists()) {
file.inputStream().use { properties.loadFromXML(it) }
}
} catch (e: Exception) {
logger.error(e) { "Error loading settings from $key" }
}
}
private val preferences =
PropertiesSettings(
properties,
onModify = { properties ->
try {
if (properties.isEmpty) {
file.deleteIfExists()
} else {
file.createParentDirectories()
file.outputStream().use {
properties.storeToXML(it, null)
}
}
} catch (e: Exception) {
logger.error(e) { "Error saving settings in $key" }
}
},
)
private val listeners = mutableMapOf<SharedPreferences.OnSharedPreferenceChangeListener, (String) -> Unit>()
private val javaPreferences = Preferences.userRoot().node("suwayomi/tachidesk/$key")
private val preferences = PreferencesSettings(javaPreferences)
private val listeners = mutableMapOf<SharedPreferences.OnSharedPreferenceChangeListener, PreferenceChangeListener>()
// TODO: 2021-05-29 Need to find a way to get this working with all pref types
override fun getAll(): MutableMap<String, *> {
return preferences.keys.associateWith { preferences.getStringOrNull(it) }.toMutableMap()
}
override fun getString(
key: String,
defValue: String?,
): String? {
override fun getString(key: String, defValue: String?): String? {
return if (defValue != null) {
preferences.getString(key, defValue)
} else {
@@ -87,10 +39,7 @@ class JavaSharedPreferences(key: String) : SharedPreferences {
}
}
override fun getStringSet(
key: String,
defValues: Set<String>?,
): Set<String>? {
override fun getStringSet(key: String, defValues: Set<String>?): Set<String>? {
try {
return if (defValues != null) {
preferences.decodeValue(SetSerializer(String.serializer()), key, defValues)
@@ -102,31 +51,19 @@ class JavaSharedPreferences(key: String) : SharedPreferences {
}
}
override fun getInt(
key: String,
defValue: Int,
): Int {
override fun getInt(key: String, defValue: Int): Int {
return preferences.getInt(key, defValue)
}
override fun getLong(
key: String,
defValue: Long,
): Long {
override fun getLong(key: String, defValue: Long): Long {
return preferences.getLong(key, defValue)
}
override fun getFloat(
key: String,
defValue: Float,
): Float {
override fun getFloat(key: String, defValue: Float): Float {
return preferences.getFloat(key, defValue)
}
override fun getBoolean(
key: String,
defValue: Boolean,
): Boolean {
override fun getBoolean(key: String, defValue: Boolean): Boolean {
return preferences.getBoolean(key, defValue)
}
@@ -135,27 +72,19 @@ class JavaSharedPreferences(key: String) : SharedPreferences {
}
override fun edit(): SharedPreferences.Editor {
return Editor(preferences) { key ->
listeners.forEach { (_, listener) ->
listener(key)
}
}
return Editor(preferences)
}
class Editor(private val preferences: Settings, private val notify: (String) -> Unit) : SharedPreferences.Editor {
class Editor(private val preferences: PreferencesSettings) : 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 object Clear : Action()
object Clear : Action()
}
override fun putString(
key: String,
value: String?,
): SharedPreferences.Editor {
override fun putString(key: String, value: String?): SharedPreferences.Editor {
if (value != null) {
actions += Action.Add(key, value)
} else {
@@ -166,7 +95,7 @@ class JavaSharedPreferences(key: String) : SharedPreferences {
override fun putStringSet(
key: String,
values: MutableSet<String>?,
values: MutableSet<String>?
): SharedPreferences.Editor {
if (values != null) {
actions += Action.Add(key, values)
@@ -176,34 +105,22 @@ class JavaSharedPreferences(key: String) : SharedPreferences {
return this
}
override fun putInt(
key: String,
value: Int,
): SharedPreferences.Editor {
override fun putInt(key: String, value: Int): SharedPreferences.Editor {
actions += Action.Add(key, value)
return this
}
override fun putLong(
key: String,
value: Long,
): SharedPreferences.Editor {
override fun putLong(key: String, value: Long): SharedPreferences.Editor {
actions += Action.Add(key, value)
return this
}
override fun putFloat(
key: String,
value: Float,
): SharedPreferences.Editor {
override fun putFloat(key: String, value: Float): SharedPreferences.Editor {
actions += Action.Add(key, value)
return this
}
override fun putBoolean(
key: String,
value: Boolean,
): SharedPreferences.Editor {
override fun putBoolean(key: String, value: Boolean): SharedPreferences.Editor {
actions += Action.Add(key, value)
return this
}
@@ -231,17 +148,14 @@ class JavaSharedPreferences(key: String) : SharedPreferences {
actions.forEach {
@Suppress("UNCHECKED_CAST")
when (it) {
is Action.Add -> {
when (val value = it.value) {
is Set<*> -> preferences.encodeValue(SetSerializer(String.serializer()), it.key, value as Set<String>)
is String -> preferences.putString(it.key, value)
is Int -> preferences.putInt(it.key, value)
is Long -> preferences.putLong(it.key, value)
is Float -> preferences.putFloat(it.key, value)
is Double -> preferences.putDouble(it.key, value)
is Boolean -> preferences.putBoolean(it.key, value)
}
notify(it.key)
is Action.Add -> when (val value = it.value) {
is Set<*> -> preferences.encodeValue(SetSerializer(String.serializer()), it.key, value as Set<String>)
is String -> preferences.putString(it.key, value)
is Int -> preferences.putInt(it.key, value)
is Long -> preferences.putLong(it.key, value)
is Float -> preferences.putFloat(it.key, value)
is Double -> preferences.putDouble(it.key, value)
is Boolean -> preferences.putBoolean(it.key, value)
}
is Action.Remove -> {
preferences.remove(it.key)
@@ -256,8 +170,6 @@ class JavaSharedPreferences(key: String) : SharedPreferences {
preferences.remove(key)
}
}
notify(it.key)
}
Action.Clear -> preferences.clear()
}
@@ -266,18 +178,22 @@ class JavaSharedPreferences(key: String) : SharedPreferences {
}
override fun registerOnSharedPreferenceChangeListener(listener: SharedPreferences.OnSharedPreferenceChangeListener) {
val javaListener: (String) -> Unit = {
listener.onSharedPreferenceChanged(this, it)
val javaListener = PreferenceChangeListener {
listener.onSharedPreferenceChanged(this, it.key)
}
listeners[listener] = javaListener
javaPreferences.addPreferenceChangeListener(javaListener)
}
override fun unregisterOnSharedPreferenceChangeListener(listener: SharedPreferences.OnSharedPreferenceChangeListener) {
listeners.remove(listener)
val registeredListener = listeners.remove(listener)
if (registeredListener != null) {
javaPreferences.removePreferenceChangeListener(registeredListener)
}
}
fun deleteAll(): Boolean {
preferences.clear()
javaPreferences.removeNode()
return true
}
}

View File

@@ -20,47 +20,42 @@ data class InstalledPackage(val root: File) {
val icon = File(root, "icon.png")
val info: PackageInfo
get() =
ApkParsers.getMetaInfo(apk).toPackageInfo(apk).also {
val parsed = ApkFile(apk)
val dbFactory = DocumentBuilderFactory.newInstance()
val dBuilder = dbFactory.newDocumentBuilder()
val doc =
parsed.manifestXml.byteInputStream().use {
dBuilder.parse(it)
}
it.applicationInfo.metaData =
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,
)
}
}
it.signatures =
(
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()
get() = ApkParsers.getMetaInfo(apk).toPackageInfo(apk).also {
val parsed = ApkFile(apk)
val dbFactory = DocumentBuilderFactory.newInstance()
val dBuilder = dbFactory.newDocumentBuilder()
val doc = parsed.manifestXml.byteInputStream().use {
dBuilder.parse(it)
}
it.applicationInfo.metaData = 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
)
}
}
it.signatures = (
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()
}
fun verify(): Boolean {
val res =
ApkVerifier.Builder(apk)
.build()
.verify()
val res = ApkVerifier.Builder(apk)
.build()
.verify()
return res.isVerified
}
@@ -69,12 +64,11 @@ data class InstalledPackage(val root: File) {
try {
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
val read = 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) {

View File

@@ -25,10 +25,7 @@ class PackageController {
return File(androidFiles.packagesDir, pn)
}
fun installPackage(
apk: File,
allowReinstall: Boolean,
) {
fun installPackage(apk: File, allowReinstall: Boolean) {
val root = findRoot(apk)
if (root.exists()) {

View File

@@ -12,18 +12,16 @@ fun ApkMeta.toPackageInfo(apk: File): PackageInfo {
it.versionCode = versionCode.toInt()
it.versionName = versionName
it.reqFeatures =
usesFeatures.map {
FeatureInfo().apply {
name = it.name
}
}.toTypedArray()
it.applicationInfo =
ApplicationInfo().apply {
packageName = it.packageName
nonLocalizedLabel = label
sourceDir = apk.absolutePath
it.reqFeatures = usesFeatures.map {
FeatureInfo().apply {
name = it.name
}
}.toTypedArray()
it.applicationInfo = ApplicationInfo().apply {
packageName = it.packageName
nonLocalizedLabel = label
sourceDir = apk.absolutePath
}
}
}

View File

@@ -18,10 +18,7 @@ class ServiceSupport {
private val logger = KotlinLogging.logger {}
fun startService(
@Suppress("UNUSED_PARAMETER") context: Context,
intent: Intent,
) {
fun startService(@Suppress("UNUSED_PARAMETER") context: Context, intent: Intent) {
val name = intentToClassName(intent)
logger.debug { "Starting service: $name" }
@@ -38,10 +35,7 @@ class ServiceSupport {
}
}
fun stopService(
@Suppress("UNUSED_PARAMETER") context: Context,
intent: Intent,
) {
fun stopService(@Suppress("UNUSED_PARAMETER") context: Context, intent: Intent) {
val name = intentToClassName(intent)
stopService(name)
}

View File

@@ -26,10 +26,7 @@ object KodeinGlobalHelper {
*/
@JvmStatic
@Suppress("UNCHECKED_CAST")
fun <T : Any> instance(
type: Class<T>,
kodein: DI? = null,
): T {
fun <T : Any> instance(type: Class<T>, kodein: DI? = null): T {
return when (type) {
AndroidFiles::class.java -> {
val instance: AndroidFiles by (kodein ?: kodein()).instance()

View File

@@ -24,7 +24,5 @@ import java.net.URI
* Utilites to convert between Java and Android Uris.
*/
fun Uri.java() = URI(this.toString())
fun Uri.file() = File(this.path)
fun URI.android() = Uri.parse(this.toString())!!

View File

@@ -1,103 +0,0 @@
package xyz.nulldev.androidcompat.webkit
import android.webkit.CookieManager
import android.webkit.ValueCallback
import android.webkit.WebView
import java.net.CookieHandler
import java.net.HttpCookie
import java.net.URI
@Suppress("DEPRECATION")
class CookieManagerImpl : CookieManager() {
private val cookieHandler = CookieHandler.getDefault() as java.net.CookieManager
private var acceptCookie = true
private var acceptThirdPartyCookies = true
private var allowFileSchemeCookies = false
override fun setAcceptCookie(accept: Boolean) {
acceptCookie = accept
}
override fun acceptCookie(): Boolean {
return acceptCookie
}
override fun setAcceptThirdPartyCookies(
webview: WebView?,
accept: Boolean,
) {
acceptThirdPartyCookies = accept
}
override fun acceptThirdPartyCookies(webview: WebView?): Boolean {
return acceptThirdPartyCookies
}
override fun setCookie(
url: String,
value: String?,
) {
val uri =
if (url.startsWith("http")) {
URI(url)
} else {
URI("http://$url")
}
HttpCookie.parse(value).forEach {
cookieHandler.cookieStore.add(uri, it)
}
}
override fun setCookie(
url: String,
value: String?,
callback: ValueCallback<Boolean>?,
) {
setCookie(url, value)
callback?.onReceiveValue(true)
}
override fun getCookie(url: String): String {
val uri =
if (url.startsWith("http")) {
URI(url)
} else {
URI("http://$url")
}
return cookieHandler.cookieStore.get(uri)
.joinToString("; ") { "${it.name}=${it.value}" }
}
@Deprecated("Deprecated in Java")
override fun removeSessionCookie() {}
override fun removeSessionCookies(callback: ValueCallback<Boolean>?) {}
@Deprecated("Deprecated in Java")
override fun removeExpiredCookie() {}
@Deprecated("Deprecated in Java")
override fun removeAllCookie() {
cookieHandler.cookieStore.removeAll()
}
override fun removeAllCookies(callback: ValueCallback<Boolean>?) {
val removedCookies = cookieHandler.cookieStore.removeAll()
callback?.onReceiveValue(removedCookies)
}
override fun hasCookies(): Boolean {
return cookieHandler.cookieStore.cookies.isNotEmpty()
}
override fun flush() {}
override fun allowFileSchemeCookiesImpl(): Boolean {
return allowFileSchemeCookies
}
override fun setAcceptFileSchemeCookiesImpl(accept: Boolean) {
allowFileSchemeCookies = acceptCookie
}
}

View File

@@ -2,10 +2,10 @@
## TL;DR
- N/A
## Suwayomi-Server Changelog
## Tachidesk-Server Changelog
- N/A
## Suwayomi-WebUI Changelog
## Tachidesk-WebUI Changelog
- N/A

View File

@@ -7,67 +7,67 @@
- WebUI changes:
- Uhh, idk, find out yourself...
## Suwayomi-Server Changelog
## Tachidesk-Server Changelog
- (r1159) v0.6.6 (by @AriaMoradi)
- (r1160) add Chagelog TL;DR (by @AriaMoradi)
- (r1161) fix Changelog typos (by @AriaMoradi)
- (r1162) WebView based cloudflare interceptor ([#456](https://github.com/Suwayomi/Suwayomi-Server/pull/456) by @AriaMoradi)
- (r1162) WebView based cloudflare interceptor ([#456](https://github.com/Suwayomi/Tachidesk-Server/pull/456) by @AriaMoradi)
- (r1163) update issue mod (by @AriaMoradi)
- (r1164) better description (by @AriaMoradi)
- (r1165) fix regex (by @AriaMoradi)
- (r1166) get default User Agent from WebView ([#457](https://github.com/Suwayomi/Suwayomi-Server/pull/457) by @AriaMoradi)
- (r1167) implementation of android.graphics.BitmapFactory ([#460](https://github.com/Suwayomi/Suwayomi-Server/pull/460) by @animeavi)
- (r1168) Basic android.graphics Rect and Canvas implementation ([#461](https://github.com/Suwayomi/Suwayomi-Server/pull/461) by @animeavi)
- (r1169) Get Playwright working ([#462](https://github.com/Suwayomi/Suwayomi-Server/pull/462) by @Syer10)
- (r1166) get default User Agent from WebView ([#457](https://github.com/Suwayomi/Tachidesk-Server/pull/457) by @AriaMoradi)
- (r1167) implementation of android.graphics.BitmapFactory ([#460](https://github.com/Suwayomi/Tachidesk-Server/pull/460) by @animeavi)
- (r1168) Basic android.graphics Rect and Canvas implementation ([#461](https://github.com/Suwayomi/Tachidesk-Server/pull/461) by @animeavi)
- (r1169) Get Playwright working ([#462](https://github.com/Suwayomi/Tachidesk-Server/pull/462) by @Syer10)
- (r1170) disable deb release (by @AriaMoradi)
- (r1171) Fix debian release ([#463](https://github.com/Suwayomi/Suwayomi-Server/pull/463) by @mahor1221)
- (r1172) Add better manga thumbnail handling ([#465](https://github.com/Suwayomi/Suwayomi-Server/pull/465) by @Syer10)
- (r1173) Use extension list fallback if extensions fail to fetch ([#469](https://github.com/Suwayomi/Suwayomi-Server/pull/469) by @Syer10)
- (r1171) Fix debian release ([#463](https://github.com/Suwayomi/Tachidesk-Server/pull/463) by @mahor1221)
- (r1172) Add better manga thumbnail handling ([#465](https://github.com/Suwayomi/Tachidesk-Server/pull/465) by @Syer10)
- (r1173) Use extension list fallback if extensions fail to fetch ([#469](https://github.com/Suwayomi/Tachidesk-Server/pull/469) by @Syer10)
- (r1174) fix when playwright fails on providing a UA (by @AriaMoradi)
- (r1175) Update CategoryMetaTable.kt (by @AriaMoradi)
- (r1176) fix CategoryMetaTable reference to CategoryTable ([#473](https://github.com/Suwayomi/Suwayomi-Server/pull/473) by @AriaMoradi)
- (r1176) fix CategoryMetaTable reference to CategoryTable ([#473](https://github.com/Suwayomi/Tachidesk-Server/pull/473) by @AriaMoradi)
- (r1177) remove possibly misleading sentence (by @AriaMoradi)
- (r1178) Clarify and Update (by @AriaMoradi)
- (r1179) Clarify and Update (by @AriaMoradi)
- (r1180) link to Tachiyomi section (by @AriaMoradi)
- (r1181) fix typo (by @AriaMoradi)
- (r1182) Improve Gradle Configuration ([#478](https://github.com/Suwayomi/Suwayomi-Server/pull/478) by @Syer10)
- (r1183) Improve Playwright handling ([#479](https://github.com/Suwayomi/Suwayomi-Server/pull/479) by @Syer10)
- (r1182) Improve Gradle Configuration ([#478](https://github.com/Suwayomi/Tachidesk-Server/pull/478) by @Syer10)
- (r1183) Improve Playwright handling ([#479](https://github.com/Suwayomi/Tachidesk-Server/pull/479) by @Syer10)
- (r1184) fix ambiguous reference issue on JDK 13+ (by @AriaMoradi)
- (r1185) update gradle version (by @AriaMoradi)
- (r1186) upgrade dorkbox stuff (by @AriaMoradi)
- (r1187) Fixe Dex2Jar and dorkbox dependency issues ([#487](https://github.com/Suwayomi/Suwayomi-Server/pull/487) by @akabhirav)
- (r1188) Fix logging and update system try ([#488](https://github.com/Suwayomi/Suwayomi-Server/pull/488) by @Syer10)
- (r1189) add support for Extensions Lib 1.4 ([#496](https://github.com/Suwayomi/Suwayomi-Server/pull/496) by @Syer10)
- (r1187) Fixe Dex2Jar and dorkbox dependency issues ([#487](https://github.com/Suwayomi/Tachidesk-Server/pull/487) by @akabhirav)
- (r1188) Fix logging and update system try ([#488](https://github.com/Suwayomi/Tachidesk-Server/pull/488) by @Syer10)
- (r1189) add support for Extensions Lib 1.4 ([#496](https://github.com/Suwayomi/Tachidesk-Server/pull/496) by @Syer10)
- (r1190) disable playwright for v0.6.7 (by @AriaMoradi)
- (r1191) Decouple Cache and Download behaviour ([#493](https://github.com/Suwayomi/Suwayomi-Server/pull/493) by @akabhirav)
- (r1192) rethink image cache ([#498](https://github.com/Suwayomi/Suwayomi-Server/pull/498) by @AriaMoradi)
- (r1193) fix Page index issues for some providers ([#491](https://github.com/Suwayomi/Suwayomi-Server/pull/491) by @akabhirav)
- (r1194) Download as CBZ ([#490](https://github.com/Suwayomi/Suwayomi-Server/pull/490) by @akabhirav)
- (r1191) Decouple Cache and Download behaviour ([#493](https://github.com/Suwayomi/Tachidesk-Server/pull/493) by @akabhirav)
- (r1192) rethink image cache ([#498](https://github.com/Suwayomi/Tachidesk-Server/pull/498) by @AriaMoradi)
- (r1193) fix Page index issues for some providers ([#491](https://github.com/Suwayomi/Tachidesk-Server/pull/491) by @akabhirav)
- (r1194) Download as CBZ ([#490](https://github.com/Suwayomi/Tachidesk-Server/pull/490) by @akabhirav)
- (r1195) re-order config options (by @AriaMoradi)
- (r1196) stop using depricated API (by @AriaMoradi)
## Suwayomi-WebUI Changelog
- (r964) Created a GridLayout enum and updated all locations to use it. ([#208](https://github.com/Suwayomi/Suwayomi-WebUI/pull/208) by @infix)
- (r965) fix library update progress rendering ([#210](https://github.com/Suwayomi/Suwayomi-WebUI/pull/210) by @schroda)
- (r966) Save reader settings per manga in Meta ([#216](https://github.com/Suwayomi/Suwayomi-WebUI/pull/216) by @schroda)
- (r967) make default reader settings changeable ([#217](https://github.com/Suwayomi/Suwayomi-WebUI/pull/217) by @schroda)
- (r968) [#211] Refresh Library after a update ([#212](https://github.com/Suwayomi/Suwayomi-WebUI/pull/212) by @schroda)
- (r969) add logic for metadata migration ([#218](https://github.com/Suwayomi/Suwayomi-WebUI/pull/218) by @schroda)
- (r970) set browser tab title ([#220](https://github.com/Suwayomi/Suwayomi-WebUI/pull/220) by @schroda)
- (r971) Add tooltip containing full manga title to title of manga ([#221](https://github.com/Suwayomi/Suwayomi-WebUI/pull/221) by @schroda)
- (r972) show more detailed upload dates for today and yesterday ([#222](https://github.com/Suwayomi/Suwayomi-WebUI/pull/222) by @schroda)
- (r973) add GitHub action on pushing to run lint ([#224](https://github.com/Suwayomi/Suwayomi-WebUI/pull/224) by @schroda)
- (r974) Ignore filters while searching ([#226](https://github.com/Suwayomi/Suwayomi-WebUI/pull/226) by @schroda)
- (r975) force absolute import path ([#223](https://github.com/Suwayomi/Suwayomi-WebUI/pull/223) by @schroda)
- (r976) add prettier for auto formatting ([#231](https://github.com/Suwayomi/Suwayomi-WebUI/pull/231) by @schroda)
- (r977) Fix import path ([#228](https://github.com/Suwayomi/Suwayomi-WebUI/pull/228) by @schroda)
- (r978) increase prettier line length to 120 ([#233](https://github.com/Suwayomi/Suwayomi-WebUI/pull/233) by @schroda)
- (r979) Add chapter page dropdown ([#230](https://github.com/Suwayomi/Suwayomi-WebUI/pull/230) by @schroda)
- (r980) add chapter dropdown to reader nav bar ([#229](https://github.com/Suwayomi/Suwayomi-WebUI/pull/229) by @schroda)
- (r981) Fix lint error ([#235](https://github.com/Suwayomi/Suwayomi-WebUI/pull/235) by @schroda)
- (r982) Fix reader nav bar scroll to page ([#236](https://github.com/Suwayomi/Suwayomi-WebUI/pull/236) by @schroda)
- (r964) Created a GridLayout enum and updated all locations to use it. ([#208](https://github.com/Suwayomi/Suwayomi-WebUI/pull/208) by @infix)
## Tachidesk-WebUI Changelog
- (r964) Created a GridLayout enum and updated all locations to use it. ([#208](https://github.com/Suwayomi/Tachidesk-WebUI/pull/208) by @infix)
- (r965) fix library update progress rendering ([#210](https://github.com/Suwayomi/Tachidesk-WebUI/pull/210) by @schroda)
- (r966) Save reader settings per manga in Meta ([#216](https://github.com/Suwayomi/Tachidesk-WebUI/pull/216) by @schroda)
- (r967) make default reader settings changeable ([#217](https://github.com/Suwayomi/Tachidesk-WebUI/pull/217) by @schroda)
- (r968) [#211] Refresh Library after a update ([#212](https://github.com/Suwayomi/Tachidesk-WebUI/pull/212) by @schroda)
- (r969) add logic for metadata migration ([#218](https://github.com/Suwayomi/Tachidesk-WebUI/pull/218) by @schroda)
- (r970) set browser tab title ([#220](https://github.com/Suwayomi/Tachidesk-WebUI/pull/220) by @schroda)
- (r971) Add tooltip containing full manga title to title of manga ([#221](https://github.com/Suwayomi/Tachidesk-WebUI/pull/221) by @schroda)
- (r972) show more detailed upload dates for today and yesterday ([#222](https://github.com/Suwayomi/Tachidesk-WebUI/pull/222) by @schroda)
- (r973) add GitHub action on pushing to run lint ([#224](https://github.com/Suwayomi/Tachidesk-WebUI/pull/224) by @schroda)
- (r974) Ignore filters while searching ([#226](https://github.com/Suwayomi/Tachidesk-WebUI/pull/226) by @schroda)
- (r975) force absolute import path ([#223](https://github.com/Suwayomi/Tachidesk-WebUI/pull/223) by @schroda)
- (r976) add prettier for auto formatting ([#231](https://github.com/Suwayomi/Tachidesk-WebUI/pull/231) by @schroda)
- (r977) Fix import path ([#228](https://github.com/Suwayomi/Tachidesk-WebUI/pull/228) by @schroda)
- (r978) increase prettier line length to 120 ([#233](https://github.com/Suwayomi/Tachidesk-WebUI/pull/233) by @schroda)
- (r979) Add chapter page dropdown ([#230](https://github.com/Suwayomi/Tachidesk-WebUI/pull/230) by @schroda)
- (r980) add chapter dropdown to reader nav bar ([#229](https://github.com/Suwayomi/Tachidesk-WebUI/pull/229) by @schroda)
- (r981) Fix lint error ([#235](https://github.com/Suwayomi/Tachidesk-WebUI/pull/235) by @schroda)
- (r982) Fix reader nav bar scroll to page ([#236](https://github.com/Suwayomi/Tachidesk-WebUI/pull/236) by @schroda)
- (r964) Created a GridLayout enum and updated all locations to use it. ([#208](https://github.com/Suwayomi/Tachidesk-WebUI/pull/208) by @infix)
@@ -80,71 +80,71 @@
- a lot of code cleanup
- some bugfixes
## Suwayomi-Server Changelog
## Tachidesk-Server Changelog
- (r1114) fix broken links (by @AriaMoradi)
- (r1115) fix more broken stuff (by @AriaMoradi)
- (r1116) fix more broken stuff (by @AriaMoradi)
- (r1117) fix more broken stuff (by @AriaMoradi)
- (r1118) Update winget.yml ([#393](https://github.com/Suwayomi/Suwayomi-Server/pull/393) by @vedantmgoyal2009)
- (r1119) fix jre path([#396](https://github.com/Suwayomi/Suwayomi-Server/pull/396) by @vedantmgoyal2009)
- (r1120) Fix deb package ([#397](https://github.com/Suwayomi/Suwayomi-Server/pull/397) by @mahor1221)
- (r1118) Update winget.yml ([#393](https://github.com/Suwayomi/Tachidesk-Server/pull/393) by @vedantmgoyal2009)
- (r1119) fix jre path([#396](https://github.com/Suwayomi/Tachidesk-Server/pull/396) by @vedantmgoyal2009)
- (r1120) Fix deb package ([#397](https://github.com/Suwayomi/Tachidesk-Server/pull/397) by @mahor1221)
- (r1121) bump version (by @AriaMoradi)
- (r1122) Update Changelog (by @AriaMoradi)
- (r1123) Add libc++-dev ([#405](https://github.com/Suwayomi/Suwayomi-Server/pull/405) by @mahor1221)
- (r1124) Revert back to correct way of handling jre_dir ([#408](https://github.com/Suwayomi/Suwayomi-Server/pull/408) by @mahor1221)
- (r1125) Update winget.yml ([#410](https://github.com/Suwayomi/Suwayomi-Server/pull/410) by @vedantmgoyal2009)
- (r1126) Remove support for Sorayomi web interface ([#414](https://github.com/Suwayomi/Suwayomi-Server/pull/414) by @marcoebbinghaus)
- (r1127) Fix downloader memory leak ([#418](https://github.com/Suwayomi/Suwayomi-Server/pull/418) by @Syer10)
- (r1128) Documentation cleanup ([#417](https://github.com/Suwayomi/Suwayomi-Server/pull/417) by @Syer10)
- (r1129) Updater cleanup and improvements ([#416](https://github.com/Suwayomi/Suwayomi-Server/pull/416) by @Syer10)
- (r1130) replace quickjs with Mozilla Rhino ([#415](https://github.com/Suwayomi/Suwayomi-Server/pull/415) by @xhzhe)
- (r1123) Add libc++-dev ([#405](https://github.com/Suwayomi/Tachidesk-Server/pull/405) by @mahor1221)
- (r1124) Revert back to correct way of handling jre_dir ([#408](https://github.com/Suwayomi/Tachidesk-Server/pull/408) by @mahor1221)
- (r1125) Update winget.yml ([#410](https://github.com/Suwayomi/Tachidesk-Server/pull/410) by @vedantmgoyal2009)
- (r1126) Remove support for Sorayomi web interface ([#414](https://github.com/Suwayomi/Tachidesk-Server/pull/414) by @marcoebbinghaus)
- (r1127) Fix downloader memory leak ([#418](https://github.com/Suwayomi/Tachidesk-Server/pull/418) by @Syer10)
- (r1128) Documentation cleanup ([#417](https://github.com/Suwayomi/Tachidesk-Server/pull/417) by @Syer10)
- (r1129) Updater cleanup and improvements ([#416](https://github.com/Suwayomi/Tachidesk-Server/pull/416) by @Syer10)
- (r1130) replace quickjs with Mozilla Rhino ([#415](https://github.com/Suwayomi/Tachidesk-Server/pull/415) by @xhzhe)
- (r1131) ktlint (by @AriaMoradi)
- (r1132) move Tachiyomi's BuildConfig to kotlin dir (by @AriaMoradi)
- (r1133) remove BuildConfig as extensions now use AppInfo (by @AriaMoradi)
- (r1134) include list of mangas missing source in restore report ([#421](https://github.com/Suwayomi/Suwayomi-Server/pull/421) by @AriaMoradi)
- (r1135) Update dependencies ([#422](https://github.com/Suwayomi/Suwayomi-Server/pull/422) by @Syer10)
- (r1136) Lint ([#423](https://github.com/Suwayomi/Suwayomi-Server/pull/423) by @Syer10)
- (r1137) Fix: Error handling for popular/latest api if pageNum was supplied as zero ([#424](https://github.com/Suwayomi/Suwayomi-Server/pull/424) by @meta-boy)
- (r1138) Add cache control header to manga page response ([#430](https://github.com/Suwayomi/Suwayomi-Server/pull/430) by @martinek)
- (r1139) add MangaTable.lastFetchedAt and ChapterTable.chaptersLastFetchedAt ([#431](https://github.com/Suwayomi/Suwayomi-Server/pull/431) by @martinek)
- (r1140) Pre-load meta entries for all chapters for optimization ([#432](https://github.com/Suwayomi/Suwayomi-Server/pull/432) by @martinek)
- (r1141) POST variant for `/{sourceId}/search` endpoint ([#434](https://github.com/Suwayomi/Suwayomi-Server/pull/434) by @martinek)
- (r1142) Add request body to documentation ([#435](https://github.com/Suwayomi/Suwayomi-Server/pull/435) by @Syer10)
- (r1143) add batch download api ([#436](https://github.com/Suwayomi/Suwayomi-Server/pull/436) by @martinek)
- (r1134) include list of mangas missing source in restore report ([#421](https://github.com/Suwayomi/Tachidesk-Server/pull/421) by @AriaMoradi)
- (r1135) Update dependencies ([#422](https://github.com/Suwayomi/Tachidesk-Server/pull/422) by @Syer10)
- (r1136) Lint ([#423](https://github.com/Suwayomi/Tachidesk-Server/pull/423) by @Syer10)
- (r1137) Fix: Error handling for popular/latest api if pageNum was supplied as zero ([#424](https://github.com/Suwayomi/Tachidesk-Server/pull/424) by @meta-boy)
- (r1138) Add cache control header to manga page response ([#430](https://github.com/Suwayomi/Tachidesk-Server/pull/430) by @martinek)
- (r1139) add MangaTable.lastFetchedAt and ChapterTable.chaptersLastFetchedAt ([#431](https://github.com/Suwayomi/Tachidesk-Server/pull/431) by @martinek)
- (r1140) Pre-load meta entries for all chapters for optimization ([#432](https://github.com/Suwayomi/Tachidesk-Server/pull/432) by @martinek)
- (r1141) POST variant for `/{sourceId}/search` endpoint ([#434](https://github.com/Suwayomi/Tachidesk-Server/pull/434) by @martinek)
- (r1142) Add request body to documentation ([#435](https://github.com/Suwayomi/Tachidesk-Server/pull/435) by @Syer10)
- (r1143) add batch download api ([#436](https://github.com/Suwayomi/Tachidesk-Server/pull/436) by @martinek)
- (r1144) Migrate to H2 v2 (by @AriaMoradi)
- (r1145) add category and global meta ([#438](https://github.com/Suwayomi/Suwayomi-Server/pull/438) by @AriaMoradi)
- (r1145) add category and global meta ([#438](https://github.com/Suwayomi/Tachidesk-Server/pull/438) by @AriaMoradi)
- (r1146) Revert H2 database to v1 (by @AriaMoradi)
- (r1147) refactor deprecated api (by @AriaMoradi)
- (r1148) Downloader Rewrite ([#437](https://github.com/Suwayomi/Suwayomi-Server/pull/437) by @Syer10)
- (r1149) Set source preference doc fix ([#441](https://github.com/Suwayomi/Suwayomi-Server/pull/441) by @Syer10)
- (r1150) Add batch chapter update endpoint ([#442](https://github.com/Suwayomi/Suwayomi-Server/pull/442) by @martinek)
- (r1148) Downloader Rewrite ([#437](https://github.com/Suwayomi/Tachidesk-Server/pull/437) by @Syer10)
- (r1149) Set source preference doc fix ([#441](https://github.com/Suwayomi/Tachidesk-Server/pull/441) by @Syer10)
- (r1150) Add batch chapter update endpoint ([#442](https://github.com/Suwayomi/Tachidesk-Server/pull/442) by @martinek)
- (r1151) changes needed for tachiyomi tracker (by @AriaMoradi)
- (r1152) Future proofing (by @AriaMoradi)
- (r1153) Fix settings/check-update endpoint ([#445](https://github.com/Suwayomi/Suwayomi-Server/pull/445) by @martinek)
- (r1154) Fix docs for /server/check-updates ([#447](https://github.com/Suwayomi/Suwayomi-Server/pull/447) by @martinek)
- (r1155) Batch editing and deleting any chapter ([#449](https://github.com/Suwayomi/Suwayomi-Server/pull/449) by @martinek)
- (r1153) Fix settings/check-update endpoint ([#445](https://github.com/Suwayomi/Tachidesk-Server/pull/445) by @martinek)
- (r1154) Fix docs for /server/check-updates ([#447](https://github.com/Suwayomi/Tachidesk-Server/pull/447) by @martinek)
- (r1155) Batch editing and deleting any chapter ([#449](https://github.com/Suwayomi/Tachidesk-Server/pull/449) by @martinek)
- (r1156) make chapters endpoint more unifrom (by @AriaMoradi)
- (r1157) Add batch endpoint for removing downloads from download queue ([#452](https://github.com/Suwayomi/Suwayomi-Server/pull/452) by @martinek)
- (r1158) Download queue missing update fix ([#450](https://github.com/Suwayomi/Suwayomi-Server/pull/450) by @martinek)
- (r1157) Add batch endpoint for removing downloads from download queue ([#452](https://github.com/Suwayomi/Tachidesk-Server/pull/452) by @martinek)
- (r1158) Download queue missing update fix ([#450](https://github.com/Suwayomi/Tachidesk-Server/pull/450) by @martinek)
## Suwayomi-WebUI Changelog
- (r947) Feature/swr for library screens ([#186](https://github.com/Suwayomi/Suwayomi-WebUI/pull/186) by @martinek)
- (r948) Feature/swr for simple queries ([#187](https://github.com/Suwayomi/Suwayomi-WebUI/pull/187) by @martinek)
- (r949) Check download queue for changes and reload chapters if any chapter download changes state. ([#189](https://github.com/Suwayomi/Suwayomi-WebUI/pull/189) by @martinek)
- (r950) Update typescript dependency ([#190](https://github.com/Suwayomi/Suwayomi-WebUI/pull/190) by @martinek)
## Tachidesk-WebUI Changelog
- (r947) Feature/swr for library screens ([#186](https://github.com/Suwayomi/Tachidesk-WebUI/pull/186) by @martinek)
- (r948) Feature/swr for simple queries ([#187](https://github.com/Suwayomi/Tachidesk-WebUI/pull/187) by @martinek)
- (r949) Check download queue for changes and reload chapters if any chapter download changes state. ([#189](https://github.com/Suwayomi/Tachidesk-WebUI/pull/189) by @martinek)
- (r950) Update typescript dependency ([#190](https://github.com/Suwayomi/Tachidesk-WebUI/pull/190) by @martinek)
- (r951) update browserlist (by @AriaMoradi)
- (r952) Feature/batch chapter download ([#191](https://github.com/Suwayomi/Suwayomi-WebUI/pull/191) by @martinek)
- (r953) Memoize empty view face so it does not change on rerender ([#193](https://github.com/Suwayomi/Suwayomi-WebUI/pull/193) by @martinek)
- (r954) Feature/batch chapter actions ([#194](https://github.com/Suwayomi/Suwayomi-WebUI/pull/194) by @martinek)
- (r955) Fix navbar back button behavior ([#195](https://github.com/Suwayomi/Suwayomi-WebUI/pull/195) by @martinek)
- (r956) Options panels refactoring ([#196](https://github.com/Suwayomi/Suwayomi-WebUI/pull/196) by @martinek)
- (r957) Refactor and fix sorting in library ([#197](https://github.com/Suwayomi/Suwayomi-WebUI/pull/197) by @martinek)
- (r958) Scroll window to top when PagedPager changes page ([#198](https://github.com/Suwayomi/Suwayomi-WebUI/pull/198) by @martinek)
- (r959) Verticall scroll navigation and fix ([#200](https://github.com/Suwayomi/Suwayomi-WebUI/pull/200) by @martinek)
- (r960) Hide overflowing text in reader title if text can't be wrapped ([#199](https://github.com/Suwayomi/Suwayomi-WebUI/pull/199) by @martinek)
- (r961) Add safezone to scroll end detection to prevent edge cases when scrolling to the end would not detect end ([#201](https://github.com/Suwayomi/Suwayomi-WebUI/pull/201) by @martinek)
- (r962) Refactor/download queue and cleanup visuals overall ([#202](https://github.com/Suwayomi/Suwayomi-WebUI/pull/202) by @martinek)
- (r963) Fix "back" pagination on double page layout in reader for spread pages ([#203](https://github.com/Suwayomi/Suwayomi-WebUI/pull/203) by @martinek)
- (r952) Feature/batch chapter download ([#191](https://github.com/Suwayomi/Tachidesk-WebUI/pull/191) by @martinek)
- (r953) Memoize empty view face so it does not change on rerender ([#193](https://github.com/Suwayomi/Tachidesk-WebUI/pull/193) by @martinek)
- (r954) Feature/batch chapter actions ([#194](https://github.com/Suwayomi/Tachidesk-WebUI/pull/194) by @martinek)
- (r955) Fix navbar back button behavior ([#195](https://github.com/Suwayomi/Tachidesk-WebUI/pull/195) by @martinek)
- (r956) Options panels refactoring ([#196](https://github.com/Suwayomi/Tachidesk-WebUI/pull/196) by @martinek)
- (r957) Refactor and fix sorting in library ([#197](https://github.com/Suwayomi/Tachidesk-WebUI/pull/197) by @martinek)
- (r958) Scroll window to top when PagedPager changes page ([#198](https://github.com/Suwayomi/Tachidesk-WebUI/pull/198) by @martinek)
- (r959) Verticall scroll navigation and fix ([#200](https://github.com/Suwayomi/Tachidesk-WebUI/pull/200) by @martinek)
- (r960) Hide overflowing text in reader title if text can't be wrapped ([#199](https://github.com/Suwayomi/Tachidesk-WebUI/pull/199) by @martinek)
- (r961) Add safezone to scroll end detection to prevent edge cases when scrolling to the end would not detect end ([#201](https://github.com/Suwayomi/Tachidesk-WebUI/pull/201) by @martinek)
- (r962) Refactor/download queue and cleanup visuals overall ([#202](https://github.com/Suwayomi/Tachidesk-WebUI/pull/202) by @martinek)
- (r963) Fix "back" pagination on double page layout in reader for spread pages ([#203](https://github.com/Suwayomi/Tachidesk-WebUI/pull/203) by @martinek)
@@ -152,18 +152,18 @@
## TL;DR
- Fixed Windows bundler
## Suwayomi-Server Changelog
## Tachidesk-Server Changelog
- (r1113) v0.6.4 (by @AriaMoradi)
- (r1114) fix broken links (by @AriaMoradi)
- (r1115) fix more broken stuff (by @AriaMoradi)
- (r1116) fix more broken stuff (by @AriaMoradi)
- (r1117) fix more broken stuff (by @AriaMoradi)
- (r1118) Update winget.yml ([#393](https://github.com/Suwayomi/Suwayomi-Server/pull/393) by @vedantmgoyal2009)
- (r1119) fix jre path([#396](https://github.com/Suwayomi/Suwayomi-Server/pull/396) by @voltrare)
- (r1120) Fix deb package ([#397](https://github.com/Suwayomi/Suwayomi-Server/pull/397) by @mahor1221)
- (r1118) Update winget.yml ([#393](https://github.com/Suwayomi/Tachidesk-Server/pull/393) by @vedantmgoyal2009)
- (r1119) fix jre path([#396](https://github.com/Suwayomi/Tachidesk-Server/pull/396) by @voltrare)
- (r1120) Fix deb package ([#397](https://github.com/Suwayomi/Tachidesk-Server/pull/397) by @mahor1221)
- (r1121) bump version (by @AriaMoradi)
## Suwayomi-WebUI Changelog
## Tachidesk-WebUI Changelog
- None
@@ -174,39 +174,39 @@
- Bug fixes and changes for packaging
- Documentation changes
## Suwayomi-Server Changelog
## Tachidesk-Server Changelog
- (r1087) v0.6.3 (by @AriaMoradi)
- (r1088) Save categories when manga is unfavorited ([#335](https://github.com/Suwayomi/Suwayomi-Server/pull/335) by @Syer10)
- (r1089) handle solid RAR archives ([#339](https://github.com/Suwayomi/Suwayomi-Server/pull/339)) cfso100@gmail.com
- (r1090) add support for changing downloads dir ([#343](https://github.com/Suwayomi/Suwayomi-Server/pull/343) by @AriaMoradi)
- (r1091) fix Applications dir dependency ([#344](https://github.com/Suwayomi/Suwayomi-Server/pull/344) by @AriaMoradi)
- (r1092) add support for alternative web interfaces ([#342](https://github.com/Suwayomi/Suwayomi-Server/pull/342) by @AriaMoradi)
- (r1093) Add displayValues json field for select filter ([#347](https://github.com/Suwayomi/Suwayomi-Server/pull/347) by @Syer10)
- (r1094) document manga endpoints ([#348](https://github.com/Suwayomi/Suwayomi-Server/pull/348) by @Syer10)
- (r1095) add ChapterCount to manga object in categoryMangas endpoint ([#349](https://github.com/Suwayomi/Suwayomi-Server/pull/349) by @abhijeetChawla)
- (r1096) document all endpoints ([#350](https://github.com/Suwayomi/Suwayomi-Server/pull/350) by @Syer10)
- (r1097) fix copymanga ([#354](https://github.com/Suwayomi/Suwayomi-Server/pull/354) by @AriaMoradi)
- (r1088) Save categories when manga is unfavorited ([#335](https://github.com/Suwayomi/Tachidesk-Server/pull/335) by @Syer10)
- (r1089) handle solid RAR archives ([#339](https://github.com/Suwayomi/Tachidesk-Server/pull/339)) cfso100@gmail.com
- (r1090) add support for changing downloads dir ([#343](https://github.com/Suwayomi/Tachidesk-Server/pull/343) by @AriaMoradi)
- (r1091) fix Applications dir dependency ([#344](https://github.com/Suwayomi/Tachidesk-Server/pull/344) by @AriaMoradi)
- (r1092) add support for alternative web interfaces ([#342](https://github.com/Suwayomi/Tachidesk-Server/pull/342) by @AriaMoradi)
- (r1093) Add displayValues json field for select filter ([#347](https://github.com/Suwayomi/Tachidesk-Server/pull/347) by @Syer10)
- (r1094) document manga endpoints ([#348](https://github.com/Suwayomi/Tachidesk-Server/pull/348) by @Syer10)
- (r1095) add ChapterCount to manga object in categoryMangas endpoint ([#349](https://github.com/Suwayomi/Tachidesk-Server/pull/349) by @abhijeetChawla)
- (r1096) document all endpoints ([#350](https://github.com/Suwayomi/Tachidesk-Server/pull/350) by @Syer10)
- (r1097) fix copymanga ([#354](https://github.com/Suwayomi/Tachidesk-Server/pull/354) by @AriaMoradi)
- (r1098) fix formatting by kotlinter (by @AriaMoradi)
- (r1099) bump WebUI (by @AriaMoradi)
- (r1100) fix WebUI release name (by @AriaMoradi)
- (r1101) Fix documentation errors ([#358](https://github.com/Suwayomi/Suwayomi-Server/pull/358) by @Syer10)
- (r1102) Docs improvements ([#359](https://github.com/Suwayomi/Suwayomi-Server/pull/359) by @Syer10)
- (r1103) Add linux-all.tar.gz & systemd service ([#366](https://github.com/Suwayomi/Suwayomi-Server/pull/366) by @mahor1221)
- (r1104) Publish to Windows Package Managar (WinGet) ([#369](https://github.com/Suwayomi/Suwayomi-Server/pull/369) by @vedantmgoyal2009)
- (r1105) Refactor scripts ([#370](https://github.com/Suwayomi/Suwayomi-Server/pull/370) by @mahor1221)
- (r1106) Run workflow jobs toghether ([#371](https://github.com/Suwayomi/Suwayomi-Server/pull/371) by @mahor1221)
- (r1107) Update gradle action ([#372](https://github.com/Suwayomi/Suwayomi-Server/pull/372) by @mahor1221)
- (r1108) Improve DocumentationDsl, bugfix default values and add queryParams ([#378](https://github.com/Suwayomi/Suwayomi-Server/pull/378) by @Syer10)
- (r1109) Tidy up bundler script ([#380](https://github.com/Suwayomi/Suwayomi-Server/pull/380) by @mahor1221)
- (r1110) Replace linux-all with linux-assets ([#381](https://github.com/Suwayomi/Suwayomi-Server/pull/381) by @mahor1221)
- (r1111) Rename every instance of Suwayomi jar to Suwayomi-Server.jar ([#384](https://github.com/Suwayomi/Suwayomi-Server/pull/384) by @AriaMoradi)
- (r1112) Fix mistakes from #384 ([#385](https://github.com/Suwayomi/Suwayomi-Server/pull/385) by @AriaMoradi)
- (r1101) Fix documentation errors ([#358](https://github.com/Suwayomi/Tachidesk-Server/pull/358) by @Syer10)
- (r1102) Docs improvements ([#359](https://github.com/Suwayomi/Tachidesk-Server/pull/359) by @Syer10)
- (r1103) Add linux-all.tar.gz & systemd service ([#366](https://github.com/Suwayomi/Tachidesk-Server/pull/366) by @mahor1221)
- (r1104) Publish to Windows Package Managar (WinGet) ([#369](https://github.com/Suwayomi/Tachidesk-Server/pull/369) by @vedantmgoyal2009)
- (r1105) Refactor scripts ([#370](https://github.com/Suwayomi/Tachidesk-Server/pull/370) by @mahor1221)
- (r1106) Run workflow jobs toghether ([#371](https://github.com/Suwayomi/Tachidesk-Server/pull/371) by @mahor1221)
- (r1107) Update gradle action ([#372](https://github.com/Suwayomi/Tachidesk-Server/pull/372) by @mahor1221)
- (r1108) Improve DocumentationDsl, bugfix default values and add queryParams ([#378](https://github.com/Suwayomi/Tachidesk-Server/pull/378) by @Syer10)
- (r1109) Tidy up bundler script ([#380](https://github.com/Suwayomi/Tachidesk-Server/pull/380) by @mahor1221)
- (r1110) Replace linux-all with linux-assets ([#381](https://github.com/Suwayomi/Tachidesk-Server/pull/381) by @mahor1221)
- (r1111) Rename every instance of Tachidesk jar to Tachdidesk-Server.jar ([#384](https://github.com/Suwayomi/Tachidesk-Server/pull/384) by @AriaMoradi)
- (r1112) Fix mistakes from #384 ([#385](https://github.com/Suwayomi/Tachidesk-Server/pull/385) by @AriaMoradi)
## Suwayomi-WebUI Changelog
- (r943) fix default width ([#171](https://github.com/Suwayomi/Suwayomi-WebUI/pull/171) by @Robonau)
- (r944) added an update checker button for library ([#172](https://github.com/Suwayomi/Suwayomi-WebUI/pull/172) by @infix)
- (r945) fix download queue delete button ([#176](https://github.com/Suwayomi/Suwayomi-WebUI/pull/176) by @Kreach37)
- (r946) fix mangadex filters ([#177](https://github.com/Suwayomi/Suwayomi-WebUI/pull/177) by @Robonau)
## Tachidesk-WebUI Changelog
- (r943) fix default width ([#171](https://github.com/Suwayomi/Tachidesk-WebUI/pull/171) by @Robonau)
- (r944) added an update checker button for library ([#172](https://github.com/Suwayomi/Tachidesk-WebUI/pull/172) by @infix)
- (r945) fix download queue delete button ([#176](https://github.com/Suwayomi/Tachidesk-WebUI/pull/176) by @Kreach37)
- (r946) fix mangadex filters ([#177](https://github.com/Suwayomi/Tachidesk-WebUI/pull/177) by @Robonau)
@@ -222,35 +222,35 @@
- Sources view layouts
- Various other changes...
## Suwayomi-Server Changelog
## Tachidesk-Server Changelog
- (r1074) v0.6.2 (by @AriaMoradi)
- (r1075) support array filter changes ([#304](https://github.com/Suwayomi/Suwayomi-Server/pull/304) by @AriaMoradi)
- (r1076) fix filterlist bugs ([#306](https://github.com/Suwayomi/Suwayomi-Server/pull/306) by @AriaMoradi)
- (r1077) Update README.md ([#305](https://github.com/Suwayomi/Suwayomi-Server/pull/305) by @mahor1221)
- (r1078) fix meta update changing all keys ([#314](https://github.com/Suwayomi/Suwayomi-Server/pull/314) by @AriaMoradi)
- (r1079) add support for tachiyomi extensions Lib 1.3 ([#316](https://github.com/Suwayomi/Suwayomi-Server/pull/316) by @AriaMoradi)
- (r1080) Fix sources list of one source throws an exception ([#308](https://github.com/Suwayomi/Suwayomi-Server/pull/308) by @Syer10)
- (r1081) Improve source handling, fix errors with uninitialized mangas in broken sources ([#319](https://github.com/Suwayomi/Suwayomi-Server/pull/319) by @Syer10)
- (r1082) Add thumbnail support for stub sources ([#320](https://github.com/Suwayomi/Suwayomi-Server/pull/320) by @Syer10)
- (r1083) update description for Tachidesk-Sorayomi ([#326](https://github.com/Suwayomi/Suwayomi-Server/pull/326) by @DattatreyaReddy)
- (r1084) Add last bit of code needed for Extensions Lib 1.3 ([#330](https://github.com/Suwayomi/Suwayomi-Server/pull/330) by @Syer10)
- (r1085) Add QuickJS, replaces Duktape for Extensions Lib 1.3 ([#331](https://github.com/Suwayomi/Suwayomi-Server/pull/331) by @Syer10)
- (r1086) fix auth not actually blocking requests ([#333](https://github.com/Suwayomi/Suwayomi-Server/pull/333) by @AriaMoradi)
- (r1075) support array filter changes ([#304](https://github.com/Suwayomi/Tachidesk-Server/pull/304) by @AriaMoradi)
- (r1076) fix filterlist bugs ([#306](https://github.com/Suwayomi/Tachidesk-Server/pull/306) by @AriaMoradi)
- (r1077) Update README.md ([#305](https://github.com/Suwayomi/Tachidesk-Server/pull/305) by @mahor1221)
- (r1078) fix meta update changing all keys ([#314](https://github.com/Suwayomi/Tachidesk-Server/pull/314) by @AriaMoradi)
- (r1079) add support for tachiyomi extensions Lib 1.3 ([#316](https://github.com/Suwayomi/Tachidesk-Server/pull/316) by @AriaMoradi)
- (r1080) Fix sources list of one source throws an exception ([#308](https://github.com/Suwayomi/Tachidesk-Server/pull/308) by @Syer10)
- (r1081) Improve source handling, fix errors with uninitialized mangas in broken sources ([#319](https://github.com/Suwayomi/Tachidesk-Server/pull/319) by @Syer10)
- (r1082) Add thumbnail support for stub sources ([#320](https://github.com/Suwayomi/Tachidesk-Server/pull/320) by @Syer10)
- (r1083) update description for Tachidesk-Sorayomi ([#326](https://github.com/Suwayomi/Tachidesk-Server/pull/326) by @DattatreyaReddy)
- (r1084) Add last bit of code needed for Extensions Lib 1.3 ([#330](https://github.com/Suwayomi/Tachidesk-Server/pull/330) by @Syer10)
- (r1085) Add QuickJS, replaces Duktape for Extensions Lib 1.3 ([#331](https://github.com/Suwayomi/Tachidesk-Server/pull/331) by @Syer10)
- (r1086) fix auth not actually blocking requests ([#333](https://github.com/Suwayomi/Tachidesk-Server/pull/333) by @AriaMoradi)
## Suwayomi-WebUI Changelog
- (r930) Source filter scroll fix (array of filters on submit [#149](https://github.com/Suwayomi/Suwayomi-WebUI/pull/149) by @Robonau)
- (r931) fix manga badges setting menu that turns the update/download badges on and off ([#150](https://github.com/Suwayomi/Suwayomi-WebUI/pull/150) by @Robonau)
- (r932) move sorts to copy tachiyomi ([#151](https://github.com/Suwayomi/Suwayomi-WebUI/pull/151) by @Robonau)
- (r933) add comfortable grid option ([#152](https://github.com/Suwayomi/Suwayomi-WebUI/pull/152) by @Robonau)
- (r934) source layouts ([#153](https://github.com/Suwayomi/Suwayomi-WebUI/pull/153) by @Robonau)
- (r935) List layout ([#154](https://github.com/Suwayomi/Suwayomi-WebUI/pull/154) by @Robonau)
- (r936) in library badge to manga in sources ([#156](https://github.com/Suwayomi/Suwayomi-WebUI/pull/156) by @Robonau)
- (r937) mass search ([#157](https://github.com/Suwayomi/Suwayomi-WebUI/pull/157) by @Robonau)
- (r938) 18+ tag on source/extension cards ([#160](https://github.com/Suwayomi/Suwayomi-WebUI/pull/160) by @Robonau)
- (r939) fix search source click ([#164](https://github.com/Suwayomi/Suwayomi-WebUI/pull/164) by @Robonau)
- (r940) items per row setting ([#165](https://github.com/Suwayomi/Suwayomi-WebUI/pull/165) by @Robonau)
- (r941) fix the grid width thing ([#169](https://github.com/Suwayomi/Suwayomi-WebUI/pull/169) by @Robonau)
- (r942) unified library options ([#168](https://github.com/Suwayomi/Suwayomi-WebUI/pull/168) by @infix)
## Tachidesk-WebUI Changelog
- (r930) Source filter scroll fix (array of filters on submit [#149](https://github.com/Suwayomi/Tachidesk-WebUI/pull/149) by @Robonau)
- (r931) fix manga badges setting menu that turns the update/download badges on and off ([#150](https://github.com/Suwayomi/Tachidesk-WebUI/pull/150) by @Robonau)
- (r932) move sorts to copy tachiyomi ([#151](https://github.com/Suwayomi/Tachidesk-WebUI/pull/151) by @Robonau)
- (r933) add comfortable grid option ([#152](https://github.com/Suwayomi/Tachidesk-WebUI/pull/152) by @Robonau)
- (r934) source layouts ([#153](https://github.com/Suwayomi/Tachidesk-WebUI/pull/153) by @Robonau)
- (r935) List layout ([#154](https://github.com/Suwayomi/Tachidesk-WebUI/pull/154) by @Robonau)
- (r936) in library badge to manga in sources ([#156](https://github.com/Suwayomi/Tachidesk-WebUI/pull/156) by @Robonau)
- (r937) mass search ([#157](https://github.com/Suwayomi/Tachidesk-WebUI/pull/157) by @Robonau)
- (r938) 18+ tag on source/extension cards ([#160](https://github.com/Suwayomi/Tachidesk-WebUI/pull/160) by @Robonau)
- (r939) fix search source click ([#164](https://github.com/Suwayomi/Tachidesk-WebUI/pull/164) by @Robonau)
- (r940) items per row setting ([#165](https://github.com/Suwayomi/Tachidesk-WebUI/pull/165) by @Robonau)
- (r941) fix the grid width thing ([#169](https://github.com/Suwayomi/Tachidesk-WebUI/pull/169) by @Robonau)
- (r942) unified library options ([#168](https://github.com/Suwayomi/Tachidesk-WebUI/pull/168) by @infix)
# Server: v0.6.2 + WebUI: r929
## TL;DR
@@ -260,28 +260,28 @@
- Better visuals for Download Queue
- A live version of WebUI is now available [at this link](https://tachidesk-webui-preview.github.io/).
## Suwayomi-Server Changelog
- (r1073) Refactor debian-packager.sh, rename launcher scripts ([#303](https://github.com/Suwayomi/Suwayomi-Server/pull/303) by @mahor1221)
## Tachidesk-Server Changelog
- (r1073) Refactor debian-packager.sh, rename launcher scripts ([#303](https://github.com/Suwayomi/Tachidesk-Server/pull/303) by @mahor1221)
## Suwayomi-WebUI Changelog
- (r912) show locale date, less confusing ([#131](https://github.com/Suwayomi/Suwayomi-WebUI/pull/131) by @AriaMoradi)
- (r913) fix links to work on a bare host ([#132](https://github.com/Suwayomi/Suwayomi-WebUI/pull/132) by @AriaMoradi)
- (r914) fix direct links ([#133](https://github.com/Suwayomi/Suwayomi-WebUI/pull/133) by @AriaMoradi)
## Tachidesk-WebUI Changelog
- (r912) show locale date, less confusing ([#131](https://github.com/Suwayomi/Tachidesk-WebUI/pull/131) by @AriaMoradi)
- (r913) fix links to work on a bare host ([#132](https://github.com/Suwayomi/Tachidesk-WebUI/pull/132) by @AriaMoradi)
- (r914) fix direct links ([#133](https://github.com/Suwayomi/Tachidesk-WebUI/pull/133) by @AriaMoradi)
- (r915) deploy to github pages (by @AriaMoradi)
- (r916) fix typo (by @AriaMoradi)
- (r917) better naming (by @AriaMoradi)
- (r918) update notice about github pages (by @AriaMoradi)
- (r919) move text (by @AriaMoradi)
- (r920) make all links work by catching 404 (by @AriaMoradi)
- (r921) fix scrolling 8px ([#135](https://github.com/Suwayomi/Suwayomi-WebUI/pull/135) by @Robonau)
- (r922) sorting ([#136](https://github.com/Suwayomi/Suwayomi-WebUI/pull/136) by @Robonau)
- (r923) Close button fix ([#141](https://github.com/Suwayomi/Suwayomi-WebUI/pull/141)) z14942744@gmail.com
- (r924) add NavBarContextProvider ([#128](https://github.com/Suwayomi/Suwayomi-WebUI/pull/128) by @abhijeetChawla)
- (r925) Resolved Merged Conflicts ([#127](https://github.com/Suwayomi/Suwayomi-WebUI/pull/127) by @abhijeetChawla)
- (r926) more Download Queue info ([#138](https://github.com/Suwayomi/Suwayomi-WebUI/pull/138) by @Robonau)
- (r927) Source filters, move search to SourceMangas ([#142](https://github.com/Suwayomi/Suwayomi-WebUI/pull/142) by @Robonau)
- (r928) Source genre sorts design ([#147](https://github.com/Suwayomi/Suwayomi-WebUI/pull/147) by @Robonau)
- (r929) Update LibraryOptions.tsx ([#146](https://github.com/Suwayomi/Suwayomi-WebUI/pull/146) by @Robonau)
- (r921) fix scrolling 8px ([#135](https://github.com/Suwayomi/Tachidesk-WebUI/pull/135) by @Robonau)
- (r922) sorting ([#136](https://github.com/Suwayomi/Tachidesk-WebUI/pull/136) by @Robonau)
- (r923) Close button fix ([#141](https://github.com/Suwayomi/Tachidesk-WebUI/pull/141)) z14942744@gmail.com
- (r924) add NavBarContextProvider ([#128](https://github.com/Suwayomi/Tachidesk-WebUI/pull/128) by @abhijeetChawla)
- (r925) Resolved Merged Conflicts ([#127](https://github.com/Suwayomi/Tachidesk-WebUI/pull/127) by @abhijeetChawla)
- (r926) more Download Queue info ([#138](https://github.com/Suwayomi/Tachidesk-WebUI/pull/138) by @Robonau)
- (r927) Source filters, move search to SourceMangas ([#142](https://github.com/Suwayomi/Tachidesk-WebUI/pull/142) by @Robonau)
- (r928) Source genre sorts design ([#147](https://github.com/Suwayomi/Tachidesk-WebUI/pull/147) by @Robonau)
- (r929) Update LibraryOptions.tsx ([#146](https://github.com/Suwayomi/Tachidesk-WebUI/pull/146) by @Robonau)
@@ -290,49 +290,49 @@
- msi and deb packages thanks to @mahor1221
- [Tachidesk-Flutter](https://github.com/Suwayomi/Tachidesk-Flutter) exists now!
## Suwayomi-Server Changelog
## Tachidesk-Server Changelog
- (r1047) update (by @AriaMoradi)
- (r1048) bump version (by @AriaMoradi)
- (r1049) Update README.md (by @AriaMoradi)
- (r1050) Update README.md (by @AriaMoradi)
- (r1051) refactor getChapter ([#268](https://github.com/Suwayomi/Suwayomi-Server/pull/268) by @AriaMoradi)
- (r1052) Improve documentation with Http codes ([#261](https://github.com/Suwayomi/Suwayomi-Server/pull/261) by @Syer10)
- (r1053) Add Route to stop and reset the updater ([#260](https://github.com/Suwayomi/Suwayomi-Server/pull/260) by @ntbm)
- (r1054) ignore non image files ([#269](https://github.com/Suwayomi/Suwayomi-Server/pull/269) by @AriaMoradi)
- (r1051) refactor getChapter ([#268](https://github.com/Suwayomi/Tachidesk-Server/pull/268) by @AriaMoradi)
- (r1052) Improve documentation with Http codes ([#261](https://github.com/Suwayomi/Tachidesk-Server/pull/261) by @Syer10)
- (r1053) Add Route to stop and reset the updater ([#260](https://github.com/Suwayomi/Tachidesk-Server/pull/260) by @ntbm)
- (r1054) ignore non image files ([#269](https://github.com/Suwayomi/Tachidesk-Server/pull/269) by @AriaMoradi)
- (r1055) fix compile erorr (by @AriaMoradi)
- (r1056) update dex2jar (by @AriaMoradi)
- (r1057) Update Gradle and Dependencies ([#281](https://github.com/Suwayomi/Suwayomi-Server/pull/281) by @Syer10)
- (r1058) Handlers must return a result ([#282](https://github.com/Suwayomi/Suwayomi-Server/pull/282) by @Syer10)
- (r1059) Allow app compilation on Java 18+ ([#286](https://github.com/Suwayomi/Suwayomi-Server/pull/286) by @Syer10)
- (r1060) Automated MSI package building ([#277](https://github.com/Suwayomi/Suwayomi-Server/pull/277) by @mahor1221)
- (r1061) Automated debian package building ([#287](https://github.com/Suwayomi/Suwayomi-Server/pull/287) by @mahor1221)
- (r1062) fix Debian package errors ([#288](https://github.com/Suwayomi/Suwayomi-Server/pull/288) by @mahor1221)
- (r1063) Fix build_push.yml Hopefully ([#289](https://github.com/Suwayomi/Suwayomi-Server/pull/289) by @mahor1221)
- (r1064) Improve windows-bundler.sh ([#290](https://github.com/Suwayomi/Suwayomi-Server/pull/290) by @mahor1221)
- (r1065) add Tachidesk-Flutter to readme ([#292](https://github.com/Suwayomi/Suwayomi-Server/pull/292)) @DattatreyaReddy)
- (r1066) no online fetch on backup ([#293](https://github.com/Suwayomi/Suwayomi-Server/pull/293) by @AriaMoradi)
- (r1067) auto-remove duplicate chapters ([#294](https://github.com/Suwayomi/Suwayomi-Server/pull/294) by @AriaMoradi)
- (r1068) remove gson ([#295](https://github.com/Suwayomi/Suwayomi-Server/pull/295) by @AriaMoradi)
- (r1057) Update Gradle and Dependencies ([#281](https://github.com/Suwayomi/Tachidesk-Server/pull/281) by @Syer10)
- (r1058) Handlers must return a result ([#282](https://github.com/Suwayomi/Tachidesk-Server/pull/282) by @Syer10)
- (r1059) Allow app compilation on Java 18+ ([#286](https://github.com/Suwayomi/Tachidesk-Server/pull/286) by @Syer10)
- (r1060) Automated MSI package building ([#277](https://github.com/Suwayomi/Tachidesk-Server/pull/277) by @mahor1221)
- (r1061) Automated debian package building ([#287](https://github.com/Suwayomi/Tachidesk-Server/pull/287) by @mahor1221)
- (r1062) fix Debian package errors ([#288](https://github.com/Suwayomi/Tachidesk-Server/pull/288) by @mahor1221)
- (r1063) Fix build_push.yml Hopefully ([#289](https://github.com/Suwayomi/Tachidesk-Server/pull/289) by @mahor1221)
- (r1064) Improve windows-bundler.sh ([#290](https://github.com/Suwayomi/Tachidesk-Server/pull/290) by @mahor1221)
- (r1065) add Tachidesk-Flutter to readme ([#292](https://github.com/Suwayomi/Tachidesk-Server/pull/292)) @DattatreyaReddy)
- (r1066) no online fetch on backup ([#293](https://github.com/Suwayomi/Tachidesk-Server/pull/293) by @AriaMoradi)
- (r1067) auto-remove duplicate chapters ([#294](https://github.com/Suwayomi/Tachidesk-Server/pull/294) by @AriaMoradi)
- (r1068) remove gson ([#295](https://github.com/Suwayomi/Tachidesk-Server/pull/295) by @AriaMoradi)
## Suwayomi-WebUI Changelog
- (r894) migrate ReaderNavbar to Mui 5 ([#84](https://github.com/Suwayomi/Suwayomi-WebUI/pull/84) by @AriaMoradi)
- (r895) migrate SpinnerImage to Mui 5 ([#97](https://github.com/Suwayomi/Suwayomi-WebUI/pull/97) by @AriaMoradi)
- (r896) migrate VerticalPager to Mui 5 ([#94](https://github.com/Suwayomi/Suwayomi-WebUI/pull/94) by @AriaMoradi)
- (r897) migrate PagedPager to Mui 5 ([#93](https://github.com/Suwayomi/Suwayomi-WebUI/pull/93) by @AriaMoradi)
- (r898) MangaCard imges don't stretch now ([#110](https://github.com/Suwayomi/Suwayomi-WebUI/pull/110) by @abhijeetChawla)
- (r899) show correct title ([#111](https://github.com/Suwayomi/Suwayomi-WebUI/pull/111) by @AriaMoradi)
- (r900) migrate DoublePage to Mui 5 ([#88](https://github.com/Suwayomi/Suwayomi-WebUI/pull/88) by @AriaMoradi)
- (r901) migrate DoublePagedPager to Mui 5 ([#91](https://github.com/Suwayomi/Suwayomi-WebUI/pull/91) by @AriaMoradi)
- (r902) migrate Reader to Mui 5 ([#100](https://github.com/Suwayomi/Suwayomi-WebUI/pull/100) by @AriaMoradi)
- (r903) migrate HorizantalPager to Mui 5 ([#92](https://github.com/Suwayomi/Suwayomi-WebUI/pull/92) by @AriaMoradi)
- (r904) migrate PageNumber to Mui 5 ([#90](https://github.com/Suwayomi/Suwayomi-WebUI/pull/90) by @AriaMoradi)
- (r905) Chapter filter is woking ([#114](https://github.com/Suwayomi/Suwayomi-WebUI/pull/114) by @abhijeetChawla)
- (r906) added extension search ([#115](https://github.com/Suwayomi/Suwayomi-WebUI/pull/115) by @abhijeetChawla)
- (r907) cleanup ([#117](https://github.com/Suwayomi/Suwayomi-WebUI/pull/117) by @AriaMoradi)
- (r908) handle search shortcuts ([#116](https://github.com/Suwayomi/Suwayomi-WebUI/pull/116) by @AriaMoradi)
- (r909) Refactor for Removing unnecesary UseEffect ([#118](https://github.com/Suwayomi/Suwayomi-WebUI/pull/118) by @abhijeetChawla)
- (r910) refactor ChapterList ([#125](https://github.com/Suwayomi/Suwayomi-WebUI/pull/125) by @abhijeetChawla)
- (r911) refactor ChapterOptions ([#126](https://github.com/Suwayomi/Suwayomi-WebUI/pull/126) by @abhijeetChawla)
## Tachidesk-WebUI Changelog
- (r894) migrate ReaderNavbar to Mui 5 ([#84](https://github.com/Suwayomi/Tachidesk-WebUI/pull/84) by @AriaMoradi)
- (r895) migrate SpinnerImage to Mui 5 ([#97](https://github.com/Suwayomi/Tachidesk-WebUI/pull/97) by @AriaMoradi)
- (r896) migrate VerticalPager to Mui 5 ([#94](https://github.com/Suwayomi/Tachidesk-WebUI/pull/94) by @AriaMoradi)
- (r897) migrate PagedPager to Mui 5 ([#93](https://github.com/Suwayomi/Tachidesk-WebUI/pull/93) by @AriaMoradi)
- (r898) MangaCard imges don't stretch now ([#110](https://github.com/Suwayomi/Tachidesk-WebUI/pull/110) by @abhijeetChawla)
- (r899) show correct title ([#111](https://github.com/Suwayomi/Tachidesk-WebUI/pull/111) by @AriaMoradi)
- (r900) migrate DoublePage to Mui 5 ([#88](https://github.com/Suwayomi/Tachidesk-WebUI/pull/88) by @AriaMoradi)
- (r901) migrate DoublePagedPager to Mui 5 ([#91](https://github.com/Suwayomi/Tachidesk-WebUI/pull/91) by @AriaMoradi)
- (r902) migrate Reader to Mui 5 ([#100](https://github.com/Suwayomi/Tachidesk-WebUI/pull/100) by @AriaMoradi)
- (r903) migrate HorizantalPager to Mui 5 ([#92](https://github.com/Suwayomi/Tachidesk-WebUI/pull/92) by @AriaMoradi)
- (r904) migrate PageNumber to Mui 5 ([#90](https://github.com/Suwayomi/Tachidesk-WebUI/pull/90) by @AriaMoradi)
- (r905) Chapter filter is woking ([#114](https://github.com/Suwayomi/Tachidesk-WebUI/pull/114) by @abhijeetChawla)
- (r906) added extension search ([#115](https://github.com/Suwayomi/Tachidesk-WebUI/pull/115) by @abhijeetChawla)
- (r907) cleanup ([#117](https://github.com/Suwayomi/Tachidesk-WebUI/pull/117) by @AriaMoradi)
- (r908) handle search shortcuts ([#116](https://github.com/Suwayomi/Tachidesk-WebUI/pull/116) by @AriaMoradi)
- (r909) Refactor for Removing unnecesary UseEffect ([#118](https://github.com/Suwayomi/Tachidesk-WebUI/pull/118) by @abhijeetChawla)
- (r910) refactor ChapterList ([#125](https://github.com/Suwayomi/Tachidesk-WebUI/pull/125) by @abhijeetChawla)
- (r911) refactor ChapterOptions ([#126](https://github.com/Suwayomi/Tachidesk-WebUI/pull/126) by @abhijeetChawla)
@@ -349,16 +349,16 @@
- Added support for updating library(Server side only)
- A bunch of API breaking changes(hence why bumping to v0.6.0)!
## Suwayomi-Server Changelog
## Tachidesk-Server Changelog
- (r996) cleanup (by @AriaMoradi)
- (r999) better cleaning algorithm (by @AriaMoradi)
- (r1007) remove anime support (by @AriaMoradi)
- (r1009) Fix tests ([#226](https://github.com/Suwayomi/Suwayomi-Server/pull/226) by @ntbm)
- (r1010) Expose unread and download count of Manga in category api ([#227](https://github.com/Suwayomi/Suwayomi-Server/pull/227) by @ntbm)
- (r1011) add Cache Header to Thumbnail Response for improved library performance ([#228](https://github.com/Suwayomi/Suwayomi-Server/pull/228) by @ntbm)
- (r1013) Fix unread and download counts casing ([#230](https://github.com/Suwayomi/Suwayomi-Server/pull/230) by @Syer10)
- (r1014) Fix broken test ([#231](https://github.com/Suwayomi/Suwayomi-Server/pull/231) by @ntbm)
- (r1016) Fix category reorder Endpoint. Added Test for Category Reorder ([#232](https://github.com/Suwayomi/Suwayomi-Server/pull/232) by @ntbm)
- (r1009) Fix tests ([#226](https://github.com/Suwayomi/Tachidesk-Server/pull/226) by @ntbm)
- (r1010) Expose unread and download count of Manga in category api ([#227](https://github.com/Suwayomi/Tachidesk-Server/pull/227) by @ntbm)
- (r1011) add Cache Header to Thumbnail Response for improved library performance ([#228](https://github.com/Suwayomi/Tachidesk-Server/pull/228) by @ntbm)
- (r1013) Fix unread and download counts casing ([#230](https://github.com/Suwayomi/Tachidesk-Server/pull/230) by @Syer10)
- (r1014) Fix broken test ([#231](https://github.com/Suwayomi/Tachidesk-Server/pull/231) by @ntbm)
- (r1016) Fix category reorder Endpoint. Added Test for Category Reorder ([#232](https://github.com/Suwayomi/Tachidesk-Server/pull/232) by @ntbm)
- (r1017) change windows bundle names (by @AriaMoradi)
- (r1018) improve tests (by @AriaMoradi)
- (r1019) allow injecting Sources (by @AriaMoradi)
@@ -379,33 +379,33 @@
- (r1034) Implement Update of Library/Category ([#235](https://github.com/Suwayomi/src/pull/235) by @ntbm)
- (r1035) update (by @AriaMoradi)
- (r1036) Mention the existence of Mahor's Tachidesk-GTK (by @AriaMoradi)
- (r1037) Add a Kotlin DSL for endpoint documentation ([#249](https://github.com/Suwayomi/Suwayomi-Server/pull/249) by @Syer10)
- (r1037) Add a Kotlin DSL for endpoint documentation ([#249](https://github.com/Suwayomi/Tachidesk-Server/pull/249) by @Syer10)
- (r1038) update (by @AriaMoradi)
- (r1039) update (by @AriaMoradi)
- (r1040) cleanup directory names ([#251](https://github.com/Suwayomi/Suwayomi-Server/pull/251) by @AriaMoradi)
- (r1041) Fix first page not being detected correctly ([#253](https://github.com/Suwayomi/Suwayomi-Server/pull/253) by @AriaMoradi)
- (r1040) cleanup directory names ([#251](https://github.com/Suwayomi/Tachidesk-Server/pull/251) by @AriaMoradi)
- (r1041) Fix first page not being detected correctly ([#253](https://github.com/Suwayomi/Tachidesk-Server/pull/253) by @AriaMoradi)
- (r1042) Update README.md (by @AriaMoradi)
- (r1043) Update README.md (by @AriaMoradi)
- (r1044) migrate application directories ([#255](https://github.com/Suwayomi/Suwayomi-Server/pull/255) by @AriaMoradi)
- (r1045) add support for MultiSelectListPreference ([#258](https://github.com/Suwayomi/Suwayomi-Server/pull/258) by @AriaMoradi)
- (r1046) empty searchTerm support ([#259](https://github.com/Suwayomi/Suwayomi-Server/pull/259) by @AriaMoradi)
- (r1044) migrate application directories ([#255](https://github.com/Suwayomi/Tachidesk-Server/pull/255) by @AriaMoradi)
- (r1045) add support for MultiSelectListPreference ([#258](https://github.com/Suwayomi/Tachidesk-Server/pull/258) by @AriaMoradi)
- (r1046) empty searchTerm support ([#259](https://github.com/Suwayomi/Tachidesk-Server/pull/259) by @AriaMoradi)
## Suwayomi-WebUI
- (r821) add Permanent sidebar for desktop widths([#46](https://github.com/Suwayomi/Suwayomi-WebUI/pull/46) by @abhijeetChawla)
## Tachidesk-WebUI
- (r821) add Permanent sidebar for desktop widths([#46](https://github.com/Suwayomi/Tachidesk-WebUI/pull/46) by @abhijeetChawla)
- (r822) Fix Local Source being missing (by @AriaMoradi)
- (r823) fix the ugliness of bare messages (by @AriaMoradi)
- (r824) add pull request template (by @AriaMoradi)
- (r825) add Unread badges ([#48](https://github.com/Suwayomi/Suwayomi-WebUI/pull/48) by @ntbm)
- (r826) Back button implementation ([#47](https://github.com/Suwayomi/Suwayomi-WebUI/pull/47) by @abhijeetChawla)
- (r825) add Unread badges ([#48](https://github.com/Suwayomi/Tachidesk-WebUI/pull/48) by @ntbm)
- (r826) Back button implementation ([#47](https://github.com/Suwayomi/Tachidesk-WebUI/pull/47) by @abhijeetChawla)
- (r827) remove redundant '/manga' prefix from paths (by @AriaMoradi)
- (r828) refactor (by @AriaMoradi)
- (r829) put Sources and Extensions in the same screen (by @AriaMoradi)
- (r830) Set Fallback Image for broken Thumbnails ([#50](https://github.com/Suwayomi/Suwayomi-WebUI/pull/50) by @ntbm)
- (r833) Apply Api changes for unread badges ([#52](https://github.com/Suwayomi/Suwayomi-WebUI/pull/52) by @ntbm)
- (r834) add EmptyView to DownloadQueue, refactro strings ([#53](https://github.com/Suwayomi/Suwayomi-WebUI/pull/53) by @abhijeetChawla)
- (r835) Bottom navbar for mobile ([#51](https://github.com/Suwayomi/Suwayomi-WebUI/pull/51) by @abhijeetChawla)
- (r836) Implement Unread Filter for Library ([#54](https://github.com/Suwayomi/Suwayomi-WebUI/pull/54) by @ntbm)
- (r830) Set Fallback Image for broken Thumbnails ([#50](https://github.com/Suwayomi/Tachidesk-WebUI/pull/50) by @ntbm)
- (r833) Apply Api changes for unread badges ([#52](https://github.com/Suwayomi/Tachidesk-WebUI/pull/52) by @ntbm)
- (r834) add EmptyView to DownloadQueue, refactro strings ([#53](https://github.com/Suwayomi/Tachidesk-WebUI/pull/53) by @abhijeetChawla)
- (r835) Bottom navbar for mobile ([#51](https://github.com/Suwayomi/Tachidesk-WebUI/pull/51) by @abhijeetChawla)
- (r836) Implement Unread Filter for Library ([#54](https://github.com/Suwayomi/Tachidesk-WebUI/pull/54) by @ntbm)
- (r837) fix navbar broken logic (by @AriaMoradi)
- (r838) fix navbar (by @AriaMoradi)
- (r839) refactor (by @AriaMoradi)
@@ -417,51 +417,51 @@
- (r845) custom Extension icon, google's version is shit (by @AriaMoradi)
- (r846) refactor (by @AriaMoradi)
- (r848) move info (by @AriaMoradi)
- (r849) add Search to Library ([#55](https://github.com/Suwayomi/Suwayomi-WebUI/pull/55) by @ntbm)
- (r850) add aspect ratio to the manga card. ([#56](https://github.com/Suwayomi/Suwayomi-WebUI/pull/56) by @abhijeetChawla)
- (r849) add Search to Library ([#55](https://github.com/Suwayomi/Tachidesk-WebUI/pull/55) by @ntbm)
- (r850) add aspect ratio to the manga card. ([#56](https://github.com/Suwayomi/Tachidesk-WebUI/pull/56) by @abhijeetChawla)
- (r851) better wording (by @AriaMoradi)
- (r852) reorder nav buttons (by @AriaMoradi)
- (r853) nicer gradient (by @AriaMoradi)
- (r854) refactor MangaCard (by @AriaMoradi)
- (r855) closes #58 (by @AriaMoradi
- (r856) Add Resume Reading FAB Manga screen ([#59](https://github.com/Suwayomi/Suwayomi-WebUI/pull/59) by @abhijeetChawla)
- (r857) add filter and badge for `downloadCount` ([#62](https://github.com/Suwayomi/Suwayomi-WebUI/pull/62) by @abhijeetChawla)
- (r856) Add Resume Reading FAB Manga screen ([#59](https://github.com/Suwayomi/Tachidesk-WebUI/pull/59) by @abhijeetChawla)
- (r857) add filter and badge for `downloadCount` ([#62](https://github.com/Suwayomi/Tachidesk-WebUI/pull/62) by @abhijeetChawla)
- (r858) add issue template (by @AriaMoradi)
- (r859) Change color of navbar in light mode ([#65](https://github.com/Suwayomi/Suwayomi-WebUI/pull/65) by @abhijeetChawla)
- (r860) fix manga FAB margins ([#66](https://github.com/Suwayomi/Suwayomi-WebUI/pull/66) by @AriaMoradi)
- (r861) remove extra scrollbar on mobile ([#67](https://github.com/Suwayomi/Suwayomi-WebUI/pull/67) by @AriaMoradi)
- (r862) Fix Bad messages in Library Appbar search ([#70](https://github.com/Suwayomi/Suwayomi-WebUI/pull/70) by @ntbm)
- (r859) Change color of navbar in light mode ([#65](https://github.com/Suwayomi/Tachidesk-WebUI/pull/65) by @abhijeetChawla)
- (r860) fix manga FAB margins ([#66](https://github.com/Suwayomi/Tachidesk-WebUI/pull/66) by @AriaMoradi)
- (r861) remove extra scrollbar on mobile ([#67](https://github.com/Suwayomi/Tachidesk-WebUI/pull/67) by @AriaMoradi)
- (r862) Fix Bad messages in Library Appbar search ([#70](https://github.com/Suwayomi/Tachidesk-WebUI/pull/70) by @ntbm)
- (r863) ban the style prop (by @AriaMoradi)
- (r864) Updates pagination update ([#68](https://github.com/Suwayomi/Suwayomi-WebUI/pull/68) by @AriaMoradi)
- (r865) make the whole chapter card into a button ([#73](https://github.com/Suwayomi/Suwayomi-WebUI/pull/73) by @AriaMoradi)
- (r866) fix chapter actions not working if manga is not fetched online ([#74](https://github.com/Suwayomi/Suwayomi-WebUI/pull/74) by @AriaMoradi)
- (r867) migrate some components to Mui5 new styling system ([#72](https://github.com/Suwayomi/Suwayomi-WebUI/pull/72) by @abhijeetChawla)
- (r868) load first page on read manga ([#76](https://github.com/Suwayomi/Suwayomi-WebUI/pull/76) by @AriaMoradi)
- (r869) Revert "migrate some components to Mui5 new styling system ([#72](https://github.com/Suwayomi/Suwayomi-WebUI/pull/72))" (by @AriaMoradi)
- (r870) migrate Backup to Mui 5 ([#106](https://github.com/Suwayomi/Suwayomi-WebUI/pull/106) by @AriaMoradi)
- (r871) migrate EmptyView to Mui 5 ([#95](https://github.com/Suwayomi/Suwayomi-WebUI/pull/95) by @AriaMoradi)
- (r872) migrate CategorySelect to Mui 5 ([#85](https://github.com/Suwayomi/Suwayomi-WebUI/pull/85) by @AriaMoradi)
- (r873) migrate LibraryOptions to Mui 5 ([#83](https://github.com/Suwayomi/Suwayomi-WebUI/pull/83) by @AriaMoradi)
- (r874) migrate ChapterCard.tsx to Mui 5 ([#80](https://github.com/Suwayomi/Suwayomi-WebUI/pull/80) by @AriaMoradi)
- (r875) migrate App.tsx to Mui 5 ([#79](https://github.com/Suwayomi/Suwayomi-WebUI/pull/79) by @AriaMoradi)
- (r876) migrate SourceConfigure to Mui 5 ([#103](https://github.com/Suwayomi/Suwayomi-WebUI/pull/103) by @AriaMoradi)
- (r877) migrate Settings to Mui 5 ([#102](https://github.com/Suwayomi/Suwayomi-WebUI/pull/102) by @AriaMoradi)
- (r878) migrate Updates to Mui 5 ([#104](https://github.com/Suwayomi/Suwayomi-WebUI/pull/104) by @AriaMoradi)
- (r879) Save tabs number in Url to persist tab when go to other paths ([#78](https://github.com/Suwayomi/Suwayomi-WebUI/pull/78) by @abhijeetChawla)
- (r880) migrate LangSelect to Mui 5 ([#86](https://github.com/Suwayomi/Suwayomi-WebUI/pull/86) by @AriaMoradi)
- (r881) migrate ExtensionCard.tsx to Mui 5 ([#81](https://github.com/Suwayomi/Suwayomi-WebUI/pull/81) by @AriaMoradi)
- (r882) migrate SingleSearch to Mui 5 ([#101](https://github.com/Suwayomi/Suwayomi-WebUI/pull/101) by @AriaMoradi)
- (r883) migrate LoadingPlaceholder to Mui 5 ([#96](https://github.com/Suwayomi/Suwayomi-WebUI/pull/96) by @AriaMoradi)
- (r884) migrate About to Mui 5 ([#105](https://github.com/Suwayomi/Suwayomi-WebUI/pull/105) by @AriaMoradi)
- (r885) migrate SourceCard to Mui 5 ([#82](https://github.com/Suwayomi/Suwayomi-WebUI/pull/82) by @AriaMoradi)
- (r886) migrate Manga to Mui 5 ([#99](https://github.com/Suwayomi/Suwayomi-WebUI/pull/99) by @AriaMoradi)
- (r887) migrate Browse to Mui 5 ([#98](https://github.com/Suwayomi/Suwayomi-WebUI/pull/98) by @AriaMoradi)
- (r888) migrate DesktopSideBar to Mui 5 ([#87](https://github.com/Suwayomi/Suwayomi-WebUI/pull/87) by @AriaMoradi)
- (r889) cleanup library ([#107](https://github.com/Suwayomi/Suwayomi-WebUI/pull/107) by @AriaMoradi)
- (r864) Updates pagination update ([#68](https://github.com/Suwayomi/Tachidesk-WebUI/pull/68) by @AriaMoradi)
- (r865) make the whole chapter card into a button ([#73](https://github.com/Suwayomi/Tachidesk-WebUI/pull/73) by @AriaMoradi)
- (r866) fix chapter actions not working if manga is not fetched online ([#74](https://github.com/Suwayomi/Tachidesk-WebUI/pull/74) by @AriaMoradi)
- (r867) migrate some components to Mui5 new styling system ([#72](https://github.com/Suwayomi/Tachidesk-WebUI/pull/72) by @abhijeetChawla)
- (r868) load first page on read manga ([#76](https://github.com/Suwayomi/Tachidesk-WebUI/pull/76) by @AriaMoradi)
- (r869) Revert "migrate some components to Mui5 new styling system ([#72](https://github.com/Suwayomi/Tachidesk-WebUI/pull/72))" (by @AriaMoradi)
- (r870) migrate Backup to Mui 5 ([#106](https://github.com/Suwayomi/Tachidesk-WebUI/pull/106) by @AriaMoradi)
- (r871) migrate EmptyView to Mui 5 ([#95](https://github.com/Suwayomi/Tachidesk-WebUI/pull/95) by @AriaMoradi)
- (r872) migrate CategorySelect to Mui 5 ([#85](https://github.com/Suwayomi/Tachidesk-WebUI/pull/85) by @AriaMoradi)
- (r873) migrate LibraryOptions to Mui 5 ([#83](https://github.com/Suwayomi/Tachidesk-WebUI/pull/83) by @AriaMoradi)
- (r874) migrate ChapterCard.tsx to Mui 5 ([#80](https://github.com/Suwayomi/Tachidesk-WebUI/pull/80) by @AriaMoradi)
- (r875) migrate App.tsx to Mui 5 ([#79](https://github.com/Suwayomi/Tachidesk-WebUI/pull/79) by @AriaMoradi)
- (r876) migrate SourceConfigure to Mui 5 ([#103](https://github.com/Suwayomi/Tachidesk-WebUI/pull/103) by @AriaMoradi)
- (r877) migrate Settings to Mui 5 ([#102](https://github.com/Suwayomi/Tachidesk-WebUI/pull/102) by @AriaMoradi)
- (r878) migrate Updates to Mui 5 ([#104](https://github.com/Suwayomi/Tachidesk-WebUI/pull/104) by @AriaMoradi)
- (r879) Save tabs number in Url to persist tab when go to other paths ([#78](https://github.com/Suwayomi/Tachidesk-WebUI/pull/78) by @abhijeetChawla)
- (r880) migrate LangSelect to Mui 5 ([#86](https://github.com/Suwayomi/Tachidesk-WebUI/pull/86) by @AriaMoradi)
- (r881) migrate ExtensionCard.tsx to Mui 5 ([#81](https://github.com/Suwayomi/Tachidesk-WebUI/pull/81) by @AriaMoradi)
- (r882) migrate SingleSearch to Mui 5 ([#101](https://github.com/Suwayomi/Tachidesk-WebUI/pull/101) by @AriaMoradi)
- (r883) migrate LoadingPlaceholder to Mui 5 ([#96](https://github.com/Suwayomi/Tachidesk-WebUI/pull/96) by @AriaMoradi)
- (r884) migrate About to Mui 5 ([#105](https://github.com/Suwayomi/Tachidesk-WebUI/pull/105) by @AriaMoradi)
- (r885) migrate SourceCard to Mui 5 ([#82](https://github.com/Suwayomi/Tachidesk-WebUI/pull/82) by @AriaMoradi)
- (r886) migrate Manga to Mui 5 ([#99](https://github.com/Suwayomi/Tachidesk-WebUI/pull/99) by @AriaMoradi)
- (r887) migrate Browse to Mui 5 ([#98](https://github.com/Suwayomi/Tachidesk-WebUI/pull/98) by @AriaMoradi)
- (r888) migrate DesktopSideBar to Mui 5 ([#87](https://github.com/Suwayomi/Tachidesk-WebUI/pull/87) by @AriaMoradi)
- (r889) cleanup library ([#107](https://github.com/Suwayomi/Tachidesk-WebUI/pull/107) by @AriaMoradi)
- (r890) support for new searchTerm (by @AriaMoradi)
- (r891) Revert "support for new searchTerm" (by @AriaMoradi)
- (r892) add support for emptySearch ([#109](https://github.com/Suwayomi/Suwayomi-WebUI/pull/109) by @AriaMoradi)
- (r893) add support for MultiSelectListPreference ([#108](https://github.com/Suwayomi/Suwayomi-WebUI/pull/108) by @AriaMoradi)
- (r892) add support for emptySearch ([#109](https://github.com/Suwayomi/Tachidesk-WebUI/pull/109) by @AriaMoradi)
- (r893) add support for MultiSelectListPreference ([#108](https://github.com/Suwayomi/Tachidesk-WebUI/pull/108) by @AriaMoradi)
@@ -469,24 +469,24 @@
## TL;DR
- Fixed ReadComicOnline, Toonily and possibly other sources not working
- Backup and Restore now includes Updates tab data
- Removed Anime support from WebUI, Anime support will also be removed from Suwayomi-Server in a future update
- Removed Anime support from WebUI, Anime support will also be removed from Tachidesk-Server in a future update
## Suwayomi-Server Changelog
## Tachidesk-Server Changelog
- (r973) convert android.jar lib to a maven repo
- (r978) mimic Tachiyomi's behaviour more closely, fixes ReadComicOnline (EN)
- (r980) fix export chapter ordering, include new props in backup
- (r982) remove isNsfw annotation detection
- (r984) use correct time conversion units when doing backups
- (r989) Support using a CatalogueSource instead of only HttpSources ([#219](https://github.com/Suwayomi/Suwayomi-Server/pull/219) by @Syer10)
- (r991) Use a custom task to run electron ([#220](https://github.com/Suwayomi/Suwayomi-Server/pull/220) by @Syer10)
- (r989) Support using a CatalogueSource instead of only HttpSources ([#219](https://github.com/Suwayomi/Tachidesk-Server/pull/219) by @Syer10)
- (r991) Use a custom task to run electron ([#220](https://github.com/Suwayomi/Tachidesk-Server/pull/220) by @Syer10)
## Suwayomi-WebUI Changelog
- (r810) fix wrong strings in set Server Address dialog, fixes [#39](https://github.com/Suwayomi/Suwayomi-WebUI/issues/39)
## Tachidesk-WebUI Changelog
- (r810) fix wrong strings in set Server Address dialog, fixes [#39](https://github.com/Suwayomi/Tachidesk-WebUI/issues/39)
- (r811) fix chapterFetch loop
- (r812) fix overlapping requests
- (r813) fix typo
- (r814) Better portrait support ([#41](https://github.com/Suwayomi/Suwayomi-WebUI/issues/41) by @minhe7735)
- (r815) fixes Reader navbar colors when in light mode ([#43](https://github.com/Suwayomi/Suwayomi-WebUI/issues/43) by @abhijeetChawla)
- (r814) Better portrait support ([#41](https://github.com/Suwayomi/Tachidesk-WebUI/issues/41) by @minhe7735)
- (r815) fixes Reader navbar colors when in light mode ([#43](https://github.com/Suwayomi/Tachidesk-WebUI/issues/43) by @abhijeetChawla)
- (r816) default languages cleanup, force Local source enabled
- (r817) force Local source at LangSelect
- (r818) rename ExtensionLangSelect: generic name for generic use
@@ -500,13 +500,13 @@
- added support for a equivalent page to Tachiyomi's Updates tab
- fix launchers not working on macOS M1/arm64
## Suwayomi-Server Changelog
## Tachidesk-Server Changelog
- (r956) fix macOS-arm64 bundle launchers not working
- (r957) Workaround StdLib issue and add KtLint to all modules ([#206](https://github.com/Suwayomi/Suwayomi-Server/pull/206) by @Syer10)
- (r957) Workaround StdLib issue and add KtLint to all modules ([#206](https://github.com/Suwayomi/Tachidesk-Server/pull/206) by @Syer10)
- (r960-r963) Add recently updated chapters(Updates) endpoint
## Suwayomi-WebUI Changelog
## Tachidesk-WebUI Changelog
- (r808) fix chapter list not calling onlineFetch=true
- (r809) add support for Updates
@@ -517,7 +517,7 @@
- Fixed Local source not working on Windows
- Fixed Chapter numbers being shown incorrectly
## Suwayomi-Server
## Tachidesk-Server
### Public API
#### Non-breaking changes
- N/A
@@ -526,17 +526,17 @@
- N/A
#### Bug fixes
- (r948) Fix ManaToki (KO) and NewToki (KO) (issue [#202](https://github.com/Suwayomi/Suwayomi-Server/issue/202))
- (r948) Fix ManaToki (KO) and NewToki (KO) (issue [#202](https://github.com/Suwayomi/Tachidesk-Server/issue/202))
- (r949) Local source: fix windows paths
### Private API
- (r941) Update BytecodeEditor to use Java NIO Paths ([#200](https://github.com/Suwayomi/Suwayomi-Server/pull/200) by @Syer10)
- (r942) Gradle Updates ([#199](https://github.com/Suwayomi/Suwayomi-Server/pull/199) by @Syer10)
- (r941) Update BytecodeEditor to use Java NIO Paths ([#200](https://github.com/Suwayomi/Tachidesk-Server/pull/200) by @Syer10)
- (r942) Gradle Updates ([#199](https://github.com/Suwayomi/Tachidesk-Server/pull/199) by @Syer10)
## Suwayomi-WebUI
## Tachidesk-WebUI
#### Visible changes
- (r804) update text positioning on Reader and Player ([#35](https://github.com/Suwayomi/Suwayomi-WebUI/pull/35) by @voltrare)
- (r804) update text positioning on Reader and Player ([#35](https://github.com/Suwayomi/Tachidesk-WebUI/pull/35) by @voltrare)
- (r806) Source card for Local source is different
- (r807) add Local source guide
@@ -552,10 +552,10 @@
## TL;DR
- Loading sources' manga list is at least twice as fast
- Added support for Tachiyomi's Local source
- Added BasicAuth support, now you can protect your Suwayomi instance if you are running it on a public server
- Added BasicAuth support, now you can protect your Tachidesk instance if you are running it on a public server
- Added ability to turn off cache for image requests
## Suwayomi-Server
## Tachidesk-Server
### Public API
#### Non-breaking changes
- (r915) add BasicAuth support
@@ -577,18 +577,18 @@
## Suwayomi-WebUI
## Tachidesk-WebUI
#### Visible changes
- (r790) nice looking progress percentage
- (r791) show a Delete button for downloaded chapters
- (r792) Update hover effect using more of Material-UI color pallete ([#29](https://github.com/Suwayomi/Suwayomi-WebUI/pull/29) by @voltrare)
- (r793) Optimize images ([#32](https://github.com/Suwayomi/Suwayomi-WebUI/pull/32) by @phanirithvij)
- (r794) try fix #30 ([#31](https://github.com/Suwayomi/Suwayomi-WebUI/pull/31) by @phanirithvij)
- (r792) Update hover effect using more of Material-UI color pallete ([#29](https://github.com/Suwayomi/Tachidesk-WebUI/pull/29) by @voltrare)
- (r793) Optimize images ([#32](https://github.com/Suwayomi/Tachidesk-WebUI/pull/32) by @phanirithvij)
- (r794) try fix #30 ([#31](https://github.com/Suwayomi/Tachidesk-WebUI/pull/31) by @phanirithvij)
- (r795) fix viewing page number when the string is long
- (r796) show proper display name for source
- (r797) fail gracefully when a thumbnail has errors
- (r798) fix when a source fails to load mangas
- (r800) add Local source ([#31](https://github.com/Suwayomi/Suwayomi-WebUI/pull/31))
- (r800) add Local source ([#31](https://github.com/Suwayomi/Tachidesk-WebUI/pull/31))
- (r803) add support for useCache
#### Bug fixes
@@ -607,13 +607,13 @@
- Added support for configuration of sources, this enables MangaDex, Komga, Cubari and many other sources
- Chapters in the Manga page and Sources in the source page now look nicer and will glow with mouse hover
## Suwayomi-Server
## Tachidesk-Server
### Public API
#### Non-breaking changes
- (r888) add installing APK from external sources endpoint
#### Breaking changes
- (r877 [#188](https://github.com/Suwayomi/Suwayomi-Server/pull/188) by @Syer10) `MangaDataClass.genre` changed type to `List<String>`
- (r877 [#188](https://github.com/Suwayomi/Tachidesk-Server/pull/188) by @Syer10) `MangaDataClass.genre` changed type to `List<String>`
#### Bug fixes
- (r899-r901) fix when an external apk is installed and it doesn't have the default tachiyomi-extensions name
@@ -624,17 +624,17 @@
- (r902) cleanup print/ln instances
- (r906) better handling of uninstalling Extensions
## Suwayomi-WebUI
## Tachidesk-WebUI
#### Visible changes
- (r770) add support for the new genre type
- (r771) set the default value of `showNsfw` to `true` so we won't have visual artifacts with a clean install
- (r774 [#21](https://github.com/Suwayomi/Suwayomi-WebUI/pull/21) by @voltrare) `ReaderNavbar.jsx`: Swap close and retract Navbar buttons
- (r775 [#23](https://github.com/Suwayomi/Suwayomi-WebUI/pull/23) by @voltrare) `yarn.lock`: Fixes version inconsistency after commit 9b866811b
- (r776 [#23](https://github.com/Suwayomi/Suwayomi-WebUI/pull/23) by @voltrare) add margin between Source and Extension cards, make the Search button look nicer
- (r774 [#21](https://github.com/Suwayomi/Tachidesk-WebUI/pull/21) by @voltrare) `ReaderNavbar.jsx`: Swap close and retract Navbar buttons
- (r775 [#23](https://github.com/Suwayomi/Tachidesk-WebUI/pull/23) by @voltrare) `yarn.lock`: Fixes version inconsistency after commit 9b866811b
- (r776 [#23](https://github.com/Suwayomi/Tachidesk-WebUI/pull/23) by @voltrare) add margin between Source and Extension cards, make the Search button look nicer
- (r777) add support for installing external APK files
- (r778) fix the makeToaster?
- (r779) Action button for installing external extension
- (r780 Suwayomi/Suwayomi-WebUI#25) add on hover, active effect to Chapter/Episode card
- (r780 Suwayomi/Tachidesk-WebUI#25) add on hover, active effect to Chapter/Episode card
- (r782-r785) updating material-ui to v5 changed the theme
- (r785-r788) better `SourceCard` looks on mobile, move `SourceDataClass.isConfigurable` gear button to `SourceMangas`
- (r789) implement source configuration
@@ -648,7 +648,7 @@
# Server: v0.4.9 + WebUI: r769
## Suwayomi-Server
## Tachidesk-Server
### Public API
#### Non-breaking changes
- N/A
@@ -677,7 +677,7 @@
- (r873) `publish.yml` and `build_push.yml`: fix oopsies
## Suwayomi-WebUI
## Tachidesk-WebUI
#### Visible changes
- (r767-r769) Support for hiding NSFW content in settings screen, extensions screen, sources screen
@@ -690,14 +690,14 @@
#### Non-code changes
- (r42-r45) Change `README.md`: some links and stuff
- (r45-r765) Add all of the commit history from when WebUI was separated from Server, jumping from r45 to r765 (r45 is exactly the same as r765)
- (r766) Steal `.gitattributes` from Suwayomi-Server
- (r766) Steal `.gitattributes` from Tachidesk-Server
- (r767) Dependency cleanup in `package.json`
# Server: v0.4.8 + WebUI: r41
## Suwayomi-Server
## Tachidesk-Server
### Public API
#### Non-breaking changes
- Added support for serializing Search Filters
@@ -708,19 +708,19 @@
#### Bug fixes
- Fixed a bug where backup restore reversed chapter order
- Open Site feature now works properly (https://github.com/Suwayomi/Suwayomi-WebUI/issues/19)
- Open Site feature now works properly (https://github.com/Suwayomi/Tachidesk-WebUI/issues/19)
### Private API
- Added `CloudflareInterceptor` from TachiWeb-Server
- Restoring backup for mangas in library(merging manga data) is now supported
## Suwayomi-WebUI
## Tachidesk-WebUI
#### Visible changes
- Better looking manga card titles
- Better reader title, next, prev buttons
#### Bug fixes
- Open Site feature now works properly (https://github.com/Suwayomi/Suwayomi-WebUI/issues/19)
- Open Site feature now works properly (https://github.com/Suwayomi/Tachidesk-WebUI/issues/19)
- Re-ordering categories now works
#### Internal changes

View File

@@ -1,6 +1,6 @@
# Contributing
## Where should I start?
Checkout [This Kanban Board](https://github.com/Suwayomi/Suwayomi-Server/projects/1) to see the rough development roadmap.
Checkout [This Kanban Board](https://github.com/Suwayomi/Tachidesk/projects/1) to see the rough development roadmap.
### Important notes
- Notify the developers on [Suwayomi discord](https://discord.gg/DDZdqZWaHA) (#tachidesk-server and #tachidesk-webui channels) or open a WIP pull request before starting if you decide to take on working on anything from/not from the roadmap in order to avoid parallel efforts on the same issue/feature.
@@ -8,56 +8,25 @@ Checkout [This Kanban Board](https://github.com/Suwayomi/Suwayomi-Server/project
- We hate big pull requests, make them as small as possible, change one meaningful thing. Spam pull requests, we don't mind.
### Project goals and vision
- Porting Tachiyomi and covering its features
- Syncing with Tachiyomi, [main issue](https://github.com/Suwayomi/Suwayomi-Server/issues/159)
- Porting Tachiyomi and covering it's features
- Syncing with Tachiyomi, [main issue](https://github.com/Suwayomi/Tachidesk-Server/issues/159)
- Generally rejecting features that Tachiyomi(main app) doesn't have,
- Unless it's something that makes sense for desktop sizes or desktop form factor (keyboard + mouse)
- Additional/crazy features can go in forks and alternative clients
- [Suwayomi-WebUI](https://github.com/Suwayomi/Suwayomi-WebUI) should
- [Tachidesk-WebUI](https://github.com/Suwayomi/Tachidesk-WebUI) should
- be responsive
- support both desktop and mobile form factors well
## How does Suwayomi-Server work?
## How does Tachidesk-Server work?
This project has two components:
1. **Server:** contains the implementation of [tachiyomi's extensions library](https://github.com/tachiyomiorg/extensions-lib) and uses an Android compatibility library to run jar libraries converted from apk extensions. All this concludes to serving a GraphQL API.
2. **WebUI:** A React SPA(`create-react-app`) project that works with the server to do the presentation located at https://github.com/Suwayomi/Suwayomi-WebUI
### API
#### GraphQL
*Only available in the preview at the moment*
The GraphQL API can be queried with a POST request to `/api/graphql`. There is also the GraphiQL IDE accessible by the browser at `/api/graphql` to perform ad-hoc queries and explore the API.
#### REST
> [!WARNING]
>
> Soon to be deprecated
The REST API can be queried at `/api/v1`. An interactive Swagger API explorer is available at `/api/swagger-ui`.
### Tracker client authorization
#### OAuth
Since the url of a Suwayomi-Server is not known, it is not possible to redirect directly to the client.<br/>
Thus, to provide tracker support via oauth, the tracker clients redirect to the [suwayomi website](https://suwayomi.org/)
and there the actual redirection to the client takes place.
When implementing the login process in your client you have to make sure to follow some preconditions:
To be able to redirect to the client you have to attach a `state` object to the query of the auth url
- this `state` object has to have a `redirectUrl` which points to the client route at which you want to handle the auth result
- besides the `redirectUrl` you can pass any information you require to handle the result (e.g. the server `id` of the tracker client)
- example URL for AniList: `https://anilist.co/api/v2/oauth/authorize?client_id=ID&response_type=token&state={ redirectUrl: "http://localhost:4567/handle/oauth/result", trackerId: 1, anyOtherInfo: "your client requires" }`
Once the permission has been granted, you will get redirected to the client at the provided route (`redirectUrl`).<br/>
- Example URL (decoded) for AniList: `http://localhost:4567/handle/oauth/result?access_token=TOKEN&token_type=Bearer&expires_in=31622400&state={ redirectUrl: "http://localhost:4567/handle/oauth/result", trackerId: 1, anyOtherInfo: "your client requires" }`).<br/>
Finally, to finish the login process, you just have to pass this URL to the server as the `callbackUrl`.
1. **Server:** contains the implementation of [tachiyomi's extensions library](https://github.com/tachiyomiorg/extensions-lib) and uses an Android compatibility library to run jar libraries converted from apk extensions. All this concludes to serving a REST API.
2. **WebUI:** A React SPA(`create-react-app`) project that works with the server to do the presentation located at https://github.com/Suwayomi/Tachidesk-WebUI
## Why a web app?
This structure is chosen to
- Achieve the maximum multi-platform-ness
- Gives the ability to access Suwayomi-Server from a remote client e.g., your phone, tablet or smart TV
- Ease development of user interfaces for Suwayomi
- Gives the ability to access Tachidesk-Server from a remote client e.g., your phone, tablet or smart TV
- Ease development of user interfaces for Tachidesk
## Building from source
### Prerequisites
@@ -65,14 +34,14 @@ You need these software packages installed in order to build the project
- Java Development Kit and Java Runtime Environment version 8 or newer(both Oracle JDK and OpenJDK works)
### building the full-blown jar (Suwayomi-Server + Suwayomi-WebUI bundle)
Run `./gradlew server:downloadWebUI server:shadowJar`, the resulting built jar file will be `server/build/Suwayomi-Server-vX.Y.Z-rxxx.jar`.
### building the full-blown jar (Tachidesk-Server + Tachidesk-WebUI bundle)
Run `./gradlew server:downloadWebUI server:shadowJar`, the resulting built jar file will be `server/build/Tachidesk-Server-vX.Y.Z-rxxx.jar`.
### building without `webUI` bundled (server only)
Delete `server/src/main/resources/WebUI.zip` if exists from previous runs, then run `./gradlew server:shadowJar`, the resulting built jar file will be `server/build/Suwayomi-Server-vX.Y.Z-rxxx.jar`.
Delete `server/src/main/resources/WebUI.zip` if exists from previous runs, then run `./gradlew server:shadowJar`, the resulting built jar file will be `server/build/Tachidesk-Server-vX.Y.Z-rxxx.jar`.
### building the Windows package
First Build the jar, then cd into the `scripts` directory and run `./windows-bundler.sh win32` or `./windows-bundler.sh win64` depending on the target architecture, the resulting built zip package file will be `server/build/Suwayomi-Server-vX.Y.Z-rxxx-winXX.zip`.
First Build the jar, then cd into the `scripts` directory and run `./windows-bundler.sh win32` or `./windows-bundler.sh win64` depending on the target architecture, the resulting built zip package file will be `server/build/Tachidesk-Server-vX.Y.Z-rxxx-winXX.zip`.
## Running in development mode
run `./gradlew :server:run --stacktrace` to run the server

107
README.md
View File

@@ -1,11 +1,11 @@
| Build | Stable | Preview | Support Server |
|-----------------------------------------------------------------------------------------------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|---------|
| ![CI](https://github.com/Suwayomi/Suwayomi-Server/actions/workflows/build_push.yml/badge.svg) | [![stable release](https://img.shields.io/github/release/Suwayomi/Suwayomi-Server.svg?maxAge=3600&label=download)](https://github.com/Suwayomi/Suwayomi-Server/releases) | [![preview](https://img.shields.io/badge/dynamic/json?url=https://github.com/Suwayomi/Suwayomi-Server-preview/raw/main/index.json&label=download&query=$.latest&color=blue)](https://github.com/Suwayomi/Suwayomi-Server-preview/releases/latest) | [![Discord](https://img.shields.io/discord/801021177333940224.svg?label=discord&labelColor=7289da&color=2c2f33&style=flat)](https://discord.gg/DDZdqZWaHA) |
| Build | Stable | Preview | Support Server |
|-------|----------|---------|---------|
| ![CI](https://github.com/Suwayomi/Tachidesk/actions/workflows/build_push.yml/badge.svg) | [![stable release](https://img.shields.io/github/release/Suwayomi/Tachidesk.svg?maxAge=3600&label=download)](https://github.com/Suwayomi/Tachidesk/releases) | [![preview](https://img.shields.io/badge/dynamic/json?url=https://github.com/Suwayomi/Tachidesk-preview/raw/main/index.json&label=download&query=$.latest&color=blue)](https://github.com/Suwayomi/Tachidesk-preview/releases/latest) | [![Discord](https://img.shields.io/discord/801021177333940224.svg?label=discord&labelColor=7289da&color=2c2f33&style=flat)](https://discord.gg/DDZdqZWaHA) |
## Table of Content
- [What is Suwayomi?](#what-is-suwayomi)
- [Suwayomi client projects](#Suwayomi-client-projects)
- [What is Tachidesk?](#what-is-tachidesk)
- [Tachidesk client projects](#tachidesk-client-projects)
* [Is this application usable? Should I test it?](#is-this-application-usable-should-i-test-it)
- [Downloading and Running the app](#downloading-and-running-the-app)
* [Using Operating System Specific Bundles](#using-operating-system-specific-bundles)
@@ -13,13 +13,13 @@
+ [Windows](#windows)
+ [macOS](#macos)
+ [GNU/Linux](#gnulinux)
* [Other methods of getting Suwayomi](#other-methods-of-getting-suwayomi)
* [Other methods of getting Tachidesk](#other-methods-of-getting-tachidesk)
+ [Arch Linux](#arch-linux)
+ [Ubuntu-based distributions](#ubuntu-based-distributions)
+ [Docker](#docker)
* [Advanced Methods](#advanced-methods)
+ [Running the jar release directly](#running-the-jar-release-directly)
+ [Using Suwayomi Remotely](#using-suwayomi-remotely)
+ [Using Tachidesk Remotely](#using-tachidesk-remotely)
- [Syncing With Tachiyomi](#syncing-with-tachiyomi)
- [Troubleshooting and Support](#troubleshooting-and-support)
- [Contributing and Technical info](#contributing-and-technical-info)
@@ -27,29 +27,27 @@
- [License](#license)
<!-- Generated with https://ecotrust-canada.github.io/markdown-toc/ -->
# What is Suwayomi?
<img src="https://github.com/Suwayomi/Suwayomi-Server/raw/master/server/src/main/resources/icon/faviconlogo.png" alt="drawing" width="200"/>
# What is Tachidesk?
<img src="https://github.com/Suwayomi/Tachidesk/raw/master/server/src/main/resources/icon/faviconlogo.png" alt="drawing" width="200"/>
A free and open source manga reader server that runs extensions built for [Tachiyomi](https://tachiyomi.org/).
Suwayomi is an independent Tachiyomi compatible software and is **not a Fork of** Tachiyomi.
Tachidesk is an independent Tachiyomi compatible software and is **not a Fork of** Tachiyomi.
Suwayomi-Server is as multi-platform as you can get. Any platform that runs java and/or has a modern browser can run it. This includes Windows, Linux, macOS, chrome OS, etc. Follow [Downloading and Running the app](#downloading-and-running-the-app) for installation instructions.
Tachidesk-Server is as multi-platform as you can get. Any platform that runs java and/or has a modern browser can run it. This includes Windows, Linux, macOS, chrome OS, etc. Follow [Downloading and Running the app](#downloading-and-running-the-app) for installation instructions.
Ability to sync with Tachiyomi is a planned feature, for more info look [here](#syncing-with-tachiyomi).
# Suwayomi client projects
**You need a client/user interface app as a front-end for Suwayomi-Server, if you [Directly Download Suwayomi-Server](https://github.com/Suwayomi/Suwayomi-Server/releases/latest) you'll get a bundled version of [Suwayomi-WebUI](https://github.com/Suwayomi/Suwayomi-WebUI) with it.**
# Tachidesk client projects
**You need a client/user interface app as a front-end for Tachidesk-Server, if you [Directly Download Tachidesk-Server](https://github.com/Suwayomi/Tachidesk-Server/releases/latest) you'll get a bundled version of [Tachidesk-WebUI](https://github.com/Suwayomi/Tachidesk-WebUI) with it.**
Here's a list of known clients/user interfaces for Suwayomi-Server:
##### Actively Developed Clients
- [Suwayomi-WebUI](https://github.com/Suwayomi/Suwayomi-WebUI): The web/ElectronJS front-end that Suwayomi-Server ships with by default.
- [Tachidesk-JUI](https://github.com/Suwayomi/Tachidesk-JUI): The native desktop front-end for Suwayomi-Server. Currently, the most advanced.
Here's a list of known clients/user interfaces for Tachidesk-Server:
##### Actively Developed Cients
- [Tachidesk-WebUI](https://github.com/Suwayomi/Tachidesk-WebUI): The web/ElectronJS front-end that Tachidesk-Server ships with by default.
- [Tachidesk-JUI](https://github.com/Suwayomi/Tachidesk-JUI): The native desktop front-end for Tachidesk-Server. Currently the most advanced.
- [Tachidesk-qtui](https://github.com/Suwayomi/Tachidesk-qtui): A C++/Qt front-end for mobile devices(Android/linux), feature support is basic.
- [Tachidesk-Sorayomi](https://github.com/Suwayomi/Tachidesk-Sorayomi): A Flutter front-end for Desktop(Linux, windows, etc.), Web and Android with a User Interface inspired by Tachiyomi.
- [Tachidesk-VaadinUI](https://github.com/Suwayomi/Tachidesk-VaadinUI): A Web front-end for Suwayomi-Server built with Vaadin.
- [Suwayomi-VUI](https://github.com/Suwayomi/Suwayomi-VUI): A preview focused web frontend built with svelte with some features the other UIs might not have (migration)
##### Inctive/Abandoned Clients
- [Tachidesk-Sorayomi](https://github.com/Suwayomi/Tachidesk-Sorayomi): A Flutter front-end for Desktop(Linux, windows, etc.), Web and Android with a User Inerface inspired by Tachiyomi.
##### Inctive/Abandoned Cients
- [Equinox](https://github.com/Suwayomi/Equinox): A web user interface made with Vue.js.
- [Tachidesk-GTK](https://github.com/mahor1221/Tachidesk-GTK): A native Rust/GTK desktop client.
@@ -63,32 +61,39 @@ Here is a list of current features:
- Backup and restore support powered by Tachiyomi-compatible Backups
- Viewing latest updated chapters.
**Note:** These are capabilities of Suwayomi-Server, the actual working support is provided by each front-end app, checkout their respective readme for more info.
**Note:** These are capabilities of Tachidesk-Server, the actual working support is provided by each front-end app, checkout their respective readme for more info.
# Downloading and Running the app
## Using Operating System Specific Bundles
To facilitate the use of Suwayomi we provide bundle releases that include The Java Runtime Environment, ElectronJS and the Suwayomi-Launcher.
To facilitate the use of Tachidesk we provide bundle releases that include The Java Runtime Environment, ElectronJS and 3 Tachidesk Launcher Scripts.
If a bundle for your operating system or cpu architecture is not provided then refer to [Advanced Methods](#advanced-methods)
If a bundle for your operating system or cpu architecture is not provided then refer to [Advanced Methods](#advanced-methods)
#### Launcher Scripts
- `Tachidesk Electron Launcher`: Launches Tachidesk inside Electron as a desktop applicaton
- `Tachidesk Browser Launcher`: Launches Tachidesk in a browser window
- `Tachidesk Debug Launcher`: Launches Tachidesk with debug logs attached. If Tachidesk doesn't work for you, running this can give you insight into why.
**Node:** Linux launcher scripts are named a bit differently but work the same.
### Windows
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).
Download the latest `win32`(Windows 32-bit) or `win64`(Windows 64-bit) release from [the releases section](https://github.com/Suwayomi/Tachidesk-Server/releases) or a preview one from [the preview repository](https://github.com/Suwayomi/Tachidesk-Server-preview/releases).
Unzip the downloaded file and double-click on one of the launcher scripts.
Unzip the downloaded file and double click on one of the launcher scripts.
### macOS
Download the latest `macOS-x64`(older macOS systems) or `macOS-arm64`(Apple M1 and newer) 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 `macOS-x64`(older macOS systems) or `macOS-arm64`(Apple M1 and newer) release from [the releases section](https://github.com/Suwayomi/Tachidesk-Server/releases) or a preview one from [the preview repository](https://github.com/Suwayomi/Tachidesk-Server-preview/releases).
Unzip the downloaded file and double-click on one of the launcher scripts.
Unzip the downloaded file and double click on one of the launcher scripts.
### GNU/Linux
Download the latest `linux-x64`(x86_64) 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 `linux-x64`(x86_64) release from [the releases section](https://github.com/Suwayomi/Tachidesk-Server/releases) or a preview one from [the preview repository](https://github.com/Suwayomi/Tachidesk-Server-preview/releases).
`tar xvf` the downloaded file and double-click on one of the launcher scripts or run them using the terminal.
`tar xvf` the downloaded file and double click on one of the launcher scripts or run them using the terminal.
## Other methods of getting Suwayomi
## Other methods of getting Tachidesk
### Arch Linux
You can install Suwayomi from the AUR:
You can install Tachidesk from the AUR:
```
yay -S tachidesk
```
@@ -96,20 +101,20 @@ yay -S tachidesk
### Debian/Ubuntu
Download the latest deb package from the release section or Install from the MPR
```
git clone https://mpr.makedeb.org/suwayomi-server.git
cd suwayomi-server
git clone https://mpr.makedeb.org/tachidesk-server.git
cd tachidesk-server
makedeb -si
```
### Ubuntu
```
sudo add-apt-repository ppa:suwayomi/suwayomi-server
sudo add-apt-repository ppa:suwayomi/tachidesk-server
sudo apt update
sudo apt install suwayomi-server
sudo apt install tachidesk-server
```
### Docker
Check our Official Docker release [Suwayomi Container](https://github.com/orgs/Suwayomi/packages/container/package/tachidesk) for running Suwayomi Server in a docker container. Source code for our container is available at [docker-tachidesk](https://github.com/Suwayomi/docker-tachidesk). By default, the server will be running on http://localhost:4567 open this url in your browser.
Check our Official Docker release [Tachidesk Container](https://github.com/orgs/Suwayomi/packages/container/package/tachidesk) for running Tachidesk Server in a docker container. Source code for our container is available at [docker-tachidesk](https://github.com/Suwayomi/docker-tachidesk). By default the server will be running on http://localhost:4567 open this url in your browser.
Install from the command line:
```
@@ -123,35 +128,33 @@ Run Container from the command line:
## Advanced Methods
### Running the jar release directly
In order to run the app you need the following:
- The jar release of Suwayomi-Server
- The jar release of Tachidesk-Server
- The Java Runtime Environment(JRE) 8 or newer
- A Browser like Google Chrome, Firefox, Edge, etc.
- ElectronJS (optional)
Download the latest `.jar` release from [the releases section](https://github.com/Suwayomi/Suwayomi-Server/releases) or a preview jar build from [the preview repository](https://github.com/Suwayomi/Suwayomi-Server-preview/releases).
Download the latest `.jar` release from [the releases section](https://github.com/Suwayomi/Tachidesk-Server/releases) or a preview jar build from [the preview repository](https://github.com/Suwayomi/Tachidesk-preview/releases).
Make sure you have The Java Runtime Environment installed on your system, Double-click on the jar file or run `java -jar Suwayomi-Server-vX.Y.Z-rxxxx.jar` from a Terminal/Command Prompt window to run the app which will open a new browser window automatically.
Make sure you have The Java Runtime Environment installed on your system, Double click on the jar file or run `java -jar Tachidesk-vX.Y.Z-rxxxx.jar` from a Terminal/Command Prompt window to run the app which will open a new browser window automatically.
### Using Suwayomi Remotely
You can run Suwayomi on your computer or a server and connect to it remotely through one of our clients or the bundled web interface with a web browser. This method of using Suwayomi is requiring a bit of networking/firewall/port forwarding/server configuration/etc. knowledge on your side, if you can run a Minecraft server and configure it, then you are good to go.
### Using Tachidesk Remotely
You can run Tachidesk on your computer or a server and connect to it remotely through one of our clients or the bundled web interface with a web browser. This method of using Tachidesk is requires a bit of networking/firewall/port forwarding/server configuration/etc. knowledge on your side, if you can run a Minecraft server and configure it, then you are good to go.
Check out [this wiki page](https://github.com/Suwayomi/Suwayomi-Server/wiki/Configuring-Tachidesk-Server) for a guide on configuring Suwayomi-Server.
Check out [this wiki page](https://github.com/Suwayomi/Tachidesk-Server/wiki/Configuring-Tachidesk-Server) for a guide on configuring Tachidesk-Server.
If you face issues with your setup then we are happy to provide help, just join our discord server(a discord badge is on the top of the page, you are just a click-clack away!).
If you face issues with your setup then we are happy to provide help, just join our discord server(a discord badge is on the top of the page, you are just a click clack away!).
## Syncing With Tachiyomi
### The Suwayomi extension and tracker
- You can install the `Suwayomi` extension inside tachiyomi.
- The extension will load your Suwayomi library.
- By manipulating extension search filters you can browse your categories.
- You can enable the Suwayomi tracker to track reading progress with your Suwayomi server.
- Note: Tachiyomi [only allows tracking one way](https://github.com/tachiyomiorg/tachiyomi/issues/1626), meaning that by reading chapters on other Suwayomi clients the last read chapter number will update on the tracker but tachiyomi won't automatically mark them as read for you.
### The Tachidesk extension
- You can install the `Tachidesk` extension inside tachiyomi.
- The extension will load Tachidesk library.
- By manipulating filters you can browse your categories.
### Other methods
Checkout [this issue](https://github.com/Suwayomi/Suwayomi-Server/issues/159) for tracking progress.
Checkout [this issue](https://github.com/Suwayomi/Tachidesk-Server/issues/159) for tracking progress.
## Troubleshooting and Support
See [this troubleshooting wiki page](https://github.com/Suwayomi/Suwayomi-Server/wiki/Troubleshooting).
See [this troubleshooting wiki page](https://github.com/Suwayomi/Tachidesk/wiki/Troubleshooting).
## Contributing and Technical info
See [CONTRIBUTING.md](./CONTRIBUTING.md).

View File

@@ -1,11 +1,12 @@
import org.jetbrains.kotlin.gradle.tasks.KotlinJvmCompile
import org.jlleitschuh.gradle.ktlint.KtlintExtension
import org.jlleitschuh.gradle.ktlint.KtlintPlugin
import org.jmailen.gradle.kotlinter.tasks.FormatTask
import org.jmailen.gradle.kotlinter.tasks.LintTask
@Suppress("DSL_SCOPE_VIOLATION")
plugins {
alias(libs.plugins.kotlin.jvm)
alias(libs.plugins.kotlin.serialization)
alias(libs.plugins.ktlint)
alias(libs.plugins.kotlinter)
alias(libs.plugins.buildconfig) apply false
alias(libs.plugins.download)
}
@@ -18,7 +19,7 @@ allprojects {
repositories {
mavenCentral()
google()
maven("https://github.com/Suwayomi/Suwayomi-Server/raw/android-jar/")
maven("https://github.com/Suwayomi/Tachidesk-Server/raw/android-jar/")
maven("https://jitpack.io")
}
}
@@ -31,25 +32,20 @@ subprojects {
}
}
plugins.withType<KtlintPlugin> {
extensions.configure<KtlintExtension>("ktlint") {
version.set(libs.versions.ktlint.get())
filter {
exclude("**/generated/**")
}
}
}
tasks {
withType<KotlinJvmCompile> {
dependsOn("ktlintFormat")
dependsOn("formatKotlin")
kotlinOptions {
jvmTarget = JavaVersion.VERSION_1_8.toString()
freeCompilerArgs += listOf(
"-Xcontext-receivers",
)
}
}
withType<LintTask> {
source(files("src/kotlin"))
}
withType<FormatTask> {
source(files("src/kotlin"))
}
}
}
}

View File

@@ -7,5 +7,5 @@ repositories {
}
dependencies {
implementation(libs.zip4j)
}
implementation("net.lingala.zip4j:zip4j:2.9.0")
}

View File

@@ -1,9 +0,0 @@
rootProject.name = "buildSrc"
dependencyResolutionManagement {
versionCatalogs {
create("libs") {
from(files("../gradle/libs.versions.toml"))
}
}
}

View File

@@ -12,22 +12,20 @@ const val MainClass = "suwayomi.tachidesk.MainKt"
// should be bumped with each stable release
val tachideskVersion = System.getenv("ProductVersion") ?: "v0.7.0"
val webUIRevisionTag = System.getenv("WebUIRevision") ?: "r1397"
val webUIRevisionTag = System.getenv("WebUIRevision") ?: "r1045"
// counts commits on the current checked out branch
val getTachideskRevision = {
runCatching {
System.getenv("ProductRevision") ?: ProcessBuilder()
.command("git", "rev-list", "HEAD", "--count")
.start()
.let { process ->
process.waitFor()
val output = process.inputStream.use {
it.bufferedReader().use(BufferedReader::readText)
}
process.destroy()
"r" + output.trim()
// counts commits on the master branch
val tachideskRevision = runCatching {
System.getenv("ProductRevision") ?: ProcessBuilder()
.command("git", "rev-list", "HEAD", "--count")
.start()
.let { process ->
process.waitFor()
val output = process.inputStream.use {
it.bufferedReader().use(BufferedReader::readText)
}
}.getOrDefault("r0")
}
process.destroy()
"r" + output.trim()
}
}.getOrDefault("r0")

View File

@@ -1,18 +1,15 @@
[versions]
kotlin = "1.9.10"
coroutines = "1.7.3"
serialization = "1.6.0"
kotlin = "1.8.0"
coroutines = "1.6.4"
serialization = "1.4.1"
okhttp = "5.0.0-alpha.11" # Major version is locked by Tachiyomi extensions
javalin = "4.6.8" # Javalin 5.0.0+ requires Java 11
javalin = "4.6.6" # 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
dex2jar = "v60"
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
@@ -27,21 +24,17 @@ coroutines-test = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-test", ve
# Serialization
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-jvm", version.ref = "xmlserialization" }
serialization-xml = { module = "io.github.pdvrieze.xmlutil:serialization-jvm", version.ref = "xmlserialization" }
# Logging
slf4japi = "org.slf4j:slf4j-api:2.0.9"
logback = "ch.qos.logback:logback-classic:1.3.11"
slf4japi = "org.slf4j:slf4j-api:2.0.6"
logback = "ch.qos.logback:logback-classic:1.3.5"
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.3.0"
# Javalin api
@@ -51,11 +44,6 @@ jackson-databind = { module = "com.fasterxml.jackson.core:jackson-databind", ver
jackson-kotlin = { module = "com.fasterxml.jackson.module:jackson-module-kotlin", version.ref = "jackson" }
jackson-annotations = { module = "com.fasterxml.jackson.core:jackson-annotations", version.ref = "jackson" }
# 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-scalars = "com.graphql-java:graphql-java-extended-scalars:20.2"
# Exposed ORM
exposed-core = { module = "org.jetbrains.exposed:exposed-core", version.ref = "exposed" }
exposed-dao = { module = "org.jetbrains.exposed:exposed-dao", version.ref = "exposed" }
@@ -67,7 +55,7 @@ h2 = "com.h2database:h2:1.4.200" # current database driver, can't update to h2 v
exposed-migrations = "com.github.Suwayomi:exposed-migrations:3.2.0"
# Dependency Injection
kodein = "org.kodein.di:kodein-di-conf-jvm:7.20.2"
kodein = "org.kodein.di:kodein-di-conf-jvm:7.15.0"
# tray icon
systemtray-core = "com.dorkbox:SystemTray:4.2.1"
@@ -77,7 +65,7 @@ systemtray-desktop = "com.dorkbox:Desktop:1.0"
# dependencies of Tachiyomi extensions
injekt = "com.github.inorichi.injekt:injekt-core:65b0440"
rxjava = "io.reactivex:rxjava:1.3.8"
jsoup = "org.jsoup:jsoup:1.16.1"
jsoup = "org.jsoup:jsoup:1.15.3"
# Config
config = "com.typesafe:config:1.4.2"
@@ -90,7 +78,7 @@ sort = "com.github.gpanther:java-nat-sort:natural-comparator-1.1"
android-stubs = "com.github.Suwayomi:android-jar:1.0.0"
# Asm modificiation
asm = "org.ow2.asm:asm:9.5" # version locked by Dex2Jar
asm = "org.ow2.asm:asm:9.4" # version locked by Dex2Jar
dex2jar-translator = { module = "com.github.ThexXTURBOXx.dex2jar:dex-translator", version.ref = "dex2jar" }
dex2jar-tools = { module = "com.github.ThexXTURBOXx.dex2jar:dex-tools", version.ref = "dex2jar" }
@@ -103,15 +91,14 @@ xmlpull = "xmlpull:xmlpull:1.1.3.4a"
# Disk & File
appdirs = "net.harawata:appdirs:1.2.1"
zip4j = "net.lingala.zip4j:zip4j:2.11.5"
commonscompress = "org.apache.commons:commons-compress:1.24.0"
junrar = "com.github.junrar:junrar:7.5.5"
zip4j = "net.lingala.zip4j:zip4j:2.11.2"
junrar = "com.github.junrar:junrar:7.5.3"
# AES/CBC/PKCS7Padding Cypher provider
bouncycastle = "org.bouncycastle:bcprov-jdk18on:1.76"
bouncycastle = "org.bouncycastle:bcprov-jdk18on:1.72"
# AndroidX annotations
android-annotations = "androidx.annotation:annotation:1.7.0"
android-annotations = "androidx.annotation:annotation:1.5.0"
# Substitute for duktape-android
rhino-runtime = { module = "org.mozilla:rhino-runtime", version.ref = "rhino" } # slimmer version of 'org.mozilla:rhino'
@@ -122,7 +109,7 @@ settings-core = { module = "com.russhwolf:multiplatform-settings-jvm", version.r
settings-serialization = { module = "com.russhwolf:multiplatform-settings-serialization-jvm", version.ref = "settings" }
# ICU4J
icu4j = "com.ibm.icu:icu4j:73.2"
icu4j = "com.ibm.icu:icu4j:72.1"
# Image Decoding implementation provider
twelvemonkeys-common-lang = { module = "com.twelvemonkeys.common:common-lang", version.ref = "twelvemonkeys" }
@@ -134,13 +121,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.7"
# cron scheduler
cron4j = "it.sauronsoftware.cron4j:cron4j:2.2.5"
# cron-utils
cronUtils = "com.cronutils:cron-utils:9.2.1"
mockk = "io.mockk:mockk:1.13.2"
[plugins]
# Kotlin
@@ -148,16 +129,16 @@ 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 = "11.6.0"}
kotlinter = { id = "org.jmailen.kotlinter", version = "3.12.0"}
# Build config
buildconfig = { id = "com.github.gmazzo.buildconfig", version = "3.1.0"}
# Download
download = { id = "de.undercouch.download", version = "5.4.0"}
download = { id = "de.undercouch.download", version = "5.3.0"}
# ShadowJar
shadowjar = { id = "com.github.johnrengelman.shadow", version = "8.1.1"}
shadowjar = { id = "com.github.johnrengelman.shadow", version = "7.1.2"}
[bundles]
shared = [
@@ -166,7 +147,6 @@ shared = [
"coroutines-core",
"coroutines-jdk8",
"serialization-json",
"serialization-json-okio",
"serialization-protobuf",
"kodein",
"slf4japi",
@@ -192,7 +172,6 @@ okhttp = [
"okhttp-core",
"okhttp-logging",
"okhttp-dnsoverhttps",
"okhttp-brotli",
]
javalin = [
"javalin-core",

View File

@@ -1,5 +1,5 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-8.2-bin.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-7.6-bin.zip
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists

View File

@@ -30,14 +30,12 @@ main() {
RELEASE_NAME="$(echo "${JAR%.*}" | xargs basename)-$OS"
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"
local electron_version="v14.0.0"
# clean temporary directory on function return
trap "rm -rf $RELEASE_NAME/" RETURN
mkdir "$RELEASE_NAME/"
download_launcher
case "$OS" in
debian-all)
RELEASE="$RELEASE_NAME.deb"
@@ -51,57 +49,65 @@ main() {
move_release_to_output_dir
;;
linux-x64)
# 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="OpenJDK8U-jre_x64_linux_hotspot_8u302b08.tar.gz"
JRE_RELEASE="jdk8u302-b08"
JRE_DIR="$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_jre_and_electron
PLAYWRIGHT_PLATFORM="linux"
setup_playwright
RELEASE="$RELEASE_NAME.tar.gz"
make_linux_bundle
move_release_to_output_dir
;;
macOS-x64)
# 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="OpenJDK8U-jre_x64_mac_hotspot_8u302b08.tar.gz"
JRE_RELEASE="jdk8u302-b08"
JRE_DIR="$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_jre_and_electron
PLAYWRIGHT_PLATFORM="mac"
setup_playwright
RELEASE="$RELEASE_NAME.zip"
make_macos_bundle
move_release_to_output_dir
;;
macOS-arm64)
# 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="zulu8.56.0.23-ca-jre8.0.302-macosx_aarch64.tar.gz"
JRE_RELEASE="zulu8.56.0.23-ca-jre8.0.302-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_jre_and_electron
PLAYWRIGHT_PLATFORM="mac-arm64"
setup_playwright
RELEASE="$RELEASE_NAME.zip"
make_macos_bundle
move_release_to_output_dir
;;
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="OpenJDK8U-jre_x86-32_windows_hotspot_8u292b10.zip"
JRE_RELEASE="jdk8u292-b10"
JRE_DIR="$JRE_RELEASE-jre"
JRE_URL="https://github.com/adoptium/temurin8-binaries/releases/download/$JRE_RELEASE/$JRE"
JRE_URL="https://github.com/AdoptOpenJDK/openjdk8-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
PLAYWRIGHT_PLATFORM="win64"
setup_playwright
RELEASE="$RELEASE_NAME.zip"
make_windows_bundle
move_release_to_output_dir
@@ -111,15 +117,18 @@ main() {
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="OpenJDK8U-jre_x64_windows_hotspot_8u302b08.zip"
JRE_RELEASE="jdk8u302-b08"
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_jre_and_electron
PYTHON=Winpython64-3.10.9.0dot.exe
PYTHON_URL=https://github.com/winpython/winpython/releases/download/5.3.20221233/Winpython64-3.10.9.0dot.exe
setup_undetected_chromedriver_and_python
RELEASE="$RELEASE_NAME.zip"
make_windows_bundle
move_release_to_output_dir
@@ -142,12 +151,6 @@ move_release_to_output_dir() {
mv "$RELEASE" "$OUTPUT_DIR/"
}
download_launcher() {
LAUNCHER_URL=$(curl -s "https://api.github.com/repos/Suwayomi/Suwayomi-Launcher/releases/latest" | grep "browser_download_url" | grep ".jar" | head -n 1 | cut -d '"' -f 4)
curl -L "$LAUNCHER_URL" -o "Suwayomi-Launcher.jar"
mv "Suwayomi-Launcher.jar" "$RELEASE_NAME/Suwayomi-Launcher.jar"
}
download_jre_and_electron() {
if [ ! -f "$JRE" ]; then
curl -L "$JRE_URL" -o "$JRE"
@@ -164,9 +167,6 @@ download_jre_and_electron() {
fi
mv "$JRE_DIR" "$RELEASE_NAME/jre"
unzip "$ELECTRON" -d "$RELEASE_NAME/electron/"
mkdir "$RELEASE_NAME/bin"
tree
}
@@ -174,26 +174,29 @@ copy_linux_package_assets_to() {
local output_dir
output_dir="$(readlink -e "$1" || exit 1)"
cp "scripts/resources/pkg/suwayomi-server.sh" "$output_dir/"
cp "scripts/resources/pkg/suwayomi-server.desktop" "$output_dir/"
cp "scripts/resources/pkg/suwayomi-launcher.sh" "$output_dir/"
cp "scripts/resources/pkg/suwayomi-launcher.desktop" "$output_dir/"
cp "scripts/resources/pkg/tachidesk-server-browser-launcher.sh" "$output_dir/"
cp "scripts/resources/pkg/tachidesk-server-debug-launcher.sh" "$output_dir/"
cp "scripts/resources/pkg/tachidesk-server-electron-launcher.sh" "$output_dir/"
cp "scripts/resources/pkg/tachidesk-server.desktop" "$output_dir/"
cp "scripts/resources/pkg/systemd"/* "$output_dir/"
cp "server/src/main/resources/icon/faviconlogo-128.png" \
"$output_dir/suwayomi-server.png"
cp "server/src/main/resources/icon/faviconlogo.png" \
"$output_dir/tachidesk-server.png"
}
make_linux_bundle() {
cp "$JAR" "$RELEASE_NAME/bin/Suwayomi-Server.jar"
cp "scripts/resources/suwayomi-launcher.sh" "$RELEASE_NAME/"
cp "scripts/resources/suwayomi-server.sh" "$RELEASE_NAME/"
cp "$JAR" "$RELEASE_NAME/Tachidesk-Server.jar"
cp "scripts/resources/tachidesk-server-browser-launcher.sh" "$RELEASE_NAME/"
cp "scripts/resources/tachidesk-server-debug-launcher.sh" "$RELEASE_NAME/"
cp "scripts/resources/tachidesk-server-electron-launcher.sh" "$RELEASE_NAME/"
tar -I "gzip -9" -cvf "$RELEASE" "$RELEASE_NAME/"
}
make_macos_bundle() {
cp "$JAR" "$RELEASE_NAME/bin/Suwayomi-Server.jar"
cp "scripts/resources/Suwayomi Launcher.command" "$RELEASE_NAME/"
cp "$JAR" "$RELEASE_NAME/Tachidesk-Server.jar"
cp "scripts/resources/Tachidesk Browser Launcher.command" "$RELEASE_NAME/"
cp "scripts/resources/Tachidesk Debug Launcher.command" "$RELEASE_NAME/"
cp "scripts/resources/Tachidesk Electron Launcher.command" "$RELEASE_NAME/"
zip -9 -r "$RELEASE" "$RELEASE_NAME/"
}
@@ -202,13 +205,12 @@ make_macos_bundle() {
# https://www.debian.org/doc/manuals/packaging-tutorial/packaging-tutorial.pdf
make_deb_package() {
#behind $RELEASE_VERSION is hyphen "-"
local source_dir="suwayomi-server-$RELEASE_VERSION"
local source_dir="tachidesk-server-$RELEASE_VERSION"
#behind $RELEASE_VERSION is underscore "_"
local upstream_source="suwayomi-server_$RELEASE_VERSION.orig.tar.gz"
local upstream_source="tachidesk-server_$RELEASE_VERSION.orig.tar.gz"
mkdir "$RELEASE_NAME/$source_dir/"
mv "$RELEASE_NAME/Suwayomi-Launcher.jar" "$RELEASE_NAME/$source_dir/Suwayomi-Launcher.jar"
cp "$JAR" "$RELEASE_NAME/$source_dir/Suwayomi-Server.jar"
cp "$JAR" "$RELEASE_NAME/$source_dir/Tachidesk-Server.jar"
copy_linux_package_assets_to "$RELEASE_NAME/$source_dir/"
tar -I "gzip" -C "$RELEASE_NAME/" -cvf "$upstream_source" "$source_dir"
@@ -216,13 +218,12 @@ make_deb_package() {
sed -i "s/\$pkgver/$RELEASE_VERSION/" "$RELEASE_NAME/$source_dir/debian/changelog"
sed -i "s/\$pkgrel/1/" "$RELEASE_NAME/$source_dir/debian/changelog"
sudo apt update
sudo apt install devscripts build-essential dh-exec
cd "$RELEASE_NAME/$source_dir/"
dpkg-buildpackage --no-sign --build=all
cd -
local deb="suwayomi-server_$RELEASE_VERSION-1_all.deb"
local deb="tachidesk-server_$RELEASE_VERSION-1_all.deb"
mv "$RELEASE_NAME/$deb" "$RELEASE"
}
@@ -255,15 +256,16 @@ make_windows_bundle() {
#WINEARCH=win32 wine "$rcedit" "$RELEASE_NAME/electron/electron.exe" \
# --set-icon "$icon"
cp "$JAR" "$RELEASE_NAME/bin/Suwayomi-Server.jar"
cp "scripts/resources/Suwayomi Launcher.bat" "$RELEASE_NAME"
cp "$JAR" "$RELEASE_NAME/Tachidesk-Server.jar"
cp "scripts/resources/Tachidesk Browser Launcher.bat" "$RELEASE_NAME"
cp "scripts/resources/Tachidesk Debug Launcher.bat" "$RELEASE_NAME"
cp "scripts/resources/Tachidesk Electron Launcher.bat" "$RELEASE_NAME"
zip -9 -r "$RELEASE" "$RELEASE_NAME"
}
make_windows_package() {
if [ "$CI" = true ]; then
sudo apt update
sudo apt install -y wixl
fi
@@ -274,16 +276,31 @@ make_windows_package() {
| wixl-heat --var var.SourceDir -p "$RELEASE_NAME/" \
--directory-ref electron --component-group electron >"$RELEASE_NAME/electron.wxs"
find "$RELEASE_NAME/bin" \
| wixl-heat --var var.SourceDir -p "$RELEASE_NAME/" \
--directory-ref bin --component-group bin >"$RELEASE_NAME/bin.wxs"
local icon="server/src/main/resources/icon/faviconlogo.ico"
local arch=${OS##*-}
wixl -D ProductVersion="$RELEASE_VERSION" -D SourceDir="$RELEASE_NAME" \
-D Icon="$icon" --arch "$arch" "scripts/resources/msi/suwayomi-server-$arch.wxs" \
"$RELEASE_NAME/jre.wxs" "$RELEASE_NAME/electron.wxs" "$RELEASE_NAME/bin.wxs" -o "$RELEASE"
-D Icon="$icon" --arch "$arch" "scripts/resources/msi/tachidesk-server-$arch.wxs" \
"$RELEASE_NAME/jre.wxs" "$RELEASE_NAME/electron.wxs" -o "$RELEASE"
}
setup_python() {
mkdir "$RELEASE_NAME/"
curl -L "$PYTHON_URL" -o "$PYTHON"
7z x $PYTHON
mv WPy64-31090/python-3.10.9.amd64 "$RELEASE_NAME/python"
}
setup_undetected_chromedriver() {
curl -L "https://github.com/Suwayomi/undetected-chromedriver/archive/refs/heads/master.zip" -o undetected-chromedriver-master.zip
unzip undetected-chromedriver-master.zip
mv undetected-chromedriver-master "$RELEASE_NAME/undetected-chromedriver"
}
setup_undetected_chromedriver_and_python() {
setup_python
setup_undetected_chromedriver
}
# Error handler

View File

@@ -1 +0,0 @@
start "" jre/bin/javaw -jar Suwayomi-Launcher.jar

View File

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

View File

@@ -0,0 +1 @@
start "" jre/bin/javaw -jar Tachidesk-Server.jar

View File

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

View File

@@ -0,0 +1,7 @@
:: cleaner output
@echo off
jre\bin\java -Dsuwayomi.tachidesk.config.server.debugLogsEnabled=true -jar Tachidesk-Server.jar
:: Prevent cmd from closing when Tachidesk crashes
pause

View File

@@ -0,0 +1,3 @@
cd "`dirname "$0"`"
./jre/Contents/Home/bin/java -Dsuwayomi.tachidesk.config.server.debugLogsEnabled=true -jar Tachidesk-Server.jar

View File

@@ -0,0 +1 @@
jre\bin\javaw "-Dsuwayomi.tachidesk.config.server.webUIInterface=electron" "-Dsuwayomi.tachidesk.config.server.electronPath=electron/electron.exe" -jar Tachidesk-Server.jar

View File

@@ -0,0 +1,3 @@
cd "`dirname "$0"`"
./jre/Contents/Home/bin/java "-Dsuwayomi.tachidesk.config.server.webUIInterface=electron" "-Dsuwayomi.tachidesk.config.server.electronPath=electron/Electron.app/Contents/MacOS/Electron" -jar Tachidesk-Server.jar

View File

@@ -1,5 +1,5 @@
suwayomi-server ($pkgver-$pkgrel) unstable; urgency=medium
tachidesk-server ($pkgver-$pkgrel) unstable; urgency=medium
* See CHANGELOG.md on https://github.com/Suwayomi/Suwayomi-Server
* See CHANGELOG.md on https://github.com/Suwayomi/Tachidesk-Server
-- Mahor1221 <mahor1221@pm.me> Fri, 14 Jan 2022 00:00:00 +0000

View File

@@ -1,14 +1,14 @@
Source: suwayomi-server
Source: tachidesk-server
Section: web
Priority: optional
Maintainer: Mahor1221 <mahor1221@pm.me>
Build-Depends: debhelper-compat (= 13), dh-exec
Standards-Version: 4.5.1
Homepage: https://github.com/Suwayomi/Suwayomi-Server
Homepage: https://github.com/Suwayomi/Tachidesk-Server
Package: suwayomi-server
Package: tachidesk-server
Architecture: all
Depends: ${misc:Depends}, java8-runtime, libc++-dev
Depends: ${misc:Depends}, java8-runtime-headless, 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.
Tachidesk is an independent Tachiyomi compatible software and is not a Fork of Tachiyomi.

View File

@@ -1,7 +1,7 @@
Format: https://www.debian.org/doc/packaging-manuals/copyright-format/1.0/
Upstream-Name: suwayomi-server
Upstream-Name: tachidesk-server
Upstream-Contact: https://discord.gg/DDZdqZWaHA
Source: https://github.com/Suwayomi/Suwayomi-Server
Source: https://github.com/Suwayomi/Tachidesk-Server
Files: *
Copyright: Contributors to the Suwayomi project

View File

@@ -1,13 +1,12 @@
#!/usr/bin/dh-exec
Suwayomi-Server.jar usr/share/java/suwayomi-server/bin/
Suwayomi-Launcher.jar usr/share/java/suwayomi-server/
suwayomi-server.png usr/share/pixmaps/
suwayomi-server.desktop usr/share/applications/
suwayomi-launcher.desktop usr/share/applications/
suwayomi-server.service usr/lib/systemd/system/
suwayomi-server.sysusers => usr/lib/sysusers.d/suwayomi-server.conf
suwayomi-server.tmpfiles => usr/lib/tmpfiles.d/suwayomi-server.conf
suwayomi-server.conf => etc/suwayomi/server.conf
suwayomi-server.sh => usr/bin/suwayomi-server
suwayomi-launcher.sh => usr/bin/suwayomi-launcher
Tachidesk-Server.jar usr/share/java/tachidesk-server/
tachidesk-server.png usr/share/pixmaps/
tachidesk-server.desktop usr/share/applications/
tachidesk-server.service usr/lib/systemd/system/
tachidesk-server.sysusers => usr/lib/sysusers.d/tachidesk-server.conf
tachidesk-server.tmpfiles => usr/lib/tmpfiles.d/tachidesk-server.conf
tachidesk-server.conf => etc/tachidesk/server.conf
tachidesk-server-browser-launcher.sh => usr/bin/tachidesk-server-browser
tachidesk-server-debug-launcher.sh => usr/bin/tachidesk-server-debug
tachidesk-server-electron-launcher.sh => usr/bin/tachidesk-server-electron

View File

@@ -1 +1 @@
suwayomi-server.png
tachidesk-server.png

View File

@@ -1,64 +0,0 @@
<?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" />
<Condition Message="This version of Windows does not support deploying 64-bit packages.">
VersionNT64
</Condition>
<!-- Directory -->
<Directory Id="TARGETDIR" Name="SourceDir">
<Directory Id="ProgramFiles64Folder">
<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="*" Win64="yes">
<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

@@ -1,60 +0,0 @@
<?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

@@ -0,0 +1,86 @@
<?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="Tachidesk Server" Manufacturer="Suwayomi">
<Package InstallerVersion="300" Compressed="yes" />
<Media Id="1" Cabinet="Tachidesk_Server.cab" EmbedCab="yes" />
<Condition Message="This version of Windows does not support deploying 64-bit packages.">
VersionNT64
</Condition>
<!-- Directory -->
<Directory Id="TARGETDIR" Name="SourceDir">
<Directory Id="ProgramFiles64Folder">
<Directory Id="INSTALLDIR" Name="Tachidesk-Server" >
<Directory Id="jre"/>
<Directory Id="electron"/>
</Directory>
</Directory>
<Directory Id="ProgramMenuFolder">
<Directory Id="ProgramMenuDir" Name="Tachidesk-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="TachideskJAR" Guid="*" Win64="yes">
<File Id="Tachidesk-Server.jar" Source="$(var.SourceDir)/Tachidesk-Server.jar" KeyPath="yes" />
</Component>
<Component Id="TachideskBrowserBAT" Guid="*" Win64="yes">
<File Id="TachideskBrowser.bat" Source="$(var.SourceDir)/Tachidesk Browser Launcher.bat" KeyPath="yes" >
<Shortcut Id="TachideskBrowser.lnk" Name="Tachidesk Browser" Directory="INSTALLDIR"
WorkingDirectory="INSTALLDIR" Icon="Tachidesk.ico" IconIndex="0" Advertise="yes" />
<Shortcut Id="DesktopTachideskBrowser.lnk" Name="Tachidesk Browser" Directory="DesktopFolder"
WorkingDirectory="INSTALLDIR" Icon="Tachidesk.ico" IconIndex="0" Advertise="yes" />
<Shortcut Id="ProgramMenuTachideskBrowser.lnk" Name="Tachidesk Browser" Directory="ProgramMenuDir"
WorkingDirectory="INSTALLDIR" Icon="Tachidesk.ico" IconIndex="0" Advertise="yes"
Description="A free and open source manga reader that runs extensions built for Tachiyomi." />
</File>
</Component>
<Component Id="TachideskDebugBAT" Guid="*" Win64="yes">
<File Id="TachideskDebug.bat" Source="$(var.SourceDir)/Tachidesk Debug Launcher.bat" KeyPath="yes">
<Shortcut Id="TachideskDebug.lnk" Name="Tachidesk Debug" Directory="INSTALLDIR"
WorkingDirectory="INSTALLDIR" Icon="Tachidesk.ico" IconIndex="0" Advertise="yes" />
<Shortcut Id="ProgramMenuTachideskDebug.lnk" Name="Tachidesk Debug" Directory="ProgramMenuDir"
WorkingDirectory="INSTALLDIR" Icon="Tachidesk.ico" IconIndex="0" Advertise="yes"
Description="Launches Tachidesk with debug logs attached. If Tachidesk doesn't work for you, running this can give you insight into why." />
</File>
</Component>
<Component Id="TachideskElectronBAT" Guid="*" Win64="yes">
<File Id="TachideskElectron.bat" Source="$(var.SourceDir)/Tachidesk Electron Launcher.bat" KeyPath="yes">
<Shortcut Id="TachideskElectron.lnk" Name="Tachidesk Electron" Directory="INSTALLDIR"
WorkingDirectory="INSTALLDIR" Icon="Tachidesk.ico" IconIndex="0" Advertise="yes" />
<Shortcut Id="DesktopTachideskElectron.lnk" Name="Tachidesk Electron" Directory="DesktopFolder"
WorkingDirectory="INSTALLDIR" Icon="Tachidesk.ico" IconIndex="0" Advertise="yes" />
<Shortcut Id="ProgramMenuTachideskElectron.lnk" Name="Tachidesk Electron" Directory="ProgramMenuDir"
WorkingDirectory="INSTALLDIR" Icon="Tachidesk.ico" IconIndex="0" Advertise="yes"
Description="Launches Tachidesk inside Electron as a desktop applicaton." />
</File>
</Component>
</DirectoryRef>
<!-- Feature -->
<Feature Id="Tachidesk_Server" Title="Tachidesk-Server" Level="1">
<ComponentGroupRef Id="jre" />
<ComponentRef Id="TachideskJAR" />
<ComponentRef Id="TachideskBrowserBAT" />
<ComponentRef Id="TachideskDebugBAT" />
<ComponentRef Id="ProgramMenuDir" />
<ComponentGroupRef Id="electron" />
<ComponentRef Id="TachideskElectronBAT" />
</Feature>
<Icon Id="Tachidesk.ico" SourceFile="$(var.Icon)" />
<Property Id="ARPPRODUCTICON" Value="Tachidesk.ico" /> <!-- Icon in Add/Remove Programs -->
</Product>
</Wix>

View File

@@ -0,0 +1,82 @@
<?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="Tachidesk Server" Manufacturer="Suwayomi">
<Package InstallerVersion="300" Compressed="yes" />
<Media Id="1" Cabinet="Tachidesk_Server.cab" EmbedCab="yes" />
<!-- Directory -->
<Directory Id="TARGETDIR" Name="SourceDir">
<Directory Id="ProgramFilesFolder">
<Directory Id="INSTALLDIR" Name="Tachidesk-Server" >
<Directory Id="jre"/>
<Directory Id="electron"/>
</Directory>
</Directory>
<Directory Id="ProgramMenuFolder">
<Directory Id="ProgramMenuDir" Name="Tachidesk-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="TachideskJAR" Guid="*">
<File Id="Tachidesk-Server.jar" Source="$(var.SourceDir)/Tachidesk-Server.jar" KeyPath="yes" />
</Component>
<Component Id="TachideskBrowserBAT" Guid="*">
<File Id="TachideskBrowser.bat" Source="$(var.SourceDir)/Tachidesk Browser Launcher.bat" KeyPath="yes" >
<Shortcut Id="TachideskBrowser.lnk" Name="Tachidesk Browser" Directory="INSTALLDIR"
WorkingDirectory="INSTALLDIR" Icon="Tachidesk.ico" IconIndex="0" Advertise="yes" />
<Shortcut Id="DesktopTachideskBrowser.lnk" Name="Tachidesk Browser" Directory="DesktopFolder"
WorkingDirectory="INSTALLDIR" Icon="Tachidesk.ico" IconIndex="0" Advertise="yes" />
<Shortcut Id="ProgramMenuTachideskBrowser.lnk" Name="Tachidesk Browser" Directory="ProgramMenuDir"
WorkingDirectory="INSTALLDIR" Icon="Tachidesk.ico" IconIndex="0" Advertise="yes"
Description="A free and open source manga reader that runs extensions built for Tachiyomi." />
</File>
</Component>
<Component Id="TachideskDebugBAT" Guid="*">
<File Id="TachideskDebug.bat" Source="$(var.SourceDir)/Tachidesk Debug Launcher.bat" KeyPath="yes">
<Shortcut Id="TachideskDebug.lnk" Name="Tachidesk Debug" Directory="INSTALLDIR"
WorkingDirectory="INSTALLDIR" Icon="Tachidesk.ico" IconIndex="0" Advertise="yes" />
<Shortcut Id="ProgramMenuTachideskDebug.lnk" Name="Tachidesk Debug" Directory="ProgramMenuDir"
WorkingDirectory="INSTALLDIR" Icon="Tachidesk.ico" IconIndex="0" Advertise="yes"
Description="Launches Tachidesk with debug logs attached. If Tachidesk doesn't work for you, running this can give you insight into why." />
</File>
</Component>
<Component Id="TachideskElectronBAT" Guid="*">
<File Id="TachideskElectron.bat" Source="$(var.SourceDir)/Tachidesk Electron Launcher.bat" KeyPath="yes">
<Shortcut Id="TachideskElectron.lnk" Name="Tachidesk Electron" Directory="INSTALLDIR"
WorkingDirectory="INSTALLDIR" Icon="Tachidesk.ico" IconIndex="0" Advertise="yes" />
<Shortcut Id="DesktopTachideskElectron.lnk" Name="Tachidesk Electron" Directory="DesktopFolder"
WorkingDirectory="INSTALLDIR" Icon="Tachidesk.ico" IconIndex="0" Advertise="yes" />
<Shortcut Id="ProgramMenuTachideskElectron.lnk" Name="Tachidesk Electron" Directory="ProgramMenuDir"
WorkingDirectory="INSTALLDIR" Icon="Tachidesk.ico" IconIndex="0" Advertise="yes"
Description="Launches Tachidesk inside Electron as a desktop applicaton." />
</File>
</Component>
</DirectoryRef>
<!-- Feature -->
<Feature Id="Tachidesk_Server" Title="Tachidesk-Server" Level="1">
<ComponentGroupRef Id="jre" />
<ComponentRef Id="TachideskJAR" />
<ComponentRef Id="TachideskBrowserBAT" />
<ComponentRef Id="TachideskDebugBAT" />
<ComponentRef Id="ProgramMenuDir" />
<ComponentGroupRef Id="electron" />
<ComponentRef Id="TachideskElectronBAT" />
</Feature>
<Icon Id="Tachidesk.ico" SourceFile="$(var.Icon)" />
<Property Id="ARPPRODUCTICON" Value="Tachidesk.ico" /> <!-- Icon in Add/Remove Programs -->
</Product>
</Wix>

View File

@@ -1,8 +0,0 @@
[Desktop Entry]
Type=Application
Name=Suwayomi-Launcher
Comment=Manga Reader
Exec=/usr/bin/java -jar /usr/share/java/suwayomi-server/Suwayomi-Launcher.jar "\\$@"
Icon=suwayomi-server
Terminal=false
Categories=Network;

View File

@@ -1,3 +0,0 @@
#!/bin/sh
exec /usr/bin/java -jar /usr/share/java/suwayomi-server/Suwayomi-Launcher.jar

View File

@@ -1,8 +0,0 @@
[Desktop Entry]
Type=Application
Name=Suwayomi-Server
Comment=Manga Reader
Exec=/usr/bin/java -jar /usr/share/java/suwayomi-server/bin/Suwayomi-Server.jar "\\$@"
Icon=suwayomi-server
Terminal=false
Categories=Network;

View File

@@ -1,3 +0,0 @@
#!/bin/sh
exec /usr/bin/java -jar /usr/share/java/suwayomi-server/bin/Suwayomi-Server.jar

View File

@@ -1,2 +0,0 @@
#Type Name ID GECOS Home directory Shell
u suwayomi-server - "Suwayomi Manga Server" /var/lib/suwayomi

View File

@@ -1,2 +0,0 @@
#Type Path Mode User Group Age Argument
d /var/lib/suwayomi 0755 suwayomi-server suwayomi-server

View File

@@ -1,4 +1,4 @@
TACHIDESK_ROOT_DIR="/var/lib/suwayomi"
TACHIDESK_ROOT_DIR="/var/lib/tachidesk"
# Extra arguments passed to the java command
# The default value disables the system tray icon, and launching a browser on service start.

View File

@@ -5,12 +5,12 @@ After=network-online.target
[Service]
Type=simple
User=suwayomi-server
Group=suwayomi-server
SyslogIdentifier=suwayomi-server
User=tachidesk
Group=tachidesk
SyslogIdentifier=tachidesk
EnvironmentFile=/etc/suwayomi/server.conf
ExecStart=/usr/bin/java $JAVA_ARGS -Dsuwayomi.tachidesk.config.server.rootDir="${TACHIDESK_ROOT_DIR}" -jar /usr/share/java/suwayomi-server/bin/Suwayomi-Server.jar
EnvironmentFile=/etc/tachidesk/server.conf
ExecStart=/usr/bin/java $JAVA_ARGS -Dsuwayomi.tachidesk.config.server.rootDir="${TACHIDESK_ROOT_DIR}" -jar /usr/share/java/tachidesk-server/Tachidesk-Server.jar
Restart=on-failure
ProtectSystem=full

View File

@@ -0,0 +1,2 @@
#Type Name ID GECOS Home directory Shell
u tachidesk - "Tachidesk Manga Server" /var/lib/tachidesk

View File

@@ -0,0 +1,2 @@
#Type Path Mode User Group Age Argument
d /var/lib/tachidesk 0755 tachidesk tachidesk

View File

@@ -0,0 +1,3 @@
#!/bin/sh
exec /usr/bin/java -jar /usr/share/java/tachidesk-server/Tachidesk-Server.jar

View File

@@ -0,0 +1,5 @@
#!/bin/sh
exec /usr/bin/java \
-Dsuwayomi.tachidesk.config.server.debugLogsEnabled=true \
-jar /usr/share/java/tachidesk-server/Tachidesk-Server.jar

View File

@@ -0,0 +1,12 @@
#!/bin/sh
if [ ! -f /usr/bin/electron ]; then
echo "Electron executable was not found!
In order to run this launcher, you need Electron installed."
exit 1
fi
exec /usr/bin/java \
-Dsuwayomi.tachidesk.config.server.webUIInterface=electron \
-Dsuwayomi.tachidesk.config.server.electronPath=/usr/bin/electron \
-jar /usr/share/java/tachidesk-server/Tachidesk-Server.jar

View File

@@ -0,0 +1,8 @@
[Desktop Entry]
Type=Application
Name=Tachidesk-Server
Comment=Manga Reader
Exec=/usr/bin/java -jar /usr/share/java/tachidesk-server/Tachidesk-Server.jar "\\$@"
Icon=tachidesk-server
Terminal=false
Categories=Network;

View File

@@ -1,3 +0,0 @@
#!/bin/sh
exec ./jre/bin/java -jar ./Suwayomi-Launcher.jar

View File

@@ -1,3 +0,0 @@
#!/bin/sh
exec ./jre/bin/java -jar ./bin/Suwayomi-Server.jar

View File

@@ -0,0 +1,3 @@
#!/bin/sh
exec ./jre/bin/java -jar ./Tachidesk-Server.jar

View File

@@ -0,0 +1,5 @@
#!/bin/sh
exec ./jre/bin/java \
-Dsuwayomi.tachidesk.config.server.debugLogsEnabled=true \
-jar ./Tachidesk-Server.jar

View File

@@ -0,0 +1,6 @@
#!/bin/sh
exec ./jre/bin/java \
-Dsuwayomi.tachidesk.config.server.webUIInterface=electron \
-Dsuwayomi.tachidesk.config.server.electronPath=./electron/electron \
-jar ./Tachidesk-Server.jar

View File

@@ -1,11 +1,11 @@
import de.undercouch.gradle.tasks.download.Download
import org.jetbrains.kotlin.gradle.tasks.KotlinJvmCompile
import java.time.Instant
@Suppress("DSL_SCOPE_VIOLATION")
plugins {
id(libs.plugins.kotlin.jvm.get().pluginId)
id(libs.plugins.kotlin.serialization.get().pluginId)
id(libs.plugins.ktlint.get().pluginId)
id(libs.plugins.kotlinter.get().pluginId)
application
alias(libs.plugins.shadowjar)
id(libs.plugins.buildconfig.get().pluginId)
@@ -24,11 +24,6 @@ dependencies {
implementation(libs.bundles.javalin)
implementation(libs.bundles.jackson)
// GraphQL
implementation(libs.graphql.kotlin.server)
implementation(libs.graphql.kotlin.scheme)
implementation(libs.graphql.scalars)
// Exposed ORM
implementation(libs.bundles.exposed)
implementation(libs.h2)
@@ -45,10 +40,6 @@ dependencies {
implementation(libs.rxjava)
implementation(libs.jsoup)
// ComicInfo
implementation(libs.serialization.xml.core)
implementation(libs.serialization.xml)
// Sort
implementation(libs.sort)
@@ -57,7 +48,6 @@ dependencies {
// Disk & File
implementation(libs.zip4j)
implementation(libs.commonscompress)
implementation(libs.junrar)
// AES/CBC/PKCS7Padding Cypher provider for zh.copymanga
@@ -72,17 +62,12 @@ dependencies {
implementation(kotlin("script-runtime"))
testImplementation(libs.mockk)
implementation(libs.cron4j)
implementation(libs.cronUtils)
}
application {
applicationDefaultJvmArgs =
listOf(
"-Djunrar.extractor.thread-keep-alive-seconds=30",
)
applicationDefaultJvmArgs = listOf(
"-Djunrar.extractor.thread-keep-alive-seconds=30"
)
mainClass.set(MainClass)
}
@@ -96,7 +81,7 @@ sourceSets {
buildConfig {
className("BuildConfig")
packageName("suwayomi.tachidesk.server.generated")
packageName("suwayomi.tachidesk.server")
useKotlinOutput()
@@ -104,31 +89,33 @@ buildConfig {
buildConfigField("String", "NAME", quoteWrap(rootProject.name))
buildConfigField("String", "VERSION", quoteWrap(tachideskVersion))
buildConfigField("String", "REVISION", quoteWrap(getTachideskRevision()))
buildConfigField("String", "REVISION", quoteWrap(tachideskRevision))
buildConfigField("String", "BUILD_TYPE", quoteWrap(if (System.getenv("ProductBuildType") == "Stable") "Stable" else "Preview"))
buildConfigField("long", "BUILD_TIME", Instant.now().epochSecond.toString())
buildConfigField("String", "WEBUI_REPO", quoteWrap("https://github.com/Suwayomi/Tachidesk-WebUI-preview"))
buildConfigField("String", "WEBUI_TAG", quoteWrap(webUIRevisionTag))
buildConfigField("String", "GITHUB", quoteWrap("https://github.com/Suwayomi/Suwayomi-Server"))
buildConfigField("String", "GITHUB", quoteWrap("https://github.com/Suwayomi/Tachidesk-Server"))
buildConfigField("String", "DISCORD", quoteWrap("https://discord.gg/DDZdqZWaHA"))
}
tasks {
shadowJar {
isZip64 = true
manifest {
attributes(
"Main-Class" to MainClass,
"Implementation-Title" to rootProject.name,
"Implementation-Vendor" to "The Suwayomi Project",
"Specification-Version" to tachideskVersion,
"Implementation-Version" to getTachideskRevision(),
"Implementation-Version" to tachideskRevision
)
}
archiveBaseName.set(rootProject.name)
archiveVersion.set(tachideskVersion)
archiveClassifier.set(getTachideskRevision())
archiveClassifier.set(tachideskRevision)
destinationDirectory.set(File("$rootDir/server/build"))
}
@@ -140,34 +127,25 @@ tasks {
}
}
withType<KotlinJvmCompile> {
kotlinOptions {
freeCompilerArgs +=
listOf(
"-opt-in=kotlinx.serialization.ExperimentalSerializationApi",
)
}
}
named<Copy>("processResources") {
duplicatesStrategy = DuplicatesStrategy.INCLUDE
mustRunAfter("downloadWebUI")
}
register<Download>("downloadWebUI") {
src("https://github.com/Suwayomi/Suwayomi-WebUI-preview/releases/download/$webUIRevisionTag/Suwayomi-WebUI-$webUIRevisionTag.zip")
src("https://github.com/Suwayomi/Tachidesk-WebUI-preview/releases/download/$webUIRevisionTag/Tachidesk-WebUI-$webUIRevisionTag.zip")
dest("src/main/resources/WebUI.zip")
fun shouldOverwrite(): Boolean {
val zipPath = project.projectDir.absolutePath + "/src/main/resources/WebUI.zip"
val zipFile = net.lingala.zip4j.ZipFile(zipPath)
val zipFile = net.lingala.zip4j.ZipFile(zipPath)
var shouldOverwrite = true
if (zipFile.isValidZipFile) {
val zipRevision =
zipFile.getInputStream(zipFile.getFileHeader("revision")).bufferedReader().use {
it.readText().trim()
}
val zipRevision = zipFile.getInputStream(zipFile.getFileHeader("revision")).bufferedReader().use {
it.readText().trim()
}
if (zipRevision == webUIRevisionTag) {
shouldOverwrite = false
@@ -184,12 +162,11 @@ tasks {
group = "application"
finalizedBy(run)
doFirst {
application.applicationDefaultJvmArgs =
listOf(
"-Dsuwayomi.tachidesk.config.server.webUIInterface=electron",
// Change this to the installed electron application
"-Dsuwayomi.tachidesk.config.server.electronPath=/usr/bin/electron",
)
application.applicationDefaultJvmArgs = listOf(
"-Dsuwayomi.tachidesk.config.server.webUIInterface=electron",
// Change this to the installed electron application
"-Dsuwayomi.tachidesk.config.server.electronPath=/usr/bin/electron"
)
}
}
}

View File

@@ -9,11 +9,15 @@ package eu.kanade.tachiyomi
import android.app.Application
import android.content.Context
// import android.content.res.Configuration
// import android.support.multidex.MultiDex
// import timber.log.Timber
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())

View File

@@ -1,34 +1,14 @@
package eu.kanade.tachiyomi
import suwayomi.tachidesk.manga.impl.util.storage.ImageUtil
import suwayomi.tachidesk.server.generated.BuildConfig
/**
* Used by extensions.
*
* @since extension-lib 1.3
*/
object AppInfo {
/**
*
* should be something like 74
*
* @since extension-lib 1.3
*/
fun getVersionCode() = BuildConfig.REVISION.substring(1).toInt()
/** should be something like 74 */
fun getVersionCode() = suwayomi.tachidesk.server.BuildConfig.REVISION.substring(1).toInt()
/**
* should be something like "0.13.1"
*
* @since extension-lib 1.3
*/
fun getVersionName() = BuildConfig.VERSION.substring(1)
/**
* A list of supported image MIME types by the reader.
* e.g. ["image/jpeg", "image/png", ...]
*
* @since extension-lib 1.5
*/
fun getSupportedImageMimeTypes(): List<String> = ImageUtil.ImageType.entries.map { it.mime }
/** should be something like "0.13.1" */
fun getVersionName() = suwayomi.tachidesk.server.BuildConfig.VERSION.substring(1)
}

View File

@@ -28,6 +28,7 @@ import uy.kohesive.injekt.api.addSingletonFactory
import uy.kohesive.injekt.api.get
class AppModule(val app: Application) : InjektModule {
override fun InjektRegistrar.registerInjectables() {
addSingleton(app)

View File

@@ -8,9 +8,8 @@ import kotlinx.coroutines.withContext
/**
* Util for evaluating JavaScript in sources.
*/
class JavaScriptEngine(
@Suppress("UNUSED_PARAMETER") context: Context,
) {
class JavaScriptEngine(context: Context) {
/**
* Evaluate arbitrary JavaScript code and get the result as a primitive type
* (e.g., String, Int).
@@ -20,10 +19,9 @@ class JavaScriptEngine(
* @return Result of JavaScript code as a primitive type.
*/
@Suppress("UNUSED", "UNCHECKED_CAST")
suspend fun <T> evaluate(script: String): T =
withContext(Dispatchers.IO) {
QuickJs.create().use {
it.evaluate(script) as T
}
suspend fun <T> evaluate(script: String): T = withContext(Dispatchers.IO) {
QuickJs.create().use {
it.evaluate(script) as T
}
}
}

View File

@@ -33,10 +33,7 @@ class MemoryCookieJar : CookieJar {
}
@Synchronized
override fun saveFromResponse(
url: HttpUrl,
cookies: List<Cookie>,
) {
override fun saveFromResponse(url: HttpUrl, cookies: List<Cookie>) {
val cookiesToAdd = cookies.map { WrappedCookie.wrap(it) }
cache.removeAll(cookiesToAdd)

View File

@@ -7,119 +7,52 @@ package eu.kanade.tachiyomi.network
* 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 android.content.Context
// import eu.kanade.tachiyomi.BuildConfig
// import eu.kanade.tachiyomi.data.preference.PreferencesHelper
// import okhttp3.HttpUrl.Companion.toHttpUrl
// import okhttp3.dnsoverhttps.DnsOverHttps
// import okhttp3.logging.HttpLoggingInterceptor
// import uy.kohesive.injekt.injectLazy
import android.content.Context
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 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
import okhttp3.logging.HttpLoggingInterceptor
import suwayomi.tachidesk.manga.impl.util.source.GetCatalogueSource
import java.io.File
import java.net.CookieHandler
import java.net.CookieManager
import java.net.CookiePolicy
import suwayomi.tachidesk.server.serverConfig
import java.util.concurrent.TimeUnit
@Suppress("UNUSED_PARAMETER")
class NetworkHelper(context: Context) {
// private val preferences: PreferencesHelper by injectLazy()
// private val preferences: PreferencesHelper by injectLazy()
// private val cacheDir = File(context.cacheDir, "network_cache")
// private val cacheSize = 5L * 1024 * 1024 // 5 MiB
// Tachidesk -->
val cookieStore = PersistentCookieStore(context)
init {
CookieHandler.setDefault(
CookieManager(cookieStore, CookiePolicy.ACCEPT_ALL),
)
}
// Tachidesk <--
private val userAgent =
MutableStateFlow(
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) " +
"AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36",
)
fun defaultUserAgentProvider(): String {
return userAgent.value
}
init {
@OptIn(DelicateCoroutinesApi::class)
userAgent
.drop(1)
.onEach {
GetCatalogueSource.unregisterAllCatalogueSources() // need to reset the headers
}
.launchIn(GlobalScope)
}
val cookieManager = PersistentCookieJar(context)
private val baseClientBuilder: OkHttpClient.Builder
get() {
val builder =
OkHttpClient.Builder()
.cookieJar(PersistentCookieJar(cookieStore))
.connectTimeout(30, TimeUnit.SECONDS)
.readTimeout(30, TimeUnit.SECONDS)
.callTimeout(2, TimeUnit.MINUTES)
.cache(
Cache(
directory = File.createTempFile("tachidesk_network_cache", null),
maxSize = 5L * 1024 * 1024, // 5 MiB
),
)
.addInterceptor(UncaughtExceptionInterceptor())
.addInterceptor(UserAgentInterceptor(::defaultUserAgentProvider))
.addNetworkInterceptor(IgnoreGzipInterceptor())
.addNetworkInterceptor(BrotliInterceptor)
val builder = OkHttpClient.Builder()
.cookieJar(cookieManager)
.connectTimeout(30, TimeUnit.SECONDS)
.readTimeout(30, TimeUnit.SECONDS)
.callTimeout(2, TimeUnit.MINUTES)
.addInterceptor(UserAgentInterceptor())
// if (preferences.verboseLogging().get()) {
val httpLoggingInterceptor =
HttpLoggingInterceptor(
object : HttpLoggingInterceptor.Logger {
val logger = KotlinLogging.logger { }
override fun log(message: String) {
logger.debug { message }
}
},
).apply {
if (serverConfig.debugLogsEnabled) {
val httpLoggingInterceptor = HttpLoggingInterceptor().apply {
level = HttpLoggingInterceptor.Level.BASIC
}
builder.addNetworkInterceptor(httpLoggingInterceptor)
// }
builder.addInterceptor(httpLoggingInterceptor)
}
builder.addInterceptor(
CloudflareInterceptor(setUserAgent = { userAgent.value = it }),
)
// when (preferences.dohProvider().get()) {
// PREF_DOH_CLOUDFLARE -> builder.dohCloudflare()
// PREF_DOH_GOOGLE -> builder.dohGoogle()
// PREF_DOH_ADGUARD -> builder.dohAdGuard()
// PREF_DOH_QUAD9 -> builder.dohQuad9()
// PREF_DOH_ALIDNS -> builder.dohAliDNS()
// PREF_DOH_DNSPOD -> builder.dohDNSPod()
// PREF_DOH_360 -> builder.doh360()
// PREF_DOH_QUAD101 -> builder.dohQuad101()
// PREF_DOH_MULLVAD -> builder.dohMullvad()
// PREF_DOH_CONTROLD -> builder.dohControlD()
// PREF_DOH_NJALLA -> builder.dohNajalla()
// PREF_DOH_SHECAN -> builder.dohShecan()
// }
// when (preferences.dohProvider()) {
// PREF_DOH_CLOUDFLARE -> builder.dohCloudflare()
// PREF_DOH_GOOGLE -> builder.dohGoogle()
// }
return builder
}
@@ -127,5 +60,14 @@ class NetworkHelper(context: Context) {
// val client by lazy { baseClientBuilder.cache(Cache(cacheDir, cacheSize)).build() }
val client by lazy { baseClientBuilder.build() }
val cloudflareClient by lazy { client }
val cloudflareClient by lazy {
client.newBuilder()
.addInterceptor(CloudflareInterceptor())
.build()
}
// Tachidesk -->
val cookies: PersistentCookieStore
get() = cookieManager.store
// Tachidesk <--
}

View File

@@ -1,65 +1,94 @@
package eu.kanade.tachiyomi.network
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.suspendCancellableCoroutine
import kotlinx.serialization.DeserializationStrategy
import kotlinx.serialization.ExperimentalSerializationApi
import kotlinx.serialization.decodeFromString
import kotlinx.serialization.json.Json
import kotlinx.serialization.json.okio.decodeFromBufferedSource
import kotlinx.serialization.serializer
import okhttp3.Call
import okhttp3.Callback
import okhttp3.MediaType.Companion.toMediaType
import okhttp3.OkHttpClient
import okhttp3.Request
import okhttp3.Response
import okhttp3.internal.closeQuietly
import rx.Observable
import rx.Producer
import rx.Subscription
import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.fullType
import java.io.IOException
import java.util.concurrent.atomic.AtomicBoolean
import kotlin.coroutines.resumeWithException
val jsonMime = "application/json; charset=utf-8".toMediaType()
fun Call.asObservable(): Observable<Response> {
return Observable.unsafeCreate { subscriber ->
// Since Call is a one-shot type, clone it for each new subscriber.
val call = clone()
// Wrap the call in a helper which handles both unsubscription and backpressure.
val requestArbiter =
object : AtomicBoolean(), Producer, Subscription {
override fun request(n: Long) {
if (n == 0L || !compareAndSet(false, true)) return
val requestArbiter = object : AtomicBoolean(), Producer, Subscription {
override fun request(n: Long) {
if (n == 0L || !compareAndSet(false, true)) return
try {
val response = call.execute()
if (!subscriber.isUnsubscribed) {
subscriber.onNext(response)
subscriber.onCompleted()
}
} catch (error: Exception) {
if (!subscriber.isUnsubscribed) {
subscriber.onError(error)
}
try {
val response = call.execute()
if (!subscriber.isUnsubscribed) {
subscriber.onNext(response)
subscriber.onCompleted()
}
} catch (error: Exception) {
if (!subscriber.isUnsubscribed) {
subscriber.onError(error)
}
}
override fun unsubscribe() {
// call.cancel()
}
override fun isUnsubscribed(): Boolean {
return call.isCanceled()
}
}
override fun unsubscribe() {
// call.cancel()
}
override fun isUnsubscribed(): Boolean {
return call.isCanceled()
}
}
subscriber.add(requestArbiter)
subscriber.setProducer(requestArbiter)
}
}
// Based on https://github.com/gildor/kotlin-coroutines-okhttp
suspend fun Call.await(): Response {
return suspendCancellableCoroutine { continuation ->
enqueue(
object : Callback {
override fun onResponse(call: Call, response: Response) {
if (!response.isSuccessful) {
continuation.resumeWithException(HttpException(response.code))
return
}
continuation.resume(response) {
response.body?.closeQuietly()
}
}
override fun onFailure(call: Call, e: IOException) {
// Don't bother with resuming the continuation if it is already cancelled.
if (continuation.isCancelled) return
continuation.resumeWithException(e)
}
}
)
continuation.invokeOnCancellation {
try {
cancel()
} catch (ex: Throwable) {
// Ignore cancel exception
}
}
}
}
fun Call.asObservableSuccess(): Observable<Response> {
return asObservable()
.doOnNext { response ->
@@ -70,93 +99,41 @@ fun Call.asObservableSuccess(): Observable<Response> {
}
}
// 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 =
object : Callback {
override fun onResponse(
call: Call,
response: Response,
) {
continuation.resume(response) {
response.body.close()
}
}
// fun OkHttpClient.newCallWithProgress(request: Request, listener: ProgressListener): Call {
// val progressClient = newBuilder()
// .cache(nasObservableSuccessull)
// .addNetworkInterceptor { chain ->
// val originalResponse = chain.proceed(chain.request())
// originalResponse.newBuilder()
// .body(ProgressResponseBody(originalResponse.body!!, listener))
// .build()
// }
// .build()
//
// return progressClient.newCall(request)
// }
override fun onFailure(
call: Call,
e: IOException,
) {
// Don't bother with resuming the continuation if it is already cancelled.
if (continuation.isCancelled) return
val exception = IOException(e.message, e).apply { stackTrace = callStack }
continuation.resumeWithException(exception)
}
}
enqueue(callback)
continuation.invokeOnCancellation {
try {
cancel()
} catch (ex: Throwable) {
// Ignore cancel exception
}
@Suppress("UNUSED_PARAMETER")
fun OkHttpClient.newCallWithProgress(request: Request, listener: ProgressListener): Call {
val progressClient = newBuilder()
.cache(null)
.addNetworkInterceptor { chain ->
val originalResponse = chain.proceed(chain.request())
originalResponse.newBuilder()
.body(ProgressResponseBody(originalResponse.body!!, listener))
.build()
}
}
}
suspend fun Call.await(): Response {
val callStack = Exception().stackTrace.run { copyOfRange(1, size) }
return await(callStack)
}
/**
* @since extensions-lib 1.5
*/
suspend fun Call.awaitSuccess(): Response {
val callStack = Exception().stackTrace.run { copyOfRange(1, size) }
val response = await(callStack)
if (!response.isSuccessful) {
response.close()
throw HttpException(response.code).apply { stackTrace = callStack }
}
return response
}
fun OkHttpClient.newCachelessCallWithProgress(
request: Request,
listener: ProgressListener,
): Call {
val progressClient =
newBuilder()
.cache(null)
.addNetworkInterceptor { chain ->
val originalResponse = chain.proceed(chain.request())
originalResponse.newBuilder()
.body(ProgressResponseBody(originalResponse.body, listener))
.build()
}
.build()
.build()
return progressClient.newCall(request)
}
context(Json)
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 {
return response.body.source().use {
decodeFromBufferedSource(deserializer, it)
// Avoiding Injekt.get<Json>() due to compiler issues
val json = Injekt.getInstance<Json>(fullType<Json>().type)
this.use {
val responseBody = it.body?.string().orEmpty()
return json.decodeFromString(responseBody)
}
}

View File

@@ -1,15 +1,16 @@
package eu.kanade.tachiyomi.network
import android.content.Context
import okhttp3.Cookie
import okhttp3.CookieJar
import okhttp3.HttpUrl
// from TachiWeb-Server
class PersistentCookieJar(private val store: PersistentCookieStore) : CookieJar {
override fun saveFromResponse(
url: HttpUrl,
cookies: List<Cookie>,
) {
class PersistentCookieJar(context: Context) : CookieJar {
val store = PersistentCookieStore(context)
override fun saveFromResponse(url: HttpUrl, cookies: List<Cookie>) {
store.addAll(url, cookies)
}

View File

@@ -4,35 +4,25 @@ import android.content.Context
import okhttp3.Cookie
import okhttp3.HttpUrl
import okhttp3.HttpUrl.Companion.toHttpUrlOrNull
import okio.withLock
import java.net.CookieStore
import java.net.HttpCookie
import java.net.URI
import java.util.concurrent.ConcurrentHashMap
import java.util.concurrent.locks.ReentrantLock
import kotlin.time.Duration.Companion.milliseconds
import kotlin.time.Duration.Companion.seconds
// from TachiWeb-Server
class PersistentCookieStore(context: Context) : CookieStore {
class PersistentCookieStore(context: Context) {
private val cookieMap = ConcurrentHashMap<String, List<Cookie>>()
private val prefs = context.getSharedPreferences("cookie_store", Context.MODE_PRIVATE)
private val lock = ReentrantLock()
init {
val domains =
prefs.all.keys.map { it.substringBeforeLast(".") }
.toSet()
domains.forEach { domain ->
val cookies = prefs.getStringSet(domain, emptySet())
if (!cookies.isNullOrEmpty()) {
for ((key, value) in prefs.all) {
@Suppress("UNCHECKED_CAST")
val cookies = value as? Set<String>
if (cookies != null) {
try {
val url = "http://$domain".toHttpUrlOrNull() ?: return@forEach
val nonExpiredCookies =
cookies.mapNotNull { Cookie.parse(url, it) }
.filter { !it.hasExpired() }
cookieMap[domain] = nonExpiredCookies
val url = "http://$key".toHttpUrlOrNull() ?: continue
val nonExpiredCookies = cookies.mapNotNull { Cookie.parse(url, it) }
.filter { !it.hasExpired() }
cookieMap.put(key, nonExpiredCookies)
} catch (e: Exception) {
// Ignore
}
@@ -40,169 +30,50 @@ class PersistentCookieStore(context: Context) : CookieStore {
}
}
fun addAll(
url: HttpUrl,
cookies: List<Cookie>,
) {
lock.withLock {
val uri = url.toUri()
@Synchronized
fun addAll(url: HttpUrl, cookies: List<Cookie>) {
val key = url.toUri().host
// Append or replace the cookies for this domain.
val cookiesForDomain = cookieMap[uri.host].orEmpty().toMutableList()
for (cookie in cookies) {
// Find a cookie with the same name. Replace it if found, otherwise add a new one.
val pos = cookiesForDomain.indexOfFirst { it.name == cookie.name }
if (pos == -1) {
cookiesForDomain.add(cookie)
} else {
cookiesForDomain[pos] = cookie
}
// Append or replace the cookies for this domain.
val cookiesForDomain = cookieMap[key].orEmpty().toMutableList()
for (cookie in cookies) {
// Find a cookie with the same name. Replace it if found, otherwise add a new one.
val pos = cookiesForDomain.indexOfFirst { it.name == cookie.name }
if (pos == -1) {
cookiesForDomain.add(cookie)
} else {
cookiesForDomain[pos] = cookie
}
cookieMap[uri.host] = cookiesForDomain
saveToDisk(uri)
}
cookieMap.put(key, cookiesForDomain)
// Get cookies to be stored in disk
val newValues = cookiesForDomain.asSequence()
.filter { it.persistent && !it.hasExpired() }
.map(Cookie::toString)
.toSet()
prefs.edit().putStringSet(key, newValues).apply()
}
override fun removeAll(): Boolean {
return lock.withLock {
val wasNotEmpty = cookieMap.isEmpty()
prefs.edit().clear().apply()
cookieMap.clear()
wasNotEmpty
}
@Synchronized
fun removeAll() {
prefs.edit().clear().apply()
cookieMap.clear()
}
fun remove(uri: URI) {
lock.withLock {
prefs.edit().remove(uri.host).apply()
cookieMap.remove(uri.host)
}
prefs.edit().remove(uri.host).apply()
cookieMap.remove(uri.host)
}
override fun get(uri: URI): List<HttpCookie> =
get(uri.host).map {
it.toHttpCookie()
}
fun get(url: HttpUrl) = get(url.toUri().host)
fun get(url: HttpUrl): List<Cookie> {
return get(url.toUri().host ?: return emptyList())
}
override fun add(
uri: URI?,
cookie: HttpCookie,
) {
@Suppress("NAME_SHADOWING")
val uri = uri ?: URI("http://" + cookie.domain.removePrefix("."))
lock.withLock {
val cookies = cookieMap[uri.host]
cookieMap[uri.host] = cookies.orEmpty() + cookie.toCookie(uri)
saveToDisk(uri)
}
}
override fun getCookies(): List<HttpCookie> {
return cookieMap.values.flatMap {
it.map {
it.toHttpCookie()
}
}
}
override fun getURIs(): List<URI> {
return cookieMap.keys().toList().map {
URI("http://$it")
}
}
override fun remove(
uri: URI?,
cookie: HttpCookie,
): Boolean {
@Suppress("NAME_SHADOWING")
val uri = uri ?: URI("http://" + cookie.domain.removePrefix("."))
return lock.withLock {
val cookies = cookieMap[uri.host].orEmpty()
val index =
cookies.indexOfFirst {
it.name == cookie.name &&
it.path == cookie.path
}
if (index >= 0) {
val newList = cookies.toMutableList()
newList.removeAt(index)
cookieMap[uri.host] = newList.toList()
saveToDisk(uri)
true
} else {
false
}
}
}
fun get(uri: URI) = get(uri.host)
private fun get(url: String): List<Cookie> {
return cookieMap[url].orEmpty().filter { !it.hasExpired() }
}
private fun saveToDisk(uri: URI) {
// Get cookies to be stored in disk
val newValues =
cookieMap[uri.host]
.orEmpty()
.asSequence()
.filter { it.persistent && !it.hasExpired() }
.map(Cookie::toString)
.toSet()
prefs.edit().putStringSet(uri.host, newValues).apply()
}
private fun Cookie.hasExpired() = System.currentTimeMillis() >= expiresAt
private fun HttpCookie.toCookie(uri: URI) =
Cookie.Builder()
.name(name)
.value(value)
.domain(uri.host)
.path(path ?: "/")
.let {
if (maxAge != -1L) {
it.expiresAt(System.currentTimeMillis() + maxAge.seconds.inWholeMilliseconds)
} else {
it.expiresAt(Long.MAX_VALUE)
}
}
.let {
if (secure) {
it.secure()
} else {
it
}
}
.let {
if (isHttpOnly) {
it.httpOnly()
} else {
it
}
}
.build()
private fun Cookie.toHttpCookie(): HttpCookie {
val it = this
return HttpCookie(it.name, it.value).apply {
domain = it.domain
path = it.path
secure = it.secure
maxAge =
if (it.persistent) {
-1
} else {
(it.expiresAt.milliseconds - System.currentTimeMillis().milliseconds).inWholeSeconds
}
isHttpOnly = it.httpOnly
}
}
}

View File

@@ -1,9 +1,5 @@
package eu.kanade.tachiyomi.network
interface ProgressListener {
fun update(
bytesRead: Long,
contentLength: Long,
done: Boolean,
)
fun update(bytesRead: Long, contentLength: Long, done: Boolean)
}

View File

@@ -10,6 +10,7 @@ import okio.buffer
import java.io.IOException
class ProgressResponseBody(private val responseBody: ResponseBody, private val progressListener: ProgressListener) : ResponseBody() {
private val bufferedSource: BufferedSource by lazy {
source(responseBody.source()).buffer()
}
@@ -31,10 +32,7 @@ class ProgressResponseBody(private val responseBody: ResponseBody, private val p
var totalBytesRead = 0L
@Throws(IOException::class)
override fun read(
sink: Buffer,
byteCount: Long,
): Long {
override fun read(sink: Buffer, byteCount: Long): Long {
val bytesRead = super.read(sink, byteCount)
// read() returns the number of bytes read, or -1 if this source is exhausted.
totalBytesRead += if (bytesRead != -1L) bytesRead else 0

View File

@@ -1,5 +1,3 @@
@file:Suppress("ktlint:standard:function-naming")
package eu.kanade.tachiyomi.network
import okhttp3.CacheControl
@@ -17,7 +15,7 @@ private val DEFAULT_BODY: RequestBody = FormBody.Builder().build()
fun GET(
url: String,
headers: Headers = DEFAULT_HEADERS,
cache: CacheControl = DEFAULT_CACHE_CONTROL,
cache: CacheControl = DEFAULT_CACHE_CONTROL
): Request {
return Request.Builder()
.url(url)
@@ -32,7 +30,7 @@ fun GET(
fun GET(
url: HttpUrl,
headers: Headers = DEFAULT_HEADERS,
cache: CacheControl = DEFAULT_CACHE_CONTROL,
cache: CacheControl = DEFAULT_CACHE_CONTROL
): Request {
return Request.Builder()
.url(url)
@@ -45,7 +43,7 @@ fun POST(
url: String,
headers: Headers = DEFAULT_HEADERS,
body: RequestBody = DEFAULT_BODY,
cache: CacheControl = DEFAULT_CACHE_CONTROL,
cache: CacheControl = DEFAULT_CACHE_CONTROL
): Request {
return Request.Builder()
.url(url)
@@ -54,31 +52,3 @@ fun POST(
.cacheControl(cache)
.build()
}
fun PUT(
url: String,
headers: Headers = DEFAULT_HEADERS,
body: RequestBody = DEFAULT_BODY,
cache: CacheControl = DEFAULT_CACHE_CONTROL,
): 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 {
return Request.Builder()
.url(url)
.delete(body)
.headers(headers)
.cacheControl(cache)
.build()
}

View File

@@ -1,65 +1,59 @@
package eu.kanade.tachiyomi.network.interceptor
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 kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.stateIn
import kotlinx.coroutines.runBlocking
import kotlinx.coroutines.sync.Mutex
import kotlinx.coroutines.sync.withLock
import kotlinx.serialization.SerialName
import eu.kanade.tachiyomi.network.interceptor.CloudflareBypasser.resolveWithWebView
import kotlinx.serialization.Serializable
import kotlinx.serialization.encodeToString
import kotlinx.serialization.decodeFromString
import kotlinx.serialization.json.Json
import mu.KotlinLogging
import okhttp3.Cookie
import okhttp3.HttpUrl
import okhttp3.Interceptor
import okhttp3.MediaType.Companion.toMediaType
import okhttp3.Request
import okhttp3.RequestBody.Companion.toRequestBody
import okhttp3.Response
import org.kodein.di.DI
import org.kodein.di.conf.global
import org.kodein.di.instance
import suwayomi.tachidesk.server.serverConfig
import uy.kohesive.injekt.injectLazy
import java.io.BufferedReader
import java.io.Closeable
import java.io.File
import java.io.IOException
import kotlin.time.Duration.Companion.seconds
import kotlin.time.toJavaDuration
import java.io.InputStreamReader
import java.io.PrintWriter
import java.nio.file.Paths
import java.util.concurrent.TimeUnit
class CloudflareInterceptor(
private val setUserAgent: (String) -> Unit,
) : Interceptor {
class CloudflareInterceptor : Interceptor {
private val logger = KotlinLogging.logger {}
private val network: NetworkHelper by injectLazy()
@Synchronized
override fun intercept(chain: Interceptor.Chain): Response {
val originalRequest = chain.request()
logger.trace { "CloudflareInterceptor is being used." }
val originalResponse = chain.proceed(originalRequest)
val originalResponse = chain.proceed(chain.request())
// Check if Cloudflare anti-bot is on
if (!(originalResponse.code in ERROR_CODES && originalResponse.header("Server") in SERVER_CHECK)) {
return originalResponse
}
if (!serverConfig.flareSolverrEnabled.value) {
throw IOException("Cloudflare bypass currently disabled")
}
logger.debug { "Cloudflare anti-bot is on, CloudflareInterceptor is kicking in..." }
if (!serverConfig.webviewEnabled) {
throw CloudflareBypassException("Webview is disabled, enable it in server config")
}
return try {
originalResponse.close()
// network.cookieStore.remove(originalRequest.url.toUri())
network.cookies.remove(originalRequest.url.toUri())
val request =
runBlocking {
CFClearance.resolveWithFlareSolverr(setUserAgent, originalRequest)
}
val request = resolveWithWebView(originalRequest)
chain.proceed(request)
} catch (e: Exception) {
@@ -76,158 +70,275 @@ class CloudflareInterceptor(
}
}
/*
* This class is ported from https://github.com/vvanglro/cf-clearance
* The original code is licensed under Apache 2.0
*/
object CFClearance {
object CloudflareBypasser {
private val logger = KotlinLogging.logger {}
private val network: NetworkHelper by injectLazy()
private val client by lazy {
@Suppress("OPT_IN_USAGE")
serverConfig.flareSolverrTimeout
.map { timeoutInt ->
val timeout = timeoutInt.seconds
network.client.newBuilder()
.callTimeout(timeout.plus(10.seconds).toJavaDuration())
.readTimeout(timeout.plus(5.seconds).toJavaDuration())
.build()
fun resolveWithWebView(originalRequest: Request): Request {
val url = originalRequest.url.toString()
logger.debug { "resolveWithWebView($url)" }
val cookies = PythonInterpreter.create().use { py ->
try {
py.exec("import undetected_chromedriver as uc")
py.exec("options = uc.ChromeOptions()")
if (serverConfig.socksProxyEnabled) {
val proxy = "socks5://${serverConfig.socksProxyHost}:${serverConfig.socksProxyPort}"
py.exec("options.add_argument('--proxy-server=$proxy')")
}
// py.exec("driver = uc.Chrome(options=options)")
py.exec("driver = uc.Chrome(options=options, driver_executable_path='${py.chromedriverPath.replace("\\","\\\\")}', version_main=111)")
// TODO: handle custom userAgent
// val userAgent = originalRequest.header("User-Agent")
// if (userAgent != null) {
// browser.newContext(Browser.NewContextOptions().setUserAgent(userAgent)).use { browserContext ->
// browserContext.newPage().use { getCookies(it, url) }
// }
// } else {
// browser.newPage().use { getCookies(it, url) }
// }
py.exec("driver.get('$url')")
getCookies(py)
} finally {
py.exec("driver.quit()")
}
.stateIn(GlobalScope, SharingStarted.Eagerly, network.client)
}
// Copy cookies to cookie store
cookies.groupBy { it.domain }.forEach { (domain, cookies) ->
network.cookies.addAll(
url = HttpUrl.Builder()
.scheme("http")
.host(domain)
.build(),
cookies = cookies
)
}
// Merge new and existing cookies for this request
// Find the cookies that we need to merge into this request
val convertedForThisRequest = cookies.filter {
it.matches(originalRequest.url)
}
// Extract cookies from current request
val existingCookies = Cookie.parseAll(
originalRequest.url,
originalRequest.headers
)
// Filter out existing values of cookies that we are about to merge in
val filteredExisting = existingCookies.filter { existing ->
convertedForThisRequest.none { converted -> converted.name == existing.name }
}
logger.trace { "Existing cookies" }
logger.trace { existingCookies.joinToString("; ") }
val newCookies = filteredExisting + convertedForThisRequest
logger.trace { "New cookies" }
logger.trace { newCookies.joinToString("; ") }
return originalRequest.newBuilder()
.header("Cookie", newCookies.joinToString("; ") { "${it.name}=${it.value}" })
.build()
}
private val json: Json by injectLazy()
private val jsonMediaType = "application/json".toMediaType()
private val mutex = Mutex()
@Serializable
data class FlareSolverCookie(
val name: String,
val value: String,
)
@Serializable
data class FlareSolverRequest(
val cmd: String,
val url: String,
val maxTimeout: Int? = null,
val session: String? = null,
@SerialName("session_ttl_minutes")
val sessionTtlMinutes: Int? = null,
val cookies: List<FlareSolverCookie>? = null,
val returnOnlyCookies: Boolean? = null,
val proxy: String? = null,
val postData: String? = null, // only used with cmd 'request.post'
)
@Serializable
data class FlareSolverSolutionCookie(
val name: String,
val value: String,
val domain: String,
val path: String,
val expires: Double? = null,
val size: Int? = null,
val httpOnly: Boolean,
val secure: Boolean,
val session: Boolean? = null,
val sameSite: String,
)
@Serializable
data class FlareSolverSolution(
val url: String,
val status: Int,
val headers: Map<String, String>? = null,
val response: String? = null,
val cookies: List<FlareSolverSolutionCookie>,
val userAgent: String,
)
@Serializable
data class FlareSolverResponse(
val solution: FlareSolverSolution,
val status: String,
val message: String,
val startTimestamp: Long,
val endTimestamp: Long,
val version: String,
)
suspend fun resolveWithFlareSolverr(
setUserAgent: (String) -> Unit,
originalRequest: Request,
): Request {
val timeout = serverConfig.flareSolverrTimeout.value.seconds
val flareSolverResponse =
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 = true,
maxTimeout = timeout.inWholeMilliseconds.toInt(),
),
).toRequestBody(jsonMediaType),
),
).awaitSuccess().parseAs<FlareSolverResponse>()
}
fun getWebViewUserAgent(): String {
return try {
if (!serverConfig.webviewEnabled) {
throw CloudflareBypassException("Webview is disabled, enable it in server config")
}
if (flareSolverResponse.solution.status in 200..299) {
setUserAgent(flareSolverResponse.solution.userAgent)
val cookies =
flareSolverResponse.solution.cookies
.map { cookie ->
Cookie.Builder()
.name(cookie.name)
.value(cookie.value)
.domain(cookie.domain.removePrefix("."))
.path(cookie.path)
.expiresAt(cookie.expires?.takeUnless { it < 0.0 }?.toLong() ?: Long.MAX_VALUE)
.also {
if (cookie.httpOnly) it.httpOnly()
if (cookie.secure) it.secure()
}
.build()
}
.groupBy { it.domain }
.flatMap { (domain, cookies) ->
network.cookieStore.addAll(
HttpUrl.Builder()
.scheme("http")
.host(domain.removePrefix("."))
.build(),
cookies,
)
PythonInterpreter.create().use { py ->
py.exec("import undetected_chromedriver as uc")
cookies
}
logger.trace { "New cookies\n${cookies.joinToString("; ")}" }
val finalCookies =
network.cookieStore.get(originalRequest.url).joinToString("; ", postfix = "; ") {
"${it.name}=${it.value}"
}
logger.trace { "Final cookies\n$finalCookies" }
return originalRequest.newBuilder()
.header("Cookie", finalCookies)
.header("User-Agent", flareSolverResponse.solution.userAgent)
.build()
} else {
logger.debug { "Cloudflare challenge failed to resolve" }
throw CloudflareBypassException()
py.exec("options = uc.ChromeOptions()")
py.exec("options.add_argument('--headless')")
py.exec("options.add_argument('--disable-gpu')")
// py.exec("driver = uc.Chrome(options=options)")
py.exec("driver = uc.Chrome(options=options, driver_executable_path='${py.chromedriverPath.replace("\\","\\\\")}', version_main=111)")
py.exec("userAgent = driver.execute_script('return navigator.userAgent')")
val userAgent = py.getValue("userAgent")
py.exec("driver.quit()")
userAgent
}
} catch (e: Exception) {
// Webview might fail on headless environments like docker
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/112.0.0.0 Safari/537.36"
}
}
private class CloudflareBypassException : Exception()
@Serializable
data class PythonSeleniumCookie(
val domain: String,
val expiry: Long?,
val httpOnly: Boolean,
val name: String,
val path: String,
val sameSite: String,
val secure: Boolean,
val value: String
)
private val json by DI.global.instance<Json>()
private fun getCookies(py: PythonInterpreter): List<Cookie> {
val challengeResolved = waitForChallengeResolve(py)
return if (challengeResolved) {
py.exec("import json")
py.exec("cookies = json.dumps(driver.get_cookies())")
val cookiesJson = py.getValue("cookies")
val cookies = json.decodeFromString<List<PythonSeleniumCookie>>(cookiesJson)
logger.debug {
py.exec("userAgent = driver.execute_script('return navigator.userAgent')")
val userAgent = py.getValue("userAgent")
"Webview User-Agent is $userAgent"
}
// Convert Webview cookies to OkHttp cookies
cookies.map {
Cookie.Builder()
.domain(it.domain.removePrefix("."))
.expiresAt(it.expiry?.times(1000) ?: Long.MAX_VALUE)
.name(it.name)
.path(it.path)
.value(it.value).apply {
if (it.httpOnly) httpOnly()
if (it.secure) secure()
}.build()
}
} else {
throw CloudflareBypassException("Cloudflare challenge failed to resolve")
}
}
private fun waitForChallengeResolve(py: PythonInterpreter): Boolean {
// sometimes the user has to solve the captcha challenge manually and multiple times, potentially wait a long time
val timeoutSeconds = 120
repeat(timeoutSeconds) {
TimeUnit.SECONDS.sleep(1)
val success = try {
py.exec("r = driver.execute_script('return document.querySelector(\"#challenge-form\") == null')")
py.getValue("r").lowercase().toBoolean()
} catch (e: Exception) {
logger.debug(e) { "query Error" }
false
}
if (success) return true
}
return false
}
}
private class CloudflareBypassException(message: String?) : Exception(message)
class PythonInterpreter
private constructor(private val process: Process, val chromedriverPath: String) : Closeable {
private val stdin = process.outputStream
private val stdout = process.inputStream
private val stderr = process.errorStream
private val stdinWriter = PrintWriter(stdin)
private val stdoutReader = BufferedReader(InputStreamReader(stdout))
private val stderrReader = BufferedReader(InputStreamReader(stderr))
private fun rawExec(command: String) {
stdinWriter.println(command)
stdinWriter.flush()
}
val BUFF_SIZE = 102400
fun exec(command: String) {
logger.debug { "Python Command: $command" }
rawExec(command)
makeSureExecDone()
}
private val commandOutputs = mutableListOf<String>()
fun makeSureExecDone() {
val makeSureString = "PYTHON_IS_READY"
rawExec("print('$makeSureString')")
var line: String?
do {
line = stdoutReader.readLine()
if (line != makeSureString) {
commandOutputs.add(line)
}
} while (line != makeSureString)
val pyError = buildString {
while (stderrReader.ready())
append(stderr.read().toChar())
}
if (pyError.isNotEmpty()) {
println("Python STDERR: $pyError")
}
}
fun getValue(variableName: String): String {
exec("print($variableName)")
return commandOutputs.last()
}
private fun flushStdoutReader() {
var line: String?
while (stdoutReader.ready()) {
val line = stdoutReader.readLine()
}
}
fun destroy() {
stdinWriter.close()
stdoutReader.close()
stderr.close()
process.destroy()
}
override fun close() {
destroy()
}
companion object {
private val logger = KotlinLogging.logger {}
fun create(pythonPath: String, workingDir: String, pythonStartupFile: String, chromedriverPath: String): PythonInterpreter {
val processBuilder = ProcessBuilder()
.command(pythonPath, "-i", "-q")
processBuilder.directory(File(workingDir))
val environment = processBuilder.environment()
environment["PYTHONSTARTUP"] = pythonStartupFile
val process = processBuilder.start()
return PythonInterpreter(process, chromedriverPath)
}
fun create(): PythonInterpreter {
val uc = Paths.get(serverConfig.undetectedChromePath).toAbsolutePath().toString()
logger.debug { "absolute path to undetected-chromedriver: $uc" }
val (pythonPath, chromedriverPath) = if (System.getProperty("os.name").startsWith("Windows")) {
arrayOf(
"$uc\\venv\\Scripts\\python.exe",
"$uc\\chromedriver.exe"
)
} else {
arrayOf(
"$uc/venv/bin/python",
"$uc/chromedriver"
)
}
return create(
pythonPath,
uc,
"$uc/console.py",
chromedriverPath
)
}
}
}

View File

@@ -1,21 +0,0 @@
package eu.kanade.tachiyomi.network.interceptor
import okhttp3.Interceptor
import okhttp3.Response
/**
* To use [okhttp3.brotli.BrotliInterceptor] as a network interceptor,
* add [IgnoreGzipInterceptor] right before it.
*
* This nullifies the transparent gzip of [okhttp3.internal.http.BridgeInterceptor]
* so gzip and Brotli are explicitly handled by the [okhttp3.brotli.BrotliInterceptor].
*/
class IgnoreGzipInterceptor : Interceptor {
override fun intercept(chain: Interceptor.Chain): Response {
var request = chain.request()
if (request.header("Accept-Encoding") == "gzip") {
request = request.newBuilder().removeHeader("Accept-Encoding").build()
}
return chain.proceed(request)
}
}

View File

@@ -4,21 +4,11 @@ import android.os.SystemClock
import okhttp3.Interceptor
import okhttp3.OkHttpClient
import okhttp3.Response
import java.io.IOException
import java.util.ArrayDeque
import java.util.concurrent.Semaphore
import java.util.concurrent.TimeUnit
import kotlin.time.Duration
import kotlin.time.Duration.Companion.seconds
import kotlin.time.toDuration
import kotlin.time.toDurationUnit
/**
* An OkHttp interceptor that handles rate limiting.
*
* This uses `java.time` APIs and is the legacy method, kept
* for compatibility reasons with existing extensions.
*
* Examples:
*
* permits = 5, period = 1, unit = seconds => 5 requests per second
@@ -26,105 +16,52 @@ import kotlin.time.toDurationUnit
*
* @since extension-lib 1.3
*
* @param permits [Int] Number of requests allowed within a period of units.
* @param period [Long] The limiting duration. Defaults to 1.
* @param unit [TimeUnit] The unit of time for the period. Defaults to seconds.
* @param permits {Int} Number of requests allowed within a period of units.
* @param period {Long} The limiting duration. Defaults to 1.
* @param unit {TimeUnit} The unit of time for the period. Defaults to seconds.
*/
@Deprecated("Use the version with kotlin.time APIs instead.")
fun OkHttpClient.Builder.rateLimit(
permits: Int,
period: Long = 1,
unit: TimeUnit = TimeUnit.SECONDS,
) = addInterceptor(RateLimitInterceptor(null, permits, period.toDuration(unit.toDurationUnit())))
unit: TimeUnit = TimeUnit.SECONDS
) = addInterceptor(RateLimitInterceptor(permits, period, unit))
/**
* An OkHttp interceptor that handles rate limiting.
*
* Examples:
*
* permits = 5, period = 1.seconds => 5 requests per second
* permits = 10, period = 2.minutes => 10 requests per 2 minutes
*
* @since extension-lib 1.5
*
* @param permits [Int] Number of requests allowed within a period of units.
* @param period [Duration] The limiting duration. Defaults to 1.seconds.
*/
fun OkHttpClient.Builder.rateLimit(
permits: Int,
period: Duration = 1.seconds,
) = addInterceptor(RateLimitInterceptor(null, permits, period))
/** We can probably accept domains or wildcards by comparing with [endsWith], etc. */
@Suppress("PLATFORM_CLASS_MAPPED_TO_KOTLIN")
internal class RateLimitInterceptor(
private val host: String?,
private class RateLimitInterceptor(
private val permits: Int,
period: Duration,
period: Long,
unit: TimeUnit
) : Interceptor {
private val requestQueue = ArrayDeque<Long>(permits)
private val rateLimitMillis = period.inWholeMilliseconds
private val fairLock = Semaphore(1, true)
private val requestQueue = ArrayList<Long>(permits)
private val rateLimitMillis = unit.toMillis(period)
override fun intercept(chain: Interceptor.Chain): Response {
val call = chain.call()
if (call.isCanceled()) throw IOException("Canceled")
synchronized(requestQueue) {
val now = SystemClock.elapsedRealtime()
val waitTime = if (requestQueue.size < permits) {
0
} else {
val oldestReq = requestQueue[0]
val newestReq = requestQueue[permits - 1]
val request = chain.request()
when (host) {
null, request.url.host -> {} // need rate limit
else -> return chain.proceed(request)
}
try {
fairLock.acquire()
} catch (e: InterruptedException) {
throw IOException(e)
}
val requestQueue = this.requestQueue
val timestamp: Long
try {
synchronized(requestQueue) {
while (requestQueue.size >= permits) { // queue is full, remove expired entries
val periodStart = SystemClock.elapsedRealtime() - rateLimitMillis
var hasRemovedExpired = false
while (requestQueue.isEmpty().not() && requestQueue.first <= periodStart) {
requestQueue.removeFirst()
hasRemovedExpired = true
}
if (call.isCanceled()) {
throw IOException("Canceled")
} else if (hasRemovedExpired) {
break
} else {
try {
// wait for the first entry to expire, or notified by cached response
(requestQueue as Object).wait(requestQueue.first - periodStart)
} catch (_: InterruptedException) {
continue
}
}
if (newestReq - oldestReq > rateLimitMillis) {
0
} else {
oldestReq + rateLimitMillis - now // Remaining time
}
// add request to queue
timestamp = SystemClock.elapsedRealtime()
requestQueue.addLast(timestamp)
}
} finally {
fairLock.release()
}
val response = chain.proceed(request)
if (response.networkResponse == null) { // response is cached, remove it from queue
synchronized(requestQueue) {
if (requestQueue.isEmpty() || timestamp < requestQueue.first()) return@synchronized
requestQueue.removeFirstOccurrence(timestamp)
(requestQueue as Object).notifyAll()
if (requestQueue.size == permits) {
requestQueue.removeAt(0)
}
if (waitTime > 0) {
requestQueue.add(now + waitTime)
Thread.sleep(waitTime) // Sleep inside synchronized to pause queued requests
} else {
requestQueue.add(now)
}
}
return response
return chain.proceed(chain.request())
}
}

View File

@@ -1,73 +1,75 @@
package eu.kanade.tachiyomi.network.interceptor
import android.os.SystemClock
import okhttp3.HttpUrl
import okhttp3.HttpUrl.Companion.toHttpUrlOrNull
import okhttp3.Interceptor
import okhttp3.OkHttpClient
import okhttp3.Response
import java.util.concurrent.TimeUnit
import kotlin.time.Duration
import kotlin.time.Duration.Companion.seconds
import kotlin.time.toDuration
import kotlin.time.toDurationUnit
/**
* An OkHttp interceptor that handles given url host's rate limiting.
*
* Examples:
*
* httpUrl = "https://api.manga.com".toHttpUrlOrNull(), permits = 5, period = 1, unit = seconds => 5 requests per second to api.manga.com
* httpUrl = "https://imagecdn.manga.com".toHttpUrlOrNull(), permits = 10, period = 2, unit = minutes => 10 requests per 2 minutes to imagecdn.manga.com
* httpUrl = "api.manga.com".toHttpUrlOrNull(), permits = 5, period = 1, unit = seconds => 5 requests per second to api.manga.com
* httpUrl = "imagecdn.manga.com".toHttpUrlOrNull(), permits = 10, period = 2, unit = minutes => 10 requests per 2 minutes to imagecdn.manga.com
*
* @since extension-lib 1.3
*
* @param httpUrl [HttpUrl] The url host that this interceptor should handle. Will get url's host by using HttpUrl.host()
* @param permits [Int] Number of requests allowed within a period of units.
* @param period [Long] The limiting duration. Defaults to 1.
* @param unit [TimeUnit] The unit of time for the period. Defaults to seconds.
* @param httpUrl {HttpUrl} The url host that this interceptor should handle. Will get url's host by using HttpUrl.host()
* @param permits {Int} Number of requests allowed within a period of units.
* @param period {Long} The limiting duration. Defaults to 1.
* @param unit {TimeUnit} The unit of time for the period. Defaults to seconds.
*/
@Deprecated("Use the version with kotlin.time APIs instead.")
fun OkHttpClient.Builder.rateLimitHost(
httpUrl: HttpUrl,
permits: Int,
period: Long = 1,
unit: TimeUnit = TimeUnit.SECONDS,
) = addInterceptor(RateLimitInterceptor(httpUrl.host, permits, period.toDuration(unit.toDurationUnit())))
unit: TimeUnit = TimeUnit.SECONDS
) = addInterceptor(SpecificHostRateLimitInterceptor(httpUrl, permits, period, unit))
/**
* An OkHttp interceptor that handles given url host's rate limiting.
*
* Examples:
*
* httpUrl = "https://api.manga.com".toHttpUrlOrNull(), permits = 5, period = 1.seconds => 5 requests per second to api.manga.com
* httpUrl = "https://imagecdn.manga.com".toHttpUrlOrNull(), permits = 10, period = 2.minutes => 10 requests per 2 minutes to imagecdn.manga.com
*
* @since extension-lib 1.5
*
* @param httpUrl [HttpUrl] The url host that this interceptor should handle. Will get url's host by using HttpUrl.host()
* @param permits [Int] Number of requests allowed within a period of units.
* @param period [Duration] The limiting duration. Defaults to 1.seconds.
*/
fun OkHttpClient.Builder.rateLimitHost(
class SpecificHostRateLimitInterceptor(
httpUrl: HttpUrl,
permits: Int,
period: Duration = 1.seconds,
): OkHttpClient.Builder = addInterceptor(RateLimitInterceptor(httpUrl.host, permits, period))
private val permits: Int,
period: Long,
unit: TimeUnit
) : Interceptor {
/**
* An OkHttp interceptor that handles given url host's rate limiting.
*
* Examples:
*
* url = "https://api.manga.com", permits = 5, period = 1.seconds => 5 requests per second to api.manga.com
* url = "https://imagecdn.manga.com", permits = 10, period = 2.minutes => 10 requests per 2 minutes to imagecdn.manga.com
*
* @since extension-lib 1.5
*
* @param url [String] The url host that this interceptor should handle. Will get url's host by using HttpUrl.host()
* @param permits [Int] Number of requests allowed within a period of units.
* @param period [Duration] The limiting duration. Defaults to 1.seconds.
*/
fun OkHttpClient.Builder.rateLimitHost(
url: String,
permits: Int,
period: Duration = 1.seconds,
): OkHttpClient.Builder = addInterceptor(RateLimitInterceptor(url.toHttpUrlOrNull()?.host, permits, period))
private val requestQueue = ArrayList<Long>(permits)
private val rateLimitMillis = unit.toMillis(period)
private val host = httpUrl.host
override fun intercept(chain: Interceptor.Chain): Response {
if (chain.request().url.host != host) {
return chain.proceed(chain.request())
}
synchronized(requestQueue) {
val now = SystemClock.elapsedRealtime()
val waitTime = if (requestQueue.size < permits) {
0
} else {
val oldestReq = requestQueue[0]
val newestReq = requestQueue[permits - 1]
if (newestReq - oldestReq > rateLimitMillis) {
0
} else {
oldestReq + rateLimitMillis - now // Remaining time
}
}
if (requestQueue.size == permits) {
requestQueue.removeAt(0)
}
if (waitTime > 0) {
requestQueue.add(now + waitTime)
Thread.sleep(waitTime) // Sleep inside synchronized to pause queued requests
} else {
requestQueue.add(now)
}
}
return chain.proceed(chain.request())
}
}

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