mirror of
https://github.com/Suwayomi/Tachidesk.git
synced 2025-12-10 06:42:07 +01:00
Switch to a new Ktlint Formatter (#705)
* Switch to new Ktlint plugin * Add ktlintCheck to PR builds * Run formatter * Put ktlint version in libs toml * Fix lint * Use Zip4Java from libs.toml
This commit is contained in:
11
.editorconfig
Normal file
11
.editorconfig
Normal file
@@ -0,0 +1,11 @@
|
||||
[*.{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
|
||||
2
.github/workflows/build_pull_request.yml
vendored
2
.github/workflows/build_pull_request.yml
vendored
@@ -49,5 +49,5 @@ jobs:
|
||||
uses: gradle/gradle-build-action@v2
|
||||
with:
|
||||
build-root-directory: master
|
||||
arguments: :server:shadowJar --stacktrace
|
||||
arguments: ktlintCheck :server:shadowJar --stacktrace
|
||||
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
@Suppress("DSL_SCOPE_VIOLATION")
|
||||
plugins {
|
||||
id(libs.plugins.kotlin.jvm.get().pluginId)
|
||||
id(libs.plugins.kotlin.serialization.get().pluginId)
|
||||
id(libs.plugins.kotlinter.get().pluginId)
|
||||
id(libs.plugins.ktlint.get().pluginId)
|
||||
}
|
||||
|
||||
dependencies {
|
||||
|
||||
@@ -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),
|
||||
)
|
||||
}
|
||||
|
||||
@@ -5,7 +5,8 @@ import org.kodein.di.bind
|
||||
import org.kodein.di.singleton
|
||||
|
||||
class ConfigKodeinModule {
|
||||
fun create() = DI.Module("ConfigManager") {
|
||||
fun create() =
|
||||
DI.Module("ConfigManager") {
|
||||
// Config module
|
||||
bind<ConfigManager>() with singleton { GlobalConfigManager }
|
||||
}
|
||||
|
||||
@@ -63,14 +63,16 @@ open class ConfigManager {
|
||||
val baseConfig =
|
||||
ConfigFactory.parseMap(
|
||||
mapOf(
|
||||
"androidcompat.rootDir" to "$ApplicationRootDir/android-compat" // override AndroidCompat's rootDir
|
||||
)
|
||||
// override AndroidCompat's rootDir
|
||||
"androidcompat.rootDir" to "$ApplicationRootDir/android-compat",
|
||||
),
|
||||
)
|
||||
|
||||
// Load user config
|
||||
val userConfig = getUserConfig()
|
||||
|
||||
val config = ConfigFactory.empty()
|
||||
val config =
|
||||
ConfigFactory.empty()
|
||||
.withFallback(baseConfig)
|
||||
.withFallback(userConfig)
|
||||
.withFallback(compatConfig)
|
||||
@@ -95,14 +97,20 @@ open class ConfigManager {
|
||||
}
|
||||
}
|
||||
|
||||
private fun updateUserConfigFile(path: String, value: ConfigValue) {
|
||||
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) {
|
||||
suspend fun updateValue(
|
||||
path: String,
|
||||
value: Any,
|
||||
) {
|
||||
mutex.withLock {
|
||||
val configValue = ConfigValueFactory.fromAnyRef(value)
|
||||
|
||||
@@ -140,10 +148,16 @@ open class ConfigManager {
|
||||
return
|
||||
}
|
||||
|
||||
logger.debug { "user config is out of date, updating... (missingSettings= $hasMissingSettings, outdatedSettings= $hasOutdatedSettings" }
|
||||
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) }
|
||||
userConfig.entrySet().filter {
|
||||
serverConfig.hasPath(
|
||||
it.key,
|
||||
)
|
||||
}.forEach { newUserConfigDoc = newUserConfigDoc.withValue(it.key, it.value) }
|
||||
|
||||
userConfigFile.writeText(newUserConfigDoc.render())
|
||||
}
|
||||
|
||||
@@ -26,12 +26,16 @@ abstract class SystemPropertyOverridableConfigModule(getConfig: () -> Config, mo
|
||||
|
||||
/** 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 {
|
||||
inline operator fun <R, reified T> getValue(
|
||||
thisRef: R,
|
||||
property: KProperty<*>,
|
||||
): T {
|
||||
val configValue: T = getConfig().getValue(thisRef, property)
|
||||
|
||||
val combined = System.getProperty(
|
||||
val combined =
|
||||
System.getProperty(
|
||||
"$CONFIG_PREFIX.$moduleName.${property.name}",
|
||||
configValue.toString()
|
||||
configValue.toString(),
|
||||
)
|
||||
|
||||
return when (T::class.simpleName) {
|
||||
|
||||
@@ -19,23 +19,29 @@ import mu.KotlinLogging
|
||||
import org.slf4j.Logger
|
||||
import org.slf4j.LoggerFactory
|
||||
|
||||
private fun createRollingFileAppender(logContext: LoggerContext, logDirPath: String): RollingFileAppender<ILoggingEvent> {
|
||||
private fun createRollingFileAppender(
|
||||
logContext: LoggerContext,
|
||||
logDirPath: String,
|
||||
): RollingFileAppender<ILoggingEvent> {
|
||||
val logFilename = "application"
|
||||
|
||||
val logEncoder = PatternLayoutEncoder().apply {
|
||||
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 {
|
||||
val appender =
|
||||
RollingFileAppender<ILoggingEvent>().apply {
|
||||
name = "FILE"
|
||||
context = logContext
|
||||
encoder = logEncoder
|
||||
file = "$logDirPath/$logFilename.log"
|
||||
}
|
||||
|
||||
val rollingPolicy = SizeAndTimeBasedRollingPolicy<ILoggingEvent>().apply {
|
||||
val rollingPolicy =
|
||||
SizeAndTimeBasedRollingPolicy<ILoggingEvent>().apply {
|
||||
context = logContext
|
||||
setParent(appender)
|
||||
fileNamePattern = "$logDirPath/${logFilename}_%d{yyyy-MM-dd}_%i.log.gz"
|
||||
@@ -72,8 +78,12 @@ fun initLoggerConfig(appRootPath: String) {
|
||||
|
||||
const val BASE_LOGGER_NAME = "_BaseLogger"
|
||||
|
||||
fun setLogLevelFor(name: String, level: Level) {
|
||||
val logger = if (name == BASE_LOGGER_NAME) {
|
||||
fun setLogLevelFor(
|
||||
name: String,
|
||||
level: Level,
|
||||
) {
|
||||
val logger =
|
||||
if (name == BASE_LOGGER_NAME) {
|
||||
getBaseLogger()
|
||||
} else {
|
||||
getLogger(name)
|
||||
|
||||
@@ -2,5 +2,6 @@ package xyz.nulldev.ts.config.util
|
||||
|
||||
import com.typesafe.config.Config
|
||||
|
||||
operator fun Config.get(key: String) = getString(key)
|
||||
operator fun Config.get(key: String) =
|
||||
getString(key)
|
||||
?: throw IllegalStateException("Could not find value for config entry: $key!")
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
@Suppress("DSL_SCOPE_VIOLATION")
|
||||
plugins {
|
||||
id(libs.plugins.kotlin.jvm.get().pluginId)
|
||||
id(libs.plugins.kotlin.serialization.get().pluginId)
|
||||
id(libs.plugins.kotlinter.get().pluginId)
|
||||
id(libs.plugins.ktlint.get().pluginId)
|
||||
}
|
||||
|
||||
dependencies {
|
||||
|
||||
@@ -12,7 +12,7 @@ class PreferenceManager {
|
||||
fun getDefaultSharedPreferences(context: Context) =
|
||||
context.getSharedPreferences(
|
||||
context.applicationInfo.packageName,
|
||||
Context.MODE_PRIVATE
|
||||
Context.MODE_PRIVATE,
|
||||
)!!
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,7 +7,6 @@ import org.kodein.di.instance
|
||||
import xyz.nulldev.androidcompat.androidimpl.CustomContext
|
||||
|
||||
class AndroidCompat {
|
||||
|
||||
val context: CustomContext by DI.global.instance()
|
||||
|
||||
fun startApp(application: Application) {
|
||||
|
||||
@@ -18,10 +18,13 @@ 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",
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,7 +18,8 @@ import xyz.nulldev.androidcompat.service.ServiceSupport
|
||||
*/
|
||||
|
||||
class AndroidCompatModule {
|
||||
fun create() = DI.Module("AndroidCompat") {
|
||||
fun create() =
|
||||
DI.Module("AndroidCompat") {
|
||||
bind<AndroidFiles>() with singleton { AndroidFiles() }
|
||||
|
||||
bind<ApplicationInfoImpl>() with singleton { ApplicationInfoImpl() }
|
||||
@@ -31,7 +32,8 @@ class AndroidCompatModule {
|
||||
|
||||
// Context
|
||||
bind<CustomContext>() with singleton { CustomContext() }
|
||||
bind<Context>() with singleton {
|
||||
bind<Context>() with
|
||||
singleton {
|
||||
val context: Context by DI.global.instance<CustomContext>()
|
||||
context
|
||||
}
|
||||
|
||||
@@ -13,7 +13,6 @@ class ApplicationInfoConfigModule(getConfig: () -> Config) : ConfigModule(getCon
|
||||
val debug: Boolean by getConfig()
|
||||
|
||||
companion object {
|
||||
fun register(config: Config) =
|
||||
ApplicationInfoConfigModule { config.getConfig("android.app") }
|
||||
fun register(config: Config) = ApplicationInfoConfigModule { config.getConfig("android.app") }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -28,7 +28,6 @@ class FilesConfigModule(getConfig: () -> Config) : ConfigModule(getConfig) {
|
||||
val packageDir: String by getConfig()
|
||||
|
||||
companion object {
|
||||
fun register(config: Config) =
|
||||
FilesConfigModule { config.getConfig("android.files") }
|
||||
fun register(config: Config) = FilesConfigModule { config.getConfig("android.files") }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,13 +10,16 @@ class SystemConfigModule(val getConfig: () -> Config) : ConfigModule(getConfig)
|
||||
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")
|
||||
|
||||
companion object {
|
||||
fun register(config: Config) =
|
||||
SystemConfigModule { config.getConfig("android.system") }
|
||||
fun register(config: Config) = SystemConfigModule { config.getConfig("android.system") }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -20,7 +20,6 @@ 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
|
||||
@@ -29,7 +28,8 @@ class ScrollableResultSet(val parent: ResultSet) : ResultSet by parent {
|
||||
|
||||
val parentMetadata = parent.metaData
|
||||
val columnCount = parentMetadata.columnCount
|
||||
val columnLabels = (1..columnCount).map {
|
||||
val columnLabels =
|
||||
(1..columnCount).map {
|
||||
parentMetadata.getColumnLabel(it)
|
||||
}.toTypedArray()
|
||||
|
||||
@@ -43,7 +43,8 @@ class ScrollableResultSet(val parent: ResultSet) : ResultSet by parent {
|
||||
|
||||
// Fill cache
|
||||
while (parent.next()) {
|
||||
cachedContent += ResultSetEntry().apply {
|
||||
cachedContent +=
|
||||
ResultSetEntry().apply {
|
||||
for (i in 1..columnCount)
|
||||
data += parent.getObject(i)
|
||||
}
|
||||
@@ -92,67 +93,121 @@ 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()
|
||||
}
|
||||
|
||||
@@ -170,12 +225,18 @@ 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()
|
||||
}
|
||||
@@ -185,11 +246,17 @@ 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()
|
||||
}
|
||||
|
||||
@@ -205,12 +272,18 @@ 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()
|
||||
}
|
||||
@@ -223,11 +296,17 @@ 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()
|
||||
}
|
||||
|
||||
@@ -249,12 +328,18 @@ 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()
|
||||
}
|
||||
@@ -328,27 +413,49 @@ 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()
|
||||
}
|
||||
|
||||
@@ -360,61 +467,107 @@ 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()
|
||||
}
|
||||
|
||||
@@ -422,11 +575,17 @@ 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()
|
||||
}
|
||||
|
||||
@@ -440,27 +599,47 @@ 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()
|
||||
}
|
||||
|
||||
@@ -480,19 +659,31 @@ 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()
|
||||
}
|
||||
|
||||
@@ -504,21 +695,33 @@ 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
|
||||
}
|
||||
|
||||
@@ -527,11 +730,17 @@ 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()
|
||||
}
|
||||
|
||||
@@ -565,35 +774,61 @@ 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()
|
||||
}
|
||||
|
||||
@@ -627,11 +862,17 @@ 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()
|
||||
}
|
||||
|
||||
@@ -651,11 +892,17 @@ 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()
|
||||
}
|
||||
|
||||
@@ -665,11 +912,17 @@ 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()
|
||||
}
|
||||
|
||||
@@ -689,11 +942,17 @@ 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()
|
||||
}
|
||||
|
||||
@@ -707,12 +966,18 @@ 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()
|
||||
}
|
||||
@@ -729,11 +994,17 @@ 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()
|
||||
}
|
||||
|
||||
@@ -745,11 +1016,17 @@ 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()
|
||||
}
|
||||
|
||||
@@ -814,9 +1091,13 @@ 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]
|
||||
}
|
||||
@@ -831,27 +1112,49 @@ 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()
|
||||
}
|
||||
|
||||
|
||||
@@ -31,7 +31,10 @@ class JavaSharedPreferences(key: String) : SharedPreferences {
|
||||
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 {
|
||||
@@ -39,7 +42,10 @@ 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)
|
||||
@@ -51,19 +57,31 @@ 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)
|
||||
}
|
||||
|
||||
@@ -80,11 +98,15 @@ class JavaSharedPreferences(key: String) : SharedPreferences {
|
||||
|
||||
private sealed class Action {
|
||||
data class Add(val key: String, val value: Any) : Action()
|
||||
|
||||
data class Remove(val key: String) : 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 {
|
||||
@@ -95,7 +117,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)
|
||||
@@ -105,22 +127,34 @@ 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
|
||||
}
|
||||
@@ -148,7 +182,8 @@ class JavaSharedPreferences(key: String) : SharedPreferences {
|
||||
actions.forEach {
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
when (it) {
|
||||
is Action.Add -> when (val value = it.value) {
|
||||
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)
|
||||
@@ -178,7 +213,8 @@ class JavaSharedPreferences(key: String) : SharedPreferences {
|
||||
}
|
||||
|
||||
override fun registerOnSharedPreferenceChangeListener(listener: SharedPreferences.OnSharedPreferenceChangeListener) {
|
||||
val javaListener = PreferenceChangeListener {
|
||||
val javaListener =
|
||||
PreferenceChangeListener {
|
||||
listener.onSharedPreferenceChanged(this, it.key)
|
||||
}
|
||||
listeners[listener] = javaListener
|
||||
|
||||
@@ -20,15 +20,18 @@ data class InstalledPackage(val root: File) {
|
||||
val icon = File(root, "icon.png")
|
||||
|
||||
val info: PackageInfo
|
||||
get() = ApkParsers.getMetaInfo(apk).toPackageInfo(apk).also {
|
||||
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 {
|
||||
val doc =
|
||||
parsed.manifestXml.byteInputStream().use {
|
||||
dBuilder.parse(it)
|
||||
}
|
||||
|
||||
it.applicationInfo.metaData = Bundle().apply {
|
||||
it.applicationInfo.metaData =
|
||||
Bundle().apply {
|
||||
val appTag = doc.getElementsByTagName("application").item(0)
|
||||
|
||||
appTag?.childNodes?.toList()?.filter {
|
||||
@@ -40,20 +43,22 @@ data class InstalledPackage(val root: File) {
|
||||
}?.map {
|
||||
putString(
|
||||
it.attributes.getNamedItem("android:name").nodeValue,
|
||||
it.attributes.getNamedItem("android:value").nodeValue
|
||||
it.attributes.getNamedItem("android:value").nodeValue,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
it.signatures = (
|
||||
it.signatures =
|
||||
(
|
||||
parsed.apkSingers.flatMap { it.certificateMetas }
|
||||
/*+ parsed.apkV2Singers.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)
|
||||
val res =
|
||||
ApkVerifier.Builder(apk)
|
||||
.build()
|
||||
.verify()
|
||||
|
||||
@@ -64,7 +69,8 @@ data class InstalledPackage(val root: File) {
|
||||
try {
|
||||
val icons = ApkFile(apk).allIcons
|
||||
|
||||
val read = icons.filter { it.isFile }.map {
|
||||
val read =
|
||||
icons.filter { it.isFile }.map {
|
||||
it.data.inputStream().use {
|
||||
ImageIO.read(it)
|
||||
}
|
||||
|
||||
@@ -25,7 +25,10 @@ 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()) {
|
||||
|
||||
@@ -12,13 +12,15 @@ fun ApkMeta.toPackageInfo(apk: File): PackageInfo {
|
||||
it.versionCode = versionCode.toInt()
|
||||
it.versionName = versionName
|
||||
|
||||
it.reqFeatures = usesFeatures.map {
|
||||
it.reqFeatures =
|
||||
usesFeatures.map {
|
||||
FeatureInfo().apply {
|
||||
name = it.name
|
||||
}
|
||||
}.toTypedArray()
|
||||
|
||||
it.applicationInfo = ApplicationInfo().apply {
|
||||
it.applicationInfo =
|
||||
ApplicationInfo().apply {
|
||||
packageName = it.packageName
|
||||
nonLocalizedLabel = label
|
||||
sourceDir = apk.absolutePath
|
||||
|
||||
@@ -18,7 +18,10 @@ 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" }
|
||||
@@ -35,7 +38,10 @@ 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)
|
||||
}
|
||||
|
||||
@@ -26,7 +26,10 @@ 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()
|
||||
|
||||
@@ -24,5 +24,7 @@ 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())!!
|
||||
|
||||
@@ -22,7 +22,10 @@ class CookieManagerImpl : CookieManager() {
|
||||
return acceptCookie
|
||||
}
|
||||
|
||||
override fun setAcceptThirdPartyCookies(webview: WebView?, accept: Boolean) {
|
||||
override fun setAcceptThirdPartyCookies(
|
||||
webview: WebView?,
|
||||
accept: Boolean,
|
||||
) {
|
||||
acceptThirdPartyCookies = accept
|
||||
}
|
||||
|
||||
@@ -30,8 +33,12 @@ class CookieManagerImpl : CookieManager() {
|
||||
return acceptThirdPartyCookies
|
||||
}
|
||||
|
||||
override fun setCookie(url: String, value: String?) {
|
||||
val uri = if (url.startsWith("http")) {
|
||||
override fun setCookie(
|
||||
url: String,
|
||||
value: String?,
|
||||
) {
|
||||
val uri =
|
||||
if (url.startsWith("http")) {
|
||||
URI(url)
|
||||
} else {
|
||||
URI("http://$url")
|
||||
@@ -42,13 +49,18 @@ class CookieManagerImpl : CookieManager() {
|
||||
}
|
||||
}
|
||||
|
||||
override fun setCookie(url: String, value: String?, callback: ValueCallback<Boolean>?) {
|
||||
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")) {
|
||||
val uri =
|
||||
if (url.startsWith("http")) {
|
||||
URI(url)
|
||||
} else {
|
||||
URI("http://$url")
|
||||
|
||||
@@ -1,12 +1,11 @@
|
||||
import org.jetbrains.kotlin.gradle.tasks.KotlinJvmCompile
|
||||
import org.jmailen.gradle.kotlinter.tasks.FormatTask
|
||||
import org.jmailen.gradle.kotlinter.tasks.LintTask
|
||||
import org.jlleitschuh.gradle.ktlint.KtlintExtension
|
||||
import org.jlleitschuh.gradle.ktlint.KtlintPlugin
|
||||
|
||||
@Suppress("DSL_SCOPE_VIOLATION")
|
||||
plugins {
|
||||
alias(libs.plugins.kotlin.jvm)
|
||||
alias(libs.plugins.kotlin.serialization)
|
||||
alias(libs.plugins.kotlinter)
|
||||
alias(libs.plugins.ktlint)
|
||||
alias(libs.plugins.buildconfig) apply false
|
||||
alias(libs.plugins.download)
|
||||
}
|
||||
@@ -32,24 +31,25 @@ subprojects {
|
||||
}
|
||||
}
|
||||
|
||||
plugins.withType<KtlintPlugin> {
|
||||
extensions.configure<KtlintExtension>("ktlint") {
|
||||
version.set(libs.versions.ktlint.get())
|
||||
filter {
|
||||
exclude("**/generated/**")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
tasks {
|
||||
withType<KotlinJvmCompile> {
|
||||
dependsOn("formatKotlin")
|
||||
dependsOn("ktlintFormat")
|
||||
kotlinOptions {
|
||||
jvmTarget = JavaVersion.VERSION_1_8.toString()
|
||||
|
||||
freeCompilerArgs += listOf(
|
||||
"-Xcontext-receivers"
|
||||
"-Xcontext-receivers",
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
withType<LintTask> {
|
||||
source(files("src/kotlin"))
|
||||
}
|
||||
|
||||
withType<FormatTask> {
|
||||
source(files("src/kotlin"))
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -7,5 +7,5 @@ repositories {
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation("net.lingala.zip4j:zip4j:2.9.0")
|
||||
implementation(libs.zip4j)
|
||||
}
|
||||
9
buildSrc/settings.gradle.kts
Normal file
9
buildSrc/settings.gradle.kts
Normal file
@@ -0,0 +1,9 @@
|
||||
rootProject.name = "buildSrc"
|
||||
|
||||
dependencyResolutionManagement {
|
||||
versionCatalogs {
|
||||
create("libs") {
|
||||
from(files("../gradle/libs.versions.toml"))
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -13,6 +13,7 @@ twelvemonkeys = "3.9.4"
|
||||
playwright = "1.28.0"
|
||||
graphqlkotlin = "6.5.6"
|
||||
xmlserialization = "0.86.2"
|
||||
ktlint = "1.0.0"
|
||||
|
||||
[libraries]
|
||||
# Kotlin
|
||||
@@ -150,7 +151,7 @@ kotlin-jvm = { id = "org.jetbrains.kotlin.jvm", version.ref = "kotlin"}
|
||||
kotlin-serialization = { id = "org.jetbrains.kotlin.plugin.serialization", version.ref = "kotlin"}
|
||||
|
||||
# Linter
|
||||
kotlinter = { id = "org.jmailen.kotlinter", version = "3.12.0"}
|
||||
ktlint = { id = "org.jlleitschuh.gradle.ktlint", version = "11.6.0"}
|
||||
|
||||
# Build config
|
||||
buildconfig = { id = "com.github.gmazzo.buildconfig", version = "3.1.0"}
|
||||
|
||||
@@ -1,11 +1,10 @@
|
||||
import de.undercouch.gradle.tasks.download.Download
|
||||
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.kotlinter.get().pluginId)
|
||||
id(libs.plugins.ktlint.get().pluginId)
|
||||
application
|
||||
alias(libs.plugins.shadowjar)
|
||||
id(libs.plugins.buildconfig.get().pluginId)
|
||||
@@ -82,8 +81,9 @@ dependencies {
|
||||
}
|
||||
|
||||
application {
|
||||
applicationDefaultJvmArgs = listOf(
|
||||
"-Djunrar.extractor.thread-keep-alive-seconds=30"
|
||||
applicationDefaultJvmArgs =
|
||||
listOf(
|
||||
"-Djunrar.extractor.thread-keep-alive-seconds=30",
|
||||
)
|
||||
mainClass.set(MainClass)
|
||||
}
|
||||
@@ -98,7 +98,7 @@ sourceSets {
|
||||
|
||||
buildConfig {
|
||||
className("BuildConfig")
|
||||
packageName("suwayomi.tachidesk.server")
|
||||
packageName("suwayomi.tachidesk.server.generated")
|
||||
|
||||
useKotlinOutput()
|
||||
|
||||
@@ -125,7 +125,7 @@ tasks {
|
||||
"Implementation-Title" to rootProject.name,
|
||||
"Implementation-Vendor" to "The Suwayomi Project",
|
||||
"Specification-Version" to tachideskVersion,
|
||||
"Implementation-Version" to tachideskRevision
|
||||
"Implementation-Version" to tachideskRevision,
|
||||
)
|
||||
}
|
||||
archiveBaseName.set(rootProject.name)
|
||||
@@ -151,14 +151,14 @@ tasks {
|
||||
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)
|
||||
|
||||
var shouldOverwrite = true
|
||||
if (zipFile.isValidZipFile) {
|
||||
val zipRevision = zipFile.getInputStream(zipFile.getFileHeader("revision")).bufferedReader().use {
|
||||
val zipRevision =
|
||||
zipFile.getInputStream(zipFile.getFileHeader("revision")).bufferedReader().use {
|
||||
it.readText().trim()
|
||||
}
|
||||
|
||||
@@ -177,10 +177,11 @@ tasks {
|
||||
group = "application"
|
||||
finalizedBy(run)
|
||||
doFirst {
|
||||
application.applicationDefaultJvmArgs = listOf(
|
||||
application.applicationDefaultJvmArgs =
|
||||
listOf(
|
||||
"-Dsuwayomi.tachidesk.config.server.webUIInterface=electron",
|
||||
// Change this to the installed electron application
|
||||
"-Dsuwayomi.tachidesk.config.server.electronPath=/usr/bin/electron"
|
||||
"-Dsuwayomi.tachidesk.config.server.electronPath=/usr/bin/electron",
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,15 +9,11 @@ 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())
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package eu.kanade.tachiyomi
|
||||
|
||||
import suwayomi.tachidesk.manga.impl.util.storage.ImageUtil
|
||||
import suwayomi.tachidesk.server.generated.BuildConfig
|
||||
|
||||
/**
|
||||
* Used by extensions.
|
||||
@@ -14,14 +15,14 @@ object AppInfo {
|
||||
*
|
||||
* @since extension-lib 1.3
|
||||
*/
|
||||
fun getVersionCode() = suwayomi.tachidesk.server.BuildConfig.REVISION.substring(1).toInt()
|
||||
fun getVersionCode() = BuildConfig.REVISION.substring(1).toInt()
|
||||
|
||||
/**
|
||||
* should be something like "0.13.1"
|
||||
*
|
||||
* @since extension-lib 1.3
|
||||
*/
|
||||
fun getVersionName() = suwayomi.tachidesk.server.BuildConfig.VERSION.substring(1)
|
||||
fun getVersionName() = BuildConfig.VERSION.substring(1)
|
||||
|
||||
/**
|
||||
* A list of supported image MIME types by the reader.
|
||||
|
||||
@@ -28,7 +28,6 @@ import uy.kohesive.injekt.api.addSingletonFactory
|
||||
import uy.kohesive.injekt.api.get
|
||||
|
||||
class AppModule(val app: Application) : InjektModule {
|
||||
|
||||
override fun InjektRegistrar.registerInjectables() {
|
||||
addSingleton(app)
|
||||
|
||||
|
||||
@@ -9,7 +9,6 @@ import kotlinx.coroutines.withContext
|
||||
* Util for evaluating JavaScript in sources.
|
||||
*/
|
||||
class JavaScriptEngine(context: Context) {
|
||||
|
||||
/**
|
||||
* Evaluate arbitrary JavaScript code and get the result as a primitive type
|
||||
* (e.g., String, Int).
|
||||
@@ -19,7 +18,8 @@ class JavaScriptEngine(context: Context) {
|
||||
* @return Result of JavaScript code as a primitive type.
|
||||
*/
|
||||
@Suppress("UNUSED", "UNCHECKED_CAST")
|
||||
suspend fun <T> evaluate(script: String): T = withContext(Dispatchers.IO) {
|
||||
suspend fun <T> evaluate(script: String): T =
|
||||
withContext(Dispatchers.IO) {
|
||||
QuickJs.create().use {
|
||||
it.evaluate(script) as T
|
||||
}
|
||||
|
||||
@@ -33,7 +33,10 @@ 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)
|
||||
|
||||
@@ -27,8 +27,7 @@ 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")
|
||||
|
||||
@@ -36,29 +35,34 @@ class NetworkHelper(context: Context) {
|
||||
|
||||
// Tachidesk -->
|
||||
val cookieStore = PersistentCookieStore(context)
|
||||
|
||||
init {
|
||||
CookieHandler.setDefault(
|
||||
CookieManager(cookieStore, CookiePolicy.ACCEPT_ALL)
|
||||
CookieManager(cookieStore, CookiePolicy.ACCEPT_ALL),
|
||||
)
|
||||
}
|
||||
// Tachidesk <--
|
||||
|
||||
private val baseClientBuilder: OkHttpClient.Builder
|
||||
get() {
|
||||
val builder = OkHttpClient.Builder()
|
||||
val builder =
|
||||
OkHttpClient.Builder()
|
||||
.cookieJar(PersistentCookieJar(cookieStore))
|
||||
.connectTimeout(30, TimeUnit.SECONDS)
|
||||
.readTimeout(30, TimeUnit.SECONDS)
|
||||
.callTimeout(2, TimeUnit.MINUTES)
|
||||
.addInterceptor(UserAgentInterceptor())
|
||||
|
||||
val httpLoggingInterceptor = HttpLoggingInterceptor(object : HttpLoggingInterceptor.Logger {
|
||||
val httpLoggingInterceptor =
|
||||
HttpLoggingInterceptor(
|
||||
object : HttpLoggingInterceptor.Logger {
|
||||
val logger = KotlinLogging.logger { }
|
||||
|
||||
override fun log(message: String) {
|
||||
logger.debug { message }
|
||||
}
|
||||
}).apply {
|
||||
},
|
||||
).apply {
|
||||
level = HttpLoggingInterceptor.Level.BASIC
|
||||
}
|
||||
builder.addInterceptor(httpLoggingInterceptor)
|
||||
|
||||
@@ -25,7 +25,8 @@ fun Call.asObservable(): Observable<Response> {
|
||||
val call = clone()
|
||||
|
||||
// Wrap the call in a helper which handles both unsubscription and backpressure.
|
||||
val requestArbiter = object : AtomicBoolean(), Producer, Subscription {
|
||||
val requestArbiter =
|
||||
object : AtomicBoolean(), Producer, Subscription {
|
||||
override fun request(n: Long) {
|
||||
if (n == 0L || !compareAndSet(false, true)) return
|
||||
|
||||
@@ -72,13 +73,19 @@ private suspend fun Call.await(callStack: Array<StackTraceElement>): Response {
|
||||
return suspendCancellableCoroutine { continuation ->
|
||||
val callback =
|
||||
object : Callback {
|
||||
override fun onResponse(call: Call, response: Response) {
|
||||
override fun onResponse(
|
||||
call: Call,
|
||||
response: Response,
|
||||
) {
|
||||
continuation.resume(response) {
|
||||
response.body.close()
|
||||
}
|
||||
}
|
||||
|
||||
override fun onFailure(call: Call, e: IOException) {
|
||||
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 }
|
||||
@@ -116,8 +123,12 @@ suspend fun Call.awaitSuccess(): Response {
|
||||
return response
|
||||
}
|
||||
|
||||
fun OkHttpClient.newCachelessCallWithProgress(request: Request, listener: ProgressListener): Call {
|
||||
val progressClient = newBuilder()
|
||||
fun OkHttpClient.newCachelessCallWithProgress(
|
||||
request: Request,
|
||||
listener: ProgressListener,
|
||||
): Call {
|
||||
val progressClient =
|
||||
newBuilder()
|
||||
.cache(null)
|
||||
.addNetworkInterceptor { chain ->
|
||||
val originalResponse = chain.proceed(chain.request())
|
||||
@@ -139,7 +150,7 @@ context(Json)
|
||||
@OptIn(ExperimentalSerializationApi::class)
|
||||
fun <T> decodeFromJsonResponse(
|
||||
deserializer: DeserializationStrategy<T>,
|
||||
response: Response
|
||||
response: Response,
|
||||
): T {
|
||||
return response.body.source().use {
|
||||
decodeFromBufferedSource(deserializer, it)
|
||||
|
||||
@@ -6,8 +6,10 @@ import okhttp3.HttpUrl
|
||||
|
||||
// from TachiWeb-Server
|
||||
class PersistentCookieJar(private val store: PersistentCookieStore) : CookieJar {
|
||||
|
||||
override fun saveFromResponse(url: HttpUrl, cookies: List<Cookie>) {
|
||||
override fun saveFromResponse(
|
||||
url: HttpUrl,
|
||||
cookies: List<Cookie>,
|
||||
) {
|
||||
store.addAll(url, cookies)
|
||||
}
|
||||
|
||||
|
||||
@@ -15,7 +15,6 @@ import kotlin.time.Duration.Companion.seconds
|
||||
|
||||
// from TachiWeb-Server
|
||||
class PersistentCookieStore(context: Context) : CookieStore {
|
||||
|
||||
private val cookieMap = ConcurrentHashMap<String, List<Cookie>>()
|
||||
private val prefs = context.getSharedPreferences("cookie_store", Context.MODE_PRIVATE)
|
||||
|
||||
@@ -28,7 +27,8 @@ class PersistentCookieStore(context: Context) : CookieStore {
|
||||
if (cookies != null) {
|
||||
try {
|
||||
val url = "http://$key".toHttpUrlOrNull() ?: continue
|
||||
val nonExpiredCookies = cookies.mapNotNull { Cookie.parse(url, it) }
|
||||
val nonExpiredCookies =
|
||||
cookies.mapNotNull { Cookie.parse(url, it) }
|
||||
.filter { !it.hasExpired() }
|
||||
cookieMap.put(key, nonExpiredCookies)
|
||||
} catch (e: Exception) {
|
||||
@@ -38,7 +38,10 @@ class PersistentCookieStore(context: Context) : CookieStore {
|
||||
}
|
||||
}
|
||||
|
||||
fun addAll(url: HttpUrl, cookies: List<Cookie>) {
|
||||
fun addAll(
|
||||
url: HttpUrl,
|
||||
cookies: List<Cookie>,
|
||||
) {
|
||||
lock.withLock {
|
||||
val uri = url.toUri()
|
||||
|
||||
@@ -75,13 +78,17 @@ class PersistentCookieStore(context: Context) : CookieStore {
|
||||
}
|
||||
}
|
||||
|
||||
override fun get(uri: URI): List<HttpCookie> = get(uri.host).map {
|
||||
override fun get(uri: URI): List<HttpCookie> =
|
||||
get(uri.host).map {
|
||||
it.toHttpCookie()
|
||||
}
|
||||
|
||||
fun get(url: HttpUrl) = get(url.toUri().host)
|
||||
|
||||
override fun add(uri: URI?, cookie: HttpCookie) {
|
||||
override fun add(
|
||||
uri: URI?,
|
||||
cookie: HttpCookie,
|
||||
) {
|
||||
@Suppress("NAME_SHADOWING")
|
||||
val uri = uri ?: URI("http://" + cookie.domain.removePrefix("."))
|
||||
lock.withLock {
|
||||
@@ -105,12 +112,16 @@ class PersistentCookieStore(context: Context) : CookieStore {
|
||||
}
|
||||
}
|
||||
|
||||
override fun remove(uri: URI?, cookie: HttpCookie): Boolean {
|
||||
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 {
|
||||
val index =
|
||||
cookies.indexOfFirst {
|
||||
it.name == cookie.name &&
|
||||
it.path == cookie.path
|
||||
}
|
||||
@@ -132,7 +143,8 @@ class PersistentCookieStore(context: Context) : CookieStore {
|
||||
|
||||
private fun saveToDisk(uri: URI) {
|
||||
// Get cookies to be stored in disk
|
||||
val newValues = cookieMap[uri.host]
|
||||
val newValues =
|
||||
cookieMap[uri.host]
|
||||
.orEmpty()
|
||||
.asSequence()
|
||||
.filter { it.persistent && !it.hasExpired() }
|
||||
@@ -144,7 +156,8 @@ class PersistentCookieStore(context: Context) : CookieStore {
|
||||
|
||||
private fun Cookie.hasExpired() = System.currentTimeMillis() >= expiresAt
|
||||
|
||||
private fun HttpCookie.toCookie(uri: URI) = Cookie.Builder()
|
||||
private fun HttpCookie.toCookie(uri: URI) =
|
||||
Cookie.Builder()
|
||||
.name(name)
|
||||
.value(value)
|
||||
.domain(uri.host)
|
||||
@@ -178,7 +191,8 @@ class PersistentCookieStore(context: Context) : CookieStore {
|
||||
domain = it.domain
|
||||
path = it.path
|
||||
secure = it.secure
|
||||
maxAge = if (it.persistent) {
|
||||
maxAge =
|
||||
if (it.persistent) {
|
||||
-1
|
||||
} else {
|
||||
(it.expiresAt.milliseconds - System.currentTimeMillis().milliseconds).inWholeSeconds
|
||||
|
||||
@@ -1,5 +1,9 @@
|
||||
package eu.kanade.tachiyomi.network
|
||||
|
||||
interface ProgressListener {
|
||||
fun update(bytesRead: Long, contentLength: Long, done: Boolean)
|
||||
fun update(
|
||||
bytesRead: Long,
|
||||
contentLength: Long,
|
||||
done: Boolean,
|
||||
)
|
||||
}
|
||||
|
||||
@@ -10,7 +10,6 @@ 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()
|
||||
}
|
||||
@@ -32,7 +31,10 @@ 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
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
@file:Suppress("ktlint:standard:function-naming")
|
||||
|
||||
package eu.kanade.tachiyomi.network
|
||||
|
||||
import okhttp3.CacheControl
|
||||
@@ -15,7 +17,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)
|
||||
@@ -30,7 +32,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)
|
||||
@@ -43,7 +45,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)
|
||||
|
||||
@@ -81,7 +81,8 @@ object CFClearance {
|
||||
|
||||
logger.debug { "resolveWithWebView($url)" }
|
||||
|
||||
val cookies = Playwright.create().use { playwright ->
|
||||
val cookies =
|
||||
Playwright.create().use { playwright ->
|
||||
playwright.chromium().launch(
|
||||
LaunchOptions()
|
||||
.setHeadless(false)
|
||||
@@ -89,7 +90,7 @@ object CFClearance {
|
||||
if (serverConfig.socksProxyEnabled.value) {
|
||||
setProxy("socks5://${serverConfig.socksProxyHost.value}:${serverConfig.socksProxyPort.value}")
|
||||
}
|
||||
}
|
||||
},
|
||||
).use { browser ->
|
||||
val userAgent = originalRequest.header("User-Agent")
|
||||
if (userAgent != null) {
|
||||
@@ -105,25 +106,29 @@ object CFClearance {
|
||||
// Copy cookies to cookie store
|
||||
cookies.groupBy { it.domain }.forEach { (domain, cookies) ->
|
||||
network.cookieStore.addAll(
|
||||
url = HttpUrl.Builder()
|
||||
url =
|
||||
HttpUrl.Builder()
|
||||
.scheme("http")
|
||||
.host(domain)
|
||||
.build(),
|
||||
cookies = cookies
|
||||
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 {
|
||||
val convertedForThisRequest =
|
||||
cookies.filter {
|
||||
it.matches(originalRequest.url)
|
||||
}
|
||||
// Extract cookies from current request
|
||||
val existingCookies = Cookie.parseAll(
|
||||
val existingCookies =
|
||||
Cookie.parseAll(
|
||||
originalRequest.url,
|
||||
originalRequest.headers
|
||||
originalRequest.headers,
|
||||
)
|
||||
// Filter out existing values of cookies that we are about to merge in
|
||||
val filteredExisting = existingCookies.filter { existing ->
|
||||
val filteredExisting =
|
||||
existingCookies.filter { existing ->
|
||||
convertedForThisRequest.none { converted -> converted.name == existing.name }
|
||||
}
|
||||
logger.trace { "Existing cookies" }
|
||||
@@ -143,7 +148,7 @@ object CFClearance {
|
||||
Playwright.create().use { playwright ->
|
||||
playwright.chromium().launch(
|
||||
LaunchOptions()
|
||||
.setHeadless(true)
|
||||
.setHeadless(true),
|
||||
).use { browser ->
|
||||
browser.newPage().use { page ->
|
||||
val userAgent = page.evaluate("() => {return navigator.userAgent}") as String
|
||||
@@ -158,7 +163,10 @@ object CFClearance {
|
||||
}
|
||||
}
|
||||
|
||||
private fun getCookies(page: Page, url: String): List<Cookie> {
|
||||
private fun getCookies(
|
||||
page: Page,
|
||||
url: String,
|
||||
): List<Cookie> {
|
||||
applyStealthInitScripts(page)
|
||||
page.navigate(url)
|
||||
val challengeResolved = waitForChallengeResolve(page)
|
||||
@@ -198,7 +206,7 @@ object CFClearance {
|
||||
ServerConfig::class.java.getResource("/cloudflare-js/navigator.permissions.js")!!.readText(),
|
||||
ServerConfig::class.java.getResource("/cloudflare-js/navigator.webdriver.js")!!.readText(),
|
||||
ServerConfig::class.java.getResource("/cloudflare-js/chrome.runtime.js")!!.readText(),
|
||||
ServerConfig::class.java.getResource("/cloudflare-js/chrome.plugin.js")!!.readText()
|
||||
ServerConfig::class.java.getResource("/cloudflare-js/chrome.plugin.js")!!.readText(),
|
||||
)
|
||||
}
|
||||
|
||||
@@ -215,7 +223,8 @@ object CFClearance {
|
||||
val timeoutSeconds = 120
|
||||
repeat(timeoutSeconds) {
|
||||
page.waitForTimeout(1.seconds.toDouble(DurationUnit.MILLISECONDS))
|
||||
val success = try {
|
||||
val success =
|
||||
try {
|
||||
page.querySelector("#challenge-form") == null
|
||||
} catch (e: Exception) {
|
||||
logger.debug(e) { "query Error" }
|
||||
|
||||
@@ -34,7 +34,7 @@ import kotlin.time.toDurationUnit
|
||||
fun OkHttpClient.Builder.rateLimit(
|
||||
permits: Int,
|
||||
period: Long = 1,
|
||||
unit: TimeUnit = TimeUnit.SECONDS
|
||||
unit: TimeUnit = TimeUnit.SECONDS,
|
||||
) = addInterceptor(RateLimitInterceptor(null, permits, period.toDuration(unit.toDurationUnit())))
|
||||
|
||||
/**
|
||||
@@ -50,17 +50,18 @@ fun OkHttpClient.Builder.rateLimit(
|
||||
* @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))
|
||||
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 val permits: Int,
|
||||
period: Duration
|
||||
period: Duration,
|
||||
) : Interceptor {
|
||||
|
||||
private val requestQueue = ArrayDeque<Long>(permits)
|
||||
private val rateLimitMillis = period.inWholeMilliseconds
|
||||
private val fairLock = Semaphore(1, true)
|
||||
@@ -98,7 +99,8 @@ internal class RateLimitInterceptor(
|
||||
} else if (hasRemovedExpired) {
|
||||
break
|
||||
} else {
|
||||
try { // wait for the first entry to expire, or notified by cached response
|
||||
try {
|
||||
// wait for the first entry to expire, or notified by cached response
|
||||
(requestQueue as Object).wait(requestQueue.first - periodStart)
|
||||
} catch (_: InterruptedException) {
|
||||
continue
|
||||
|
||||
@@ -29,7 +29,7 @@ fun OkHttpClient.Builder.rateLimitHost(
|
||||
httpUrl: HttpUrl,
|
||||
permits: Int,
|
||||
period: Long = 1,
|
||||
unit: TimeUnit = TimeUnit.SECONDS
|
||||
unit: TimeUnit = TimeUnit.SECONDS,
|
||||
) = addInterceptor(RateLimitInterceptor(httpUrl.host, permits, period.toDuration(unit.toDurationUnit())))
|
||||
|
||||
/**
|
||||
@@ -49,7 +49,7 @@ fun OkHttpClient.Builder.rateLimitHost(
|
||||
fun OkHttpClient.Builder.rateLimitHost(
|
||||
httpUrl: HttpUrl,
|
||||
permits: Int,
|
||||
period: Duration = 1.seconds
|
||||
period: Duration = 1.seconds,
|
||||
): OkHttpClient.Builder = addInterceptor(RateLimitInterceptor(httpUrl.host, permits, period))
|
||||
|
||||
/**
|
||||
@@ -69,5 +69,5 @@ fun OkHttpClient.Builder.rateLimitHost(
|
||||
fun OkHttpClient.Builder.rateLimitHost(
|
||||
url: String,
|
||||
permits: Int,
|
||||
period: Duration = 1.seconds
|
||||
period: Duration = 1.seconds,
|
||||
): OkHttpClient.Builder = addInterceptor(RateLimitInterceptor(url.toHttpUrlOrNull()?.host, permits, period))
|
||||
|
||||
@@ -9,7 +9,8 @@ class UserAgentInterceptor : Interceptor {
|
||||
val originalRequest = chain.request()
|
||||
|
||||
return if (originalRequest.header("User-Agent").isNullOrEmpty()) {
|
||||
val newRequest = originalRequest
|
||||
val newRequest =
|
||||
originalRequest
|
||||
.newBuilder()
|
||||
.removeHeader("User-Agent")
|
||||
.addHeader("User-Agent", HttpSource.DEFAULT_USER_AGENT)
|
||||
|
||||
@@ -6,7 +6,6 @@ import rx.Observable
|
||||
import suwayomi.tachidesk.manga.impl.util.lang.awaitSingle
|
||||
|
||||
interface CatalogueSource : Source {
|
||||
|
||||
/**
|
||||
* An ISO 639-1 compliant language code (two letters in lower case).
|
||||
*/
|
||||
@@ -37,7 +36,11 @@ interface CatalogueSource : Source {
|
||||
* @param filters the list of filters to apply.
|
||||
*/
|
||||
@Suppress("DEPRECATION")
|
||||
suspend fun getSearchManga(page: Int, query: String, filters: FilterList): MangasPage {
|
||||
suspend fun getSearchManga(
|
||||
page: Int,
|
||||
query: String,
|
||||
filters: FilterList,
|
||||
): MangasPage {
|
||||
return fetchSearchManga(page, query, filters).awaitSingle()
|
||||
}
|
||||
|
||||
@@ -59,22 +62,23 @@ interface CatalogueSource : Source {
|
||||
|
||||
@Deprecated(
|
||||
"Use the non-RxJava API instead",
|
||||
ReplaceWith("getPopularManga")
|
||||
ReplaceWith("getPopularManga"),
|
||||
)
|
||||
fun fetchPopularManga(page: Int): Observable<MangasPage> =
|
||||
throw IllegalStateException("Not used")
|
||||
fun fetchPopularManga(page: Int): Observable<MangasPage> = throw IllegalStateException("Not used")
|
||||
|
||||
@Deprecated(
|
||||
"Use the non-RxJava API instead",
|
||||
ReplaceWith("getSearchManga")
|
||||
ReplaceWith("getSearchManga"),
|
||||
)
|
||||
fun fetchSearchManga(page: Int, query: String, filters: FilterList): Observable<MangasPage> =
|
||||
throw IllegalStateException("Not used")
|
||||
fun fetchSearchManga(
|
||||
page: Int,
|
||||
query: String,
|
||||
filters: FilterList,
|
||||
): Observable<MangasPage> = throw IllegalStateException("Not used")
|
||||
|
||||
@Deprecated(
|
||||
"Use the non-RxJava API instead",
|
||||
ReplaceWith("getLatestUpdates")
|
||||
ReplaceWith("getLatestUpdates"),
|
||||
)
|
||||
fun fetchLatestUpdates(page: Int): Observable<MangasPage> =
|
||||
throw IllegalStateException("Not used")
|
||||
fun fetchLatestUpdates(page: Int): Observable<MangasPage> = throw IllegalStateException("Not used")
|
||||
}
|
||||
|
||||
@@ -8,14 +8,12 @@ import uy.kohesive.injekt.Injekt
|
||||
import uy.kohesive.injekt.api.get
|
||||
|
||||
interface ConfigurableSource : Source {
|
||||
|
||||
/**
|
||||
* Gets instance of [SharedPreferences] scoped to the specific source.
|
||||
*
|
||||
* @since extensions-lib 1.5
|
||||
*/
|
||||
fun getSourcePreferences(): SharedPreferences =
|
||||
Injekt.get<Application>().getSharedPreferences(preferenceKey(), Context.MODE_PRIVATE)
|
||||
fun getSourcePreferences(): SharedPreferences = Injekt.get<Application>().getSharedPreferences(preferenceKey(), Context.MODE_PRIVATE)
|
||||
|
||||
fun setupPreferenceScreen(screen: PreferenceScreen)
|
||||
}
|
||||
|
||||
@@ -10,7 +10,6 @@ import suwayomi.tachidesk.manga.impl.util.lang.awaitSingle
|
||||
* A basic interface for creating a source. It could be an online source, a local source, etc...
|
||||
*/
|
||||
interface Source {
|
||||
|
||||
/**
|
||||
* Id for the source. Must be unique.
|
||||
*/
|
||||
@@ -60,19 +59,19 @@ interface Source {
|
||||
|
||||
@Deprecated(
|
||||
"Use the non-RxJava API instead",
|
||||
ReplaceWith("getMangaDetails")
|
||||
ReplaceWith("getMangaDetails"),
|
||||
)
|
||||
fun fetchMangaDetails(manga: SManga): Observable<SManga> = throw IllegalStateException("Not used")
|
||||
|
||||
@Deprecated(
|
||||
"Use the non-RxJava API instead",
|
||||
ReplaceWith("getChapterList")
|
||||
ReplaceWith("getChapterList"),
|
||||
)
|
||||
fun fetchChapterList(manga: SManga): Observable<List<SChapter>> = throw IllegalStateException("Not used")
|
||||
|
||||
@Deprecated(
|
||||
"Use the non-RxJava API instead",
|
||||
ReplaceWith("getPageList")
|
||||
ReplaceWith("getPageList"),
|
||||
)
|
||||
fun fetchPageList(chapter: SChapter): Observable<List<Page>> = Observable.empty()
|
||||
}
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
@file:Suppress("ktlint:standard:property-naming")
|
||||
|
||||
package eu.kanade.tachiyomi.source.local
|
||||
|
||||
import eu.kanade.tachiyomi.source.CatalogueSource
|
||||
@@ -55,9 +57,8 @@ import com.github.junrar.Archive as JunrarArchive
|
||||
|
||||
class LocalSource(
|
||||
private val fileSystem: LocalSourceFileSystem,
|
||||
private val coverManager: LocalCoverManager
|
||||
private val coverManager: LocalCoverManager,
|
||||
) : CatalogueSource, UnmeteredSource {
|
||||
|
||||
private val json: Json by injectLazy()
|
||||
private val xml: XML by injectLazy()
|
||||
|
||||
@@ -79,10 +80,15 @@ class LocalSource(
|
||||
|
||||
override suspend fun getLatestUpdates(page: Int) = getSearchManga(page, "", LATEST_FILTERS)
|
||||
|
||||
override suspend fun getSearchManga(page: Int, query: String, filters: FilterList): MangasPage {
|
||||
override suspend fun getSearchManga(
|
||||
page: Int,
|
||||
query: String,
|
||||
filters: FilterList,
|
||||
): MangasPage {
|
||||
val baseDirsFiles = fileSystem.getFilesInBaseDirectories()
|
||||
val lastModifiedLimit by lazy { if (filters === LATEST_FILTERS) System.currentTimeMillis() - LATEST_THRESHOLD else 0L }
|
||||
var mangaDirs = baseDirsFiles
|
||||
var mangaDirs =
|
||||
baseDirsFiles
|
||||
// Filter out files that are hidden and is not a folder
|
||||
.filter { it.isDirectory && !it.name.startsWith('.') }
|
||||
.distinctBy { it.name }
|
||||
@@ -97,14 +103,16 @@ class LocalSource(
|
||||
filters.forEach { filter ->
|
||||
when (filter) {
|
||||
is OrderBy.Popular -> {
|
||||
mangaDirs = if (filter.state!!.ascending) {
|
||||
mangaDirs =
|
||||
if (filter.state!!.ascending) {
|
||||
mangaDirs.sortedWith(compareBy(String.CASE_INSENSITIVE_ORDER) { it.name })
|
||||
} else {
|
||||
mangaDirs.sortedWith(compareByDescending(String.CASE_INSENSITIVE_ORDER) { it.name })
|
||||
}
|
||||
}
|
||||
is OrderBy.Latest -> {
|
||||
mangaDirs = if (filter.state!!.ascending) {
|
||||
mangaDirs =
|
||||
if (filter.state!!.ascending) {
|
||||
mangaDirs.sortedBy(File::lastModified)
|
||||
} else {
|
||||
mangaDirs.sortedByDescending(File::lastModified)
|
||||
@@ -112,13 +120,14 @@ class LocalSource(
|
||||
}
|
||||
|
||||
else -> {
|
||||
/* Do nothing */
|
||||
// Do nothing
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Transform mangaDirs to list of SManga
|
||||
val mangas = mangaDirs.map { mangaDir ->
|
||||
val mangas =
|
||||
mangaDirs.map { mangaDir ->
|
||||
SManga.create().apply {
|
||||
title = mangaDir.name
|
||||
url = mangaDir.name
|
||||
@@ -156,7 +165,8 @@ class LocalSource(
|
||||
}
|
||||
|
||||
// Manga details related
|
||||
override suspend fun getMangaDetails(manga: SManga): SManga = withContext(Dispatchers.IO) {
|
||||
override suspend fun getMangaDetails(manga: SManga): SManga =
|
||||
withContext(Dispatchers.IO) {
|
||||
coverManager.find(manga.url)?.let {
|
||||
manga.thumbnail_url = it.absolutePath
|
||||
}
|
||||
@@ -165,11 +175,14 @@ class LocalSource(
|
||||
try {
|
||||
val mangaDirFiles = fileSystem.getFilesInMangaDirectory(manga.url).toList()
|
||||
|
||||
val comicInfoFile = mangaDirFiles
|
||||
val comicInfoFile =
|
||||
mangaDirFiles
|
||||
.firstOrNull { it.name == COMIC_INFO_FILE }
|
||||
val noXmlFile = mangaDirFiles
|
||||
val noXmlFile =
|
||||
mangaDirFiles
|
||||
.firstOrNull { it.name == ".noxml" }
|
||||
val legacyJsonDetailsFile = mangaDirFiles
|
||||
val legacyJsonDetailsFile =
|
||||
mangaDirFiles
|
||||
.firstOrNull { it.extension == "json" }
|
||||
|
||||
when {
|
||||
@@ -193,7 +206,8 @@ class LocalSource(
|
||||
|
||||
// Copy ComicInfo.xml from chapter archive to top level if found
|
||||
noXmlFile == null -> {
|
||||
val chapterArchives = mangaDirFiles
|
||||
val chapterArchives =
|
||||
mangaDirFiles
|
||||
.filter(Archive::isSupported)
|
||||
.toList()
|
||||
|
||||
@@ -216,7 +230,10 @@ class LocalSource(
|
||||
return@withContext manga
|
||||
}
|
||||
|
||||
private fun copyComicInfoFileFromArchive(chapterArchives: List<File>, folderPath: String?): File? {
|
||||
private fun copyComicInfoFileFromArchive(
|
||||
chapterArchives: List<File>,
|
||||
folderPath: String?,
|
||||
): File? {
|
||||
for (chapter in chapterArchives) {
|
||||
when (Format.valueOf(chapter)) {
|
||||
is Format.Zip -> {
|
||||
@@ -243,7 +260,10 @@ class LocalSource(
|
||||
return null
|
||||
}
|
||||
|
||||
private fun copyComicInfoFile(comicInfoFileStream: InputStream, folderPath: String?): File {
|
||||
private fun copyComicInfoFile(
|
||||
comicInfoFileStream: InputStream,
|
||||
folderPath: String?,
|
||||
): File {
|
||||
return File("$folderPath/$COMIC_INFO_FILE").apply {
|
||||
outputStream().use { outputStream ->
|
||||
comicInfoFileStream.use { it.copyTo(outputStream) }
|
||||
@@ -251,8 +271,12 @@ class LocalSource(
|
||||
}
|
||||
}
|
||||
|
||||
private fun setMangaDetailsFromComicInfoFile(stream: InputStream, manga: SManga) {
|
||||
val comicInfo = KtXmlReader(stream, StandardCharsets.UTF_8.name()).use {
|
||||
private fun setMangaDetailsFromComicInfoFile(
|
||||
stream: InputStream,
|
||||
manga: SManga,
|
||||
) {
|
||||
val comicInfo =
|
||||
KtXmlReader(stream, StandardCharsets.UTF_8.name()).use {
|
||||
xml.decodeFromReader<ComicInfo>(it)
|
||||
}
|
||||
|
||||
@@ -267,13 +291,15 @@ class LocalSource(
|
||||
.map { chapterFile ->
|
||||
SChapter.create().apply {
|
||||
url = "${manga.url}/${chapterFile.name}"
|
||||
name = if (chapterFile.isDirectory) {
|
||||
name =
|
||||
if (chapterFile.isDirectory) {
|
||||
chapterFile.name
|
||||
} else {
|
||||
chapterFile.nameWithoutExtension
|
||||
}
|
||||
date_upload = chapterFile.lastModified()
|
||||
chapter_number = ChapterRecognition
|
||||
chapter_number =
|
||||
ChapterRecognition
|
||||
.parseChapterNumber(manga.title, this.name, this.chapter_number.toDouble())
|
||||
.toFloat()
|
||||
|
||||
@@ -305,7 +331,7 @@ class LocalSource(
|
||||
.mapIndexed { index, page ->
|
||||
Page(
|
||||
index,
|
||||
imageUrl = applicationDirs.localMangaRoot + "/" + chapter.url + "/" + page.name
|
||||
imageUrl = applicationDirs.localMangaRoot + "/" + chapter.url + "/" + page.name,
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -347,11 +373,15 @@ class LocalSource(
|
||||
}
|
||||
}
|
||||
|
||||
private fun updateCover(chapter: SChapter, manga: SManga): File? {
|
||||
private fun updateCover(
|
||||
chapter: SChapter,
|
||||
manga: SManga,
|
||||
): File? {
|
||||
return try {
|
||||
when (val format = getFormat(chapter)) {
|
||||
is Format.Directory -> {
|
||||
val entry = format.file.listFiles()
|
||||
val entry =
|
||||
format.file.listFiles()
|
||||
?.sortedWith { f1, f2 -> f1.name.compareToCaseInsensitiveNaturalOrder(f2.name) }
|
||||
?.find { !it.isDirectory && ImageUtil.isImage(it.name) { FileInputStream(it) } }
|
||||
|
||||
@@ -359,7 +389,8 @@ class LocalSource(
|
||||
}
|
||||
is Format.Zip -> {
|
||||
ZipFile(format.file).use { zip ->
|
||||
val entry = zip.entries.toList()
|
||||
val entry =
|
||||
zip.entries.toList()
|
||||
.sortedWith { f1, f2 -> f1.name.compareToCaseInsensitiveNaturalOrder(f2.name) }
|
||||
.find { !it.isDirectory && ImageUtil.isImage(it.name) { zip.getInputStream(it) } }
|
||||
|
||||
@@ -368,7 +399,8 @@ class LocalSource(
|
||||
}
|
||||
is Format.Rar -> {
|
||||
JunrarArchive(format.file).use { archive ->
|
||||
val entry = archive.fileHeaders
|
||||
val entry =
|
||||
archive.fileHeaders
|
||||
.sortedWith { f1, f2 -> f1.fileName.compareToCaseInsensitiveNaturalOrder(f2.fileName) }
|
||||
.find { !it.isDirectory && ImageUtil.isImage(it.fileName) { archive.getInputStream(it) } }
|
||||
|
||||
@@ -377,7 +409,8 @@ class LocalSource(
|
||||
}
|
||||
is Format.Epub -> {
|
||||
EpubFile(format.file).use { epub ->
|
||||
val entry = epub.getImagesFromPages()
|
||||
val entry =
|
||||
epub.getImagesFromPages()
|
||||
.firstOrNull()
|
||||
?.let { epub.getEntry(it) }
|
||||
|
||||
@@ -412,7 +445,8 @@ class LocalSource(
|
||||
|
||||
if (sourceRecord == null) {
|
||||
// must do this to avoid database integrity errors
|
||||
val extensionId = ExtensionTable.insertAndGetId {
|
||||
val extensionId =
|
||||
ExtensionTable.insertAndGetId {
|
||||
it[apkName] = "localSource"
|
||||
it[name] = EXTENSION_NAME
|
||||
it[pkgName] = LocalSource::class.java.`package`.name
|
||||
|
||||
@@ -5,8 +5,9 @@ import eu.kanade.tachiyomi.source.model.Filter
|
||||
sealed class OrderBy(selection: Selection) : Filter.Sort(
|
||||
"Order by",
|
||||
arrayOf("Title", "Date"),
|
||||
selection
|
||||
selection,
|
||||
) {
|
||||
class Popular() : OrderBy(Selection(0, true))
|
||||
|
||||
class Latest() : OrderBy(Selection(1, false))
|
||||
}
|
||||
|
||||
@@ -9,9 +9,8 @@ import java.io.InputStream
|
||||
private const val DEFAULT_COVER_NAME = "cover.jpg"
|
||||
|
||||
class LocalCoverManager(
|
||||
private val fileSystem: LocalSourceFileSystem
|
||||
private val fileSystem: LocalSourceFileSystem,
|
||||
) {
|
||||
|
||||
fun find(mangaUrl: String): File? {
|
||||
return fileSystem.getFilesInMangaDirectory(mangaUrl)
|
||||
// Get all file whose names start with 'cover'
|
||||
@@ -24,7 +23,7 @@ class LocalCoverManager(
|
||||
|
||||
fun update(
|
||||
manga: SManga,
|
||||
inputStream: InputStream
|
||||
inputStream: InputStream,
|
||||
): File? {
|
||||
val directory = fileSystem.getMangaDirectory(manga.url)
|
||||
if (directory == null) {
|
||||
|
||||
@@ -3,10 +3,10 @@ package eu.kanade.tachiyomi.source.local.io
|
||||
import java.io.File
|
||||
|
||||
object Archive {
|
||||
|
||||
private val SUPPORTED_ARCHIVE_TYPES = listOf("zip", "cbz", "rar", "cbr", "epub")
|
||||
|
||||
fun isSupported(file: File): Boolean = with(file) {
|
||||
fun isSupported(file: File): Boolean =
|
||||
with(file) {
|
||||
return extension.lowercase() in SUPPORTED_ARCHIVE_TYPES
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,15 +4,18 @@ import java.io.File
|
||||
|
||||
sealed interface Format {
|
||||
data class Directory(val file: File) : Format
|
||||
|
||||
data class Zip(val file: File) : Format
|
||||
|
||||
data class Rar(val file: File) : Format
|
||||
|
||||
data class Epub(val file: File) : Format
|
||||
|
||||
class UnknownFormatException : Exception()
|
||||
|
||||
companion object {
|
||||
|
||||
fun valueOf(file: File) = with(file) {
|
||||
fun valueOf(file: File) =
|
||||
with(file) {
|
||||
when {
|
||||
isDirectory -> Directory(this)
|
||||
extension.equals("zip", true) || extension.equals("cbz", true) -> Zip(this)
|
||||
|
||||
@@ -4,9 +4,8 @@ import suwayomi.tachidesk.server.ApplicationDirs
|
||||
import java.io.File
|
||||
|
||||
class LocalSourceFileSystem(
|
||||
private val applicationDirs: ApplicationDirs
|
||||
private val applicationDirs: ApplicationDirs,
|
||||
) {
|
||||
|
||||
fun getBaseDirectories(): Sequence<File> {
|
||||
return sequenceOf(File(applicationDirs.localMangaRoot))
|
||||
}
|
||||
|
||||
@@ -7,7 +7,6 @@ import java.io.File
|
||||
* Loader used to load a chapter from a .epub file.
|
||||
*/
|
||||
class EpubPageLoader(file: File) : PageLoader {
|
||||
|
||||
private val epub = EpubFile(file)
|
||||
|
||||
override suspend fun getPages(): List<ReaderPage> {
|
||||
|
||||
@@ -13,7 +13,6 @@ import java.io.PipedOutputStream
|
||||
* Loader used to load a chapter from a .rar or .cbr file.
|
||||
*/
|
||||
class RarPageLoader(file: File) : PageLoader {
|
||||
|
||||
private val rar = Archive(file)
|
||||
|
||||
override suspend fun getPages(): List<ReaderPage> {
|
||||
@@ -35,7 +34,10 @@ class RarPageLoader(file: File) : PageLoader {
|
||||
/**
|
||||
* Returns an input stream for the given [header].
|
||||
*/
|
||||
private fun getStream(rar: Archive, header: FileHeader): InputStream {
|
||||
private fun getStream(
|
||||
rar: Archive,
|
||||
header: FileHeader,
|
||||
): InputStream {
|
||||
val pipeIn = PipedInputStream()
|
||||
val pipeOut = PipedOutputStream(pipeIn)
|
||||
synchronized(this) {
|
||||
|
||||
@@ -7,5 +7,5 @@ class ReaderPage(
|
||||
index: Int,
|
||||
url: String = "",
|
||||
imageUrl: String? = null,
|
||||
var stream: (() -> InputStream)? = null
|
||||
var stream: (() -> InputStream)? = null,
|
||||
) : Page(index, url, imageUrl, null)
|
||||
|
||||
@@ -9,7 +9,6 @@ import java.io.File
|
||||
* Loader used to load a chapter from a .zip or .cbz file.
|
||||
*/
|
||||
class ZipPageLoader(file: File) : PageLoader {
|
||||
|
||||
private val zip = ZipFile(file)
|
||||
|
||||
override suspend fun getPages(): List<ReaderPage> {
|
||||
|
||||
@@ -16,7 +16,7 @@ fun SManga.copyFromComicInfo(comicInfo: ComicInfo) {
|
||||
listOfNotNull(
|
||||
comicInfo.genre?.value,
|
||||
comicInfo.tags?.value,
|
||||
comicInfo.categories?.value
|
||||
comicInfo.categories?.value,
|
||||
)
|
||||
.distinct()
|
||||
.joinToString(", ") { it.trim() }
|
||||
@@ -28,7 +28,7 @@ fun SManga.copyFromComicInfo(comicInfo: ComicInfo) {
|
||||
comicInfo.inker?.value,
|
||||
comicInfo.colorist?.value,
|
||||
comicInfo.letterer?.value,
|
||||
comicInfo.coverArtist?.value
|
||||
comicInfo.coverArtist?.value,
|
||||
)
|
||||
.flatMap { it.split(", ") }
|
||||
.distinct()
|
||||
@@ -57,7 +57,7 @@ data class ComicInfo(
|
||||
val tags: Tags?,
|
||||
val web: Web?,
|
||||
val publishingStatus: PublishingStatusTachiyomi?,
|
||||
val categories: CategoriesTachiyomi?
|
||||
val categories: CategoriesTachiyomi?,
|
||||
) {
|
||||
@Suppress("UNUSED")
|
||||
@XmlElement(false)
|
||||
@@ -71,73 +71,105 @@ data class ComicInfo(
|
||||
|
||||
@Serializable
|
||||
@XmlSerialName("Title", "", "")
|
||||
data class Title(@XmlValue(true) val value: String = "")
|
||||
data class Title(
|
||||
@XmlValue(true) val value: String = "",
|
||||
)
|
||||
|
||||
@Serializable
|
||||
@XmlSerialName("Series", "", "")
|
||||
data class Series(@XmlValue(true) val value: String = "")
|
||||
data class Series(
|
||||
@XmlValue(true) val value: String = "",
|
||||
)
|
||||
|
||||
@Serializable
|
||||
@XmlSerialName("Number", "", "")
|
||||
data class Number(@XmlValue(true) val value: String = "")
|
||||
data class Number(
|
||||
@XmlValue(true) val value: String = "",
|
||||
)
|
||||
|
||||
@Serializable
|
||||
@XmlSerialName("Summary", "", "")
|
||||
data class Summary(@XmlValue(true) val value: String = "")
|
||||
data class Summary(
|
||||
@XmlValue(true) val value: String = "",
|
||||
)
|
||||
|
||||
@Serializable
|
||||
@XmlSerialName("Writer", "", "")
|
||||
data class Writer(@XmlValue(true) val value: String = "")
|
||||
data class Writer(
|
||||
@XmlValue(true) val value: String = "",
|
||||
)
|
||||
|
||||
@Serializable
|
||||
@XmlSerialName("Penciller", "", "")
|
||||
data class Penciller(@XmlValue(true) val value: String = "")
|
||||
data class Penciller(
|
||||
@XmlValue(true) val value: String = "",
|
||||
)
|
||||
|
||||
@Serializable
|
||||
@XmlSerialName("Inker", "", "")
|
||||
data class Inker(@XmlValue(true) val value: String = "")
|
||||
data class Inker(
|
||||
@XmlValue(true) val value: String = "",
|
||||
)
|
||||
|
||||
@Serializable
|
||||
@XmlSerialName("Colorist", "", "")
|
||||
data class Colorist(@XmlValue(true) val value: String = "")
|
||||
data class Colorist(
|
||||
@XmlValue(true) val value: String = "",
|
||||
)
|
||||
|
||||
@Serializable
|
||||
@XmlSerialName("Letterer", "", "")
|
||||
data class Letterer(@XmlValue(true) val value: String = "")
|
||||
data class Letterer(
|
||||
@XmlValue(true) val value: String = "",
|
||||
)
|
||||
|
||||
@Serializable
|
||||
@XmlSerialName("CoverArtist", "", "")
|
||||
data class CoverArtist(@XmlValue(true) val value: String = "")
|
||||
data class CoverArtist(
|
||||
@XmlValue(true) val value: String = "",
|
||||
)
|
||||
|
||||
@Serializable
|
||||
@XmlSerialName("Translator", "", "")
|
||||
data class Translator(@XmlValue(true) val value: String = "")
|
||||
data class Translator(
|
||||
@XmlValue(true) val value: String = "",
|
||||
)
|
||||
|
||||
@Serializable
|
||||
@XmlSerialName("Genre", "", "")
|
||||
data class Genre(@XmlValue(true) val value: String = "")
|
||||
data class Genre(
|
||||
@XmlValue(true) val value: String = "",
|
||||
)
|
||||
|
||||
@Serializable
|
||||
@XmlSerialName("Tags", "", "")
|
||||
data class Tags(@XmlValue(true) val value: String = "")
|
||||
data class Tags(
|
||||
@XmlValue(true) val value: String = "",
|
||||
)
|
||||
|
||||
@Serializable
|
||||
@XmlSerialName("Web", "", "")
|
||||
data class Web(@XmlValue(true) val value: String = "")
|
||||
data class Web(
|
||||
@XmlValue(true) val value: String = "",
|
||||
)
|
||||
|
||||
// The spec doesn't have a good field for this
|
||||
@Serializable
|
||||
@XmlSerialName("PublishingStatusTachiyomi", "http://www.w3.org/2001/XMLSchema", "ty")
|
||||
data class PublishingStatusTachiyomi(@XmlValue(true) val value: String = "")
|
||||
data class PublishingStatusTachiyomi(
|
||||
@XmlValue(true) val value: String = "",
|
||||
)
|
||||
|
||||
@Serializable
|
||||
@XmlSerialName("Categories", "http://www.w3.org/2001/XMLSchema", "ty")
|
||||
data class CategoriesTachiyomi(@XmlValue(true) val value: String = "")
|
||||
data class CategoriesTachiyomi(
|
||||
@XmlValue(true) val value: String = "",
|
||||
)
|
||||
}
|
||||
|
||||
enum class ComicInfoPublishingStatus(
|
||||
val comicInfoValue: String,
|
||||
val sMangaModelValue: Int
|
||||
val sMangaModelValue: Int,
|
||||
) {
|
||||
ONGOING("Ongoing", SManga.ONGOING),
|
||||
COMPLETED("Completed", SManga.COMPLETED),
|
||||
@@ -145,7 +177,7 @@ enum class ComicInfoPublishingStatus(
|
||||
PUBLISHING_FINISHED("Publishing finished", SManga.PUBLISHING_FINISHED),
|
||||
CANCELLED("Cancelled", SManga.CANCELLED),
|
||||
ON_HIATUS("On hiatus", SManga.ON_HIATUS),
|
||||
UNKNOWN("Unknown", SManga.UNKNOWN)
|
||||
UNKNOWN("Unknown", SManga.UNKNOWN),
|
||||
;
|
||||
|
||||
companion object {
|
||||
|
||||
@@ -9,5 +9,5 @@ class MangaDetails(
|
||||
val artist: String? = null,
|
||||
val description: String? = null,
|
||||
val genre: List<String>? = null,
|
||||
val status: Int? = null
|
||||
val status: Int? = null,
|
||||
)
|
||||
|
||||
@@ -4,15 +4,22 @@ package eu.kanade.tachiyomi.source.model
|
||||
// sealed class Filter<T>(val name: String, var state: T) {
|
||||
open class Filter<T>(val name: String, var state: T) {
|
||||
open class Header(name: String) : Filter<Any>(name, 0)
|
||||
|
||||
open class Separator(name: String = "") : Filter<Any>(name, 0)
|
||||
|
||||
abstract class Select<V>(name: String, val values: Array<V>, state: Int = 0) : Filter<Int>(name, state) {
|
||||
val displayValues get() = values.map { it.toString() }
|
||||
}
|
||||
|
||||
abstract class Text(name: String, state: String = "") : Filter<String>(name, state)
|
||||
|
||||
abstract class CheckBox(name: String, state: Boolean = false) : Filter<Boolean>(name, state)
|
||||
|
||||
abstract class TriState(name: String, state: Int = STATE_IGNORE) : Filter<Int>(name, state) {
|
||||
fun isIgnored() = state == STATE_IGNORE
|
||||
|
||||
fun isIncluded() = state == STATE_INCLUDE
|
||||
|
||||
fun isExcluded() = state == STATE_EXCLUDE
|
||||
|
||||
companion object {
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
package eu.kanade.tachiyomi.source.model
|
||||
|
||||
data class FilterList(val list: List<Filter<*>>) : List<Filter<*>> by list {
|
||||
|
||||
constructor(vararg fs: Filter<*>) : this(if (fs.isNotEmpty()) fs.asList() else emptyList())
|
||||
}
|
||||
|
||||
@@ -9,14 +9,19 @@ open class Page(
|
||||
val index: Int,
|
||||
val url: String = "",
|
||||
var imageUrl: String? = null,
|
||||
@Transient var uri: Uri? = null // Deprecated but can't be deleted due to extensions
|
||||
// Deprecated but can't be deleted due to extensions
|
||||
@Transient var uri: Uri? = null,
|
||||
) : ProgressListener {
|
||||
|
||||
private val _progress = MutableStateFlow(0)
|
||||
val progress = _progress.asStateFlow()
|
||||
|
||||
override fun update(bytesRead: Long, contentLength: Long, done: Boolean) {
|
||||
_progress.value = if (contentLength > 0) {
|
||||
override fun update(
|
||||
bytesRead: Long,
|
||||
contentLength: Long,
|
||||
done: Boolean,
|
||||
) {
|
||||
_progress.value =
|
||||
if (contentLength > 0) {
|
||||
(100 * bytesRead / contentLength).toInt()
|
||||
} else {
|
||||
-1
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
@file:Suppress("ktlint:standard:property-naming")
|
||||
|
||||
package eu.kanade.tachiyomi.source.model
|
||||
|
||||
import java.io.Serializable
|
||||
|
||||
interface SChapter : Serializable {
|
||||
|
||||
var url: String
|
||||
|
||||
var name: String
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
@file:Suppress("ktlint:standard:property-naming")
|
||||
|
||||
package eu.kanade.tachiyomi.source.model
|
||||
|
||||
class SChapterImpl : SChapter {
|
||||
|
||||
override lateinit var url: String
|
||||
|
||||
override lateinit var name: String
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
@file:Suppress("ktlint:standard:property-naming")
|
||||
|
||||
package eu.kanade.tachiyomi.source.model
|
||||
|
||||
import java.io.Serializable
|
||||
|
||||
interface SManga : Serializable {
|
||||
|
||||
var url: String
|
||||
|
||||
var title: String
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
@file:Suppress("ktlint:standard:property-naming")
|
||||
|
||||
package eu.kanade.tachiyomi.source.model
|
||||
|
||||
class SMangaImpl : SManga {
|
||||
|
||||
override lateinit var url: String
|
||||
|
||||
override lateinit var title: String
|
||||
|
||||
@@ -2,5 +2,5 @@ package eu.kanade.tachiyomi.source.model
|
||||
|
||||
enum class UpdateStrategy {
|
||||
ALWAYS_UPDATE,
|
||||
ONLY_FETCH_ONCE
|
||||
ONLY_FETCH_ONCE,
|
||||
}
|
||||
|
||||
@@ -19,7 +19,6 @@ import okhttp3.Response
|
||||
import rx.Observable
|
||||
import suwayomi.tachidesk.manga.impl.util.lang.awaitSingle
|
||||
import uy.kohesive.injekt.injectLazy
|
||||
// import uy.kohesive.injekt.injectLazy
|
||||
import java.net.URI
|
||||
import java.net.URISyntaxException
|
||||
import java.security.MessageDigest
|
||||
@@ -28,7 +27,6 @@ import java.security.MessageDigest
|
||||
* A simple implementation for sources from a website.
|
||||
*/
|
||||
abstract class HttpSource : CatalogueSource {
|
||||
|
||||
/**
|
||||
* Network service.
|
||||
*/
|
||||
@@ -91,7 +89,11 @@ abstract class HttpSource : CatalogueSource {
|
||||
* @param versionId [Int] the version ID of the source
|
||||
* @return a unique ID for the source
|
||||
*/
|
||||
protected fun generateId(name: String, lang: String, versionId: Int): Long {
|
||||
protected fun generateId(
|
||||
name: String,
|
||||
lang: String,
|
||||
versionId: Int,
|
||||
): Long {
|
||||
val key = "${name.lowercase()}/$lang/$versionId"
|
||||
val bytes = MessageDigest.getInstance("MD5").digest(key.toByteArray())
|
||||
return (0..7).map { bytes[it].toLong() and 0xff shl 8 * (7 - it) }.reduce(Long::or) and Long.MAX_VALUE
|
||||
@@ -100,7 +102,8 @@ abstract class HttpSource : CatalogueSource {
|
||||
/**
|
||||
* Headers builder for requests. Implementations can override this method for custom headers.
|
||||
*/
|
||||
protected open fun headersBuilder() = Headers.Builder().apply {
|
||||
protected open fun headersBuilder() =
|
||||
Headers.Builder().apply {
|
||||
add("User-Agent", DEFAULT_USER_AGENT)
|
||||
}
|
||||
|
||||
@@ -147,7 +150,11 @@ abstract class HttpSource : CatalogueSource {
|
||||
* @param filters the list of filters to apply.
|
||||
*/
|
||||
@Deprecated("Use the non-RxJava API instead", replaceWith = ReplaceWith("getSearchManga"))
|
||||
override fun fetchSearchManga(page: Int, query: String, filters: FilterList): Observable<MangasPage> {
|
||||
override fun fetchSearchManga(
|
||||
page: Int,
|
||||
query: String,
|
||||
filters: FilterList,
|
||||
): Observable<MangasPage> {
|
||||
return client.newCall(searchMangaRequest(page, query, filters))
|
||||
.asObservableSuccess()
|
||||
.map { response ->
|
||||
@@ -162,7 +169,11 @@ abstract class HttpSource : CatalogueSource {
|
||||
* @param query the search query.
|
||||
* @param filters the list of filters to apply.
|
||||
*/
|
||||
protected abstract fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request
|
||||
protected abstract fun searchMangaRequest(
|
||||
page: Int,
|
||||
query: String,
|
||||
filters: FilterList,
|
||||
): Request
|
||||
|
||||
/**
|
||||
* Parses the response from the site and returns a [MangasPage] object.
|
||||
@@ -450,7 +461,10 @@ abstract class HttpSource : CatalogueSource {
|
||||
* @param chapter the chapter to be added.
|
||||
* @param manga the manga of the chapter.
|
||||
*/
|
||||
open fun prepareNewChapter(chapter: SChapter, manga: SManga) {}
|
||||
open fun prepareNewChapter(
|
||||
chapter: SChapter,
|
||||
manga: SManga,
|
||||
) {}
|
||||
|
||||
/**
|
||||
* Returns the list of filters for the source.
|
||||
|
||||
@@ -13,7 +13,6 @@ import org.jsoup.nodes.Element
|
||||
* A simple implementation for sources from a website using Jsoup, an HTML parser.
|
||||
*/
|
||||
abstract class ParsedHttpSource : HttpSource() {
|
||||
|
||||
/**
|
||||
* Parses the response from the site and returns a [MangasPage] object.
|
||||
*
|
||||
@@ -22,11 +21,13 @@ abstract class ParsedHttpSource : HttpSource() {
|
||||
override fun popularMangaParse(response: Response): MangasPage {
|
||||
val document = response.asJsoup()
|
||||
|
||||
val mangas = document.select(popularMangaSelector()).map { element ->
|
||||
val mangas =
|
||||
document.select(popularMangaSelector()).map { element ->
|
||||
popularMangaFromElement(element)
|
||||
}
|
||||
|
||||
val hasNextPage = popularMangaNextPageSelector()?.let { selector ->
|
||||
val hasNextPage =
|
||||
popularMangaNextPageSelector()?.let { selector ->
|
||||
document.select(selector).first()
|
||||
} != null
|
||||
|
||||
@@ -60,11 +61,13 @@ abstract class ParsedHttpSource : HttpSource() {
|
||||
override fun searchMangaParse(response: Response): MangasPage {
|
||||
val document = response.asJsoup()
|
||||
|
||||
val mangas = document.select(searchMangaSelector()).map { element ->
|
||||
val mangas =
|
||||
document.select(searchMangaSelector()).map { element ->
|
||||
searchMangaFromElement(element)
|
||||
}
|
||||
|
||||
val hasNextPage = searchMangaNextPageSelector()?.let { selector ->
|
||||
val hasNextPage =
|
||||
searchMangaNextPageSelector()?.let { selector ->
|
||||
document.select(selector).first()
|
||||
} != null
|
||||
|
||||
@@ -98,11 +101,13 @@ abstract class ParsedHttpSource : HttpSource() {
|
||||
override fun latestUpdatesParse(response: Response): MangasPage {
|
||||
val document = response.asJsoup()
|
||||
|
||||
val mangas = document.select(latestUpdatesSelector()).map { element ->
|
||||
val mangas =
|
||||
document.select(latestUpdatesSelector()).map { element ->
|
||||
latestUpdatesFromElement(element)
|
||||
}
|
||||
|
||||
val hasNextPage = latestUpdatesNextPageSelector()?.let { selector ->
|
||||
val hasNextPage =
|
||||
latestUpdatesNextPageSelector()?.let { selector ->
|
||||
document.select(selector).first()
|
||||
} != null
|
||||
|
||||
|
||||
@@ -10,7 +10,6 @@ import eu.kanade.tachiyomi.source.model.SManga
|
||||
*/
|
||||
@Suppress("unused")
|
||||
interface ResolvableSource : Source {
|
||||
|
||||
/**
|
||||
* Whether this source may potentially handle the given URI.
|
||||
*
|
||||
|
||||
@@ -5,11 +5,17 @@ import org.jsoup.Jsoup
|
||||
import org.jsoup.nodes.Document
|
||||
import org.jsoup.nodes.Element
|
||||
|
||||
fun Element.selectText(css: String, defaultValue: String? = null): String? {
|
||||
fun Element.selectText(
|
||||
css: String,
|
||||
defaultValue: String? = null,
|
||||
): String? {
|
||||
return select(css).first()?.text() ?: defaultValue
|
||||
}
|
||||
|
||||
fun Element.selectInt(css: String, defaultValue: Int = 0): Int {
|
||||
fun Element.selectInt(
|
||||
css: String,
|
||||
defaultValue: Int = 0,
|
||||
): Int {
|
||||
return select(css).first()?.text()?.toInt() ?: defaultValue
|
||||
}
|
||||
|
||||
|
||||
@@ -4,7 +4,6 @@ package eu.kanade.tachiyomi.util.chapter
|
||||
* -R> = regex conversion.
|
||||
*/
|
||||
object ChapterRecognition {
|
||||
|
||||
private const val NUMBER_PATTERN = """([0-9]+)(\.[0-9]+)?(\.?[a-z]+)?"""
|
||||
|
||||
/**
|
||||
@@ -30,7 +29,11 @@ object ChapterRecognition {
|
||||
*/
|
||||
private val unwantedWhiteSpace = Regex("""\s(?=extra|special|omake)""")
|
||||
|
||||
fun parseChapterNumber(mangaTitle: String, chapterName: String, chapterNumber: Double? = null): Double {
|
||||
fun parseChapterNumber(
|
||||
mangaTitle: String,
|
||||
chapterName: String,
|
||||
chapterNumber: Double? = null,
|
||||
): Double {
|
||||
// If chapter number is known return.
|
||||
if (chapterNumber != null && (chapterNumber == -2.0 || chapterNumber > -1.0)) {
|
||||
return chapterNumber
|
||||
@@ -81,7 +84,10 @@ object ChapterRecognition {
|
||||
* @param alpha alpha value of regex
|
||||
* @return decimal/alpha float value
|
||||
*/
|
||||
private fun checkForDecimal(decimal: String?, alpha: String?): Double {
|
||||
private fun checkForDecimal(
|
||||
decimal: String?,
|
||||
alpha: String?,
|
||||
): Double {
|
||||
if (!decimal.isNullOrEmpty()) {
|
||||
return decimal.toDouble()
|
||||
}
|
||||
|
||||
@@ -3,10 +3,10 @@ package eu.kanade.tachiyomi.util.lang
|
||||
import java.security.MessageDigest
|
||||
|
||||
object Hash {
|
||||
|
||||
private val chars = charArrayOf(
|
||||
private val chars =
|
||||
charArrayOf(
|
||||
'0', '1', '2', '3', '4', '5', '6', '7', '8', '9',
|
||||
'a', 'b', 'c', 'd', 'e', 'f'
|
||||
'a', 'b', 'c', 'd', 'e', 'f',
|
||||
)
|
||||
|
||||
private val MD5 get() = MessageDigest.getInstance("MD5")
|
||||
|
||||
@@ -7,7 +7,10 @@ import kotlin.math.floor
|
||||
* Replaces the given string to have at most [count] characters using [replacement] at its end.
|
||||
* If [replacement] is longer than [count] an exception will be thrown when `length > count`.
|
||||
*/
|
||||
fun String.chop(count: Int, replacement: String = "…"): String {
|
||||
fun String.chop(
|
||||
count: Int,
|
||||
replacement: String = "…",
|
||||
): String {
|
||||
return if (length > count) {
|
||||
take(count - replacement.length) + replacement
|
||||
} else {
|
||||
@@ -19,7 +22,10 @@ fun String.chop(count: Int, replacement: String = "…"): String {
|
||||
* Replaces the given string to have at most [count] characters using [replacement] near the center.
|
||||
* If [replacement] is longer than [count] an exception will be thrown when `length > count`.
|
||||
*/
|
||||
fun String.truncateCenter(count: Int, replacement: String = "..."): String {
|
||||
fun String.truncateCenter(
|
||||
count: Int,
|
||||
replacement: String = "...",
|
||||
): String {
|
||||
if (length <= count) {
|
||||
return this
|
||||
}
|
||||
|
||||
@@ -12,7 +12,6 @@ import java.io.InputStream
|
||||
* Wrapper over ZipFile to load files in epub format.
|
||||
*/
|
||||
class EpubFile(file: File) : Closeable {
|
||||
|
||||
/**
|
||||
* Zip file of this epub.
|
||||
*/
|
||||
@@ -81,7 +80,8 @@ class EpubFile(file: File) : Closeable {
|
||||
* Returns all the pages from the epub.
|
||||
*/
|
||||
fun getPagesFromDocument(document: Document): List<String> {
|
||||
val pages = document.select("manifest > item")
|
||||
val pages =
|
||||
document.select("manifest > item")
|
||||
.filter { node -> "application/xhtml+xml" == node.attr("media-type") }
|
||||
.associateBy { it.attr("id") }
|
||||
|
||||
@@ -92,7 +92,10 @@ class EpubFile(file: File) : Closeable {
|
||||
/**
|
||||
* Returns all the images contained in every page from the epub.
|
||||
*/
|
||||
private fun getImagesFromPages(pages: List<String>, packageHref: String): List<String> {
|
||||
private fun getImagesFromPages(
|
||||
pages: List<String>,
|
||||
packageHref: String,
|
||||
): List<String> {
|
||||
val result = mutableListOf<String>()
|
||||
val basePath = getParentDirectory(packageHref)
|
||||
pages.forEach { page ->
|
||||
@@ -128,7 +131,10 @@ class EpubFile(file: File) : Closeable {
|
||||
/**
|
||||
* Resolves a zip path from base and relative components and a path separator.
|
||||
*/
|
||||
private fun resolveZipPath(basePath: String, relativePath: String): String {
|
||||
private fun resolveZipPath(
|
||||
basePath: String,
|
||||
relativePath: String,
|
||||
): String {
|
||||
if (relativePath.startsWith(pathSeparator)) {
|
||||
// Path is absolute, so return as-is.
|
||||
return relativePath
|
||||
|
||||
@@ -15,7 +15,8 @@ import suwayomi.tachidesk.server.util.withOperation
|
||||
|
||||
object GlobalMetaController {
|
||||
/** used to modify a category's meta parameters */
|
||||
val getMeta = handler(
|
||||
val getMeta =
|
||||
handler(
|
||||
documentWith = {
|
||||
withOperation {
|
||||
summary("Server level meta mapping")
|
||||
@@ -28,11 +29,12 @@ object GlobalMetaController {
|
||||
},
|
||||
withResults = {
|
||||
httpCode(HttpCode.OK)
|
||||
}
|
||||
},
|
||||
)
|
||||
|
||||
/** used to modify global meta parameters */
|
||||
val modifyMeta = handler(
|
||||
val modifyMeta =
|
||||
handler(
|
||||
formParam<String>("key"),
|
||||
formParam<String>("value"),
|
||||
documentWith = {
|
||||
@@ -48,6 +50,6 @@ object GlobalMetaController {
|
||||
withResults = {
|
||||
httpCode(HttpCode.OK)
|
||||
httpCode(HttpCode.NOT_FOUND)
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
@@ -19,7 +19,8 @@ import suwayomi.tachidesk.server.util.withOperation
|
||||
/** Settings Page/Screen */
|
||||
object SettingsController {
|
||||
/** returns some static info about the current app build */
|
||||
val about = handler(
|
||||
val about =
|
||||
handler(
|
||||
documentWith = {
|
||||
withOperation {
|
||||
summary("About Tachidesk")
|
||||
@@ -31,11 +32,12 @@ object SettingsController {
|
||||
},
|
||||
withResults = {
|
||||
json<AboutDataClass>(HttpCode.OK)
|
||||
}
|
||||
},
|
||||
)
|
||||
|
||||
/** check for app updates */
|
||||
val checkUpdate = handler(
|
||||
val checkUpdate =
|
||||
handler(
|
||||
documentWith = {
|
||||
withOperation {
|
||||
summary("Tachidesk update check")
|
||||
@@ -44,11 +46,11 @@ object SettingsController {
|
||||
},
|
||||
behaviorOf = { ctx ->
|
||||
ctx.future(
|
||||
future { AppUpdate.checkUpdate() }
|
||||
future { AppUpdate.checkUpdate() },
|
||||
)
|
||||
},
|
||||
withResults = {
|
||||
json<Array<UpdateDataClass>>(HttpCode.OK)
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
@@ -7,7 +7,7 @@ package suwayomi.tachidesk.global.impl
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
|
||||
|
||||
import suwayomi.tachidesk.server.BuildConfig
|
||||
import suwayomi.tachidesk.server.generated.BuildConfig
|
||||
|
||||
data class AboutDataClass(
|
||||
val name: String,
|
||||
@@ -16,7 +16,7 @@ data class AboutDataClass(
|
||||
val buildType: String,
|
||||
val buildTime: Long,
|
||||
val github: String,
|
||||
val discord: String
|
||||
val discord: String,
|
||||
)
|
||||
|
||||
object About {
|
||||
@@ -28,7 +28,7 @@ object About {
|
||||
BuildConfig.BUILD_TYPE,
|
||||
BuildConfig.BUILD_TIME,
|
||||
BuildConfig.GITHUB,
|
||||
BuildConfig.DISCORD
|
||||
BuildConfig.DISCORD,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -19,7 +19,7 @@ data class UpdateDataClass(
|
||||
/** [channel] mirrors [suwayomi.tachidesk.server.BuildConfig.BUILD_TYPE] */
|
||||
val channel: String,
|
||||
val tag: String,
|
||||
val url: String
|
||||
val url: String,
|
||||
)
|
||||
|
||||
object AppUpdate {
|
||||
@@ -30,29 +30,31 @@ object AppUpdate {
|
||||
private val network: NetworkHelper by injectLazy()
|
||||
|
||||
suspend fun checkUpdate(): List<UpdateDataClass> {
|
||||
val stableJson = json.parseToJsonElement(
|
||||
val stableJson =
|
||||
json.parseToJsonElement(
|
||||
network.client.newCall(
|
||||
GET(LATEST_STABLE_CHANNEL_URL)
|
||||
).await().body.string()
|
||||
GET(LATEST_STABLE_CHANNEL_URL),
|
||||
).await().body.string(),
|
||||
).jsonObject
|
||||
|
||||
val previewJson = json.parseToJsonElement(
|
||||
val previewJson =
|
||||
json.parseToJsonElement(
|
||||
network.client.newCall(
|
||||
GET(LATEST_PREVIEW_CHANNEL_URL)
|
||||
).await().body.string()
|
||||
GET(LATEST_PREVIEW_CHANNEL_URL),
|
||||
).await().body.string(),
|
||||
).jsonObject
|
||||
|
||||
return listOf(
|
||||
UpdateDataClass(
|
||||
"Stable",
|
||||
stableJson["tag_name"]!!.jsonPrimitive.content,
|
||||
stableJson["html_url"]!!.jsonPrimitive.content
|
||||
stableJson["html_url"]!!.jsonPrimitive.content,
|
||||
),
|
||||
UpdateDataClass(
|
||||
"Preview",
|
||||
previewJson["tag_name"]!!.jsonPrimitive.content,
|
||||
previewJson["html_url"]!!.jsonPrimitive.content
|
||||
)
|
||||
previewJson["html_url"]!!.jsonPrimitive.content,
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,9 +15,13 @@ import suwayomi.tachidesk.global.model.table.GlobalMetaTable
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
|
||||
|
||||
object GlobalMeta {
|
||||
fun modifyMeta(key: String, value: String) {
|
||||
fun modifyMeta(
|
||||
key: String,
|
||||
value: String,
|
||||
) {
|
||||
transaction {
|
||||
val meta =
|
||||
transaction {
|
||||
val meta = transaction {
|
||||
GlobalMetaTable.select { GlobalMetaTable.key eq key }
|
||||
}.firstOrNull()
|
||||
|
||||
|
||||
@@ -23,7 +23,7 @@ object GraphQLController {
|
||||
ctx.future(
|
||||
future {
|
||||
server.execute(ctx)
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -23,11 +23,14 @@ import suwayomi.tachidesk.server.JavalinSetup.future
|
||||
|
||||
class CategoryDataLoader : KotlinDataLoader<Int, CategoryType> {
|
||||
override val dataLoaderName = "CategoryDataLoader"
|
||||
override fun getDataLoader(): DataLoader<Int, CategoryType> = DataLoaderFactory.newDataLoader { ids ->
|
||||
|
||||
override fun getDataLoader(): DataLoader<Int, CategoryType> =
|
||||
DataLoaderFactory.newDataLoader { ids ->
|
||||
future {
|
||||
transaction {
|
||||
addLogger(Slf4jSqlDebugLogger)
|
||||
val categories = CategoryTable.select { CategoryTable.id inList ids }
|
||||
val categories =
|
||||
CategoryTable.select { CategoryTable.id inList ids }
|
||||
.map { CategoryType(it) }
|
||||
.associateBy { it.id }
|
||||
ids.map { categories[it] }
|
||||
@@ -38,7 +41,9 @@ class CategoryDataLoader : KotlinDataLoader<Int, CategoryType> {
|
||||
|
||||
class CategoryForIdsDataLoader : KotlinDataLoader<List<Int>, CategoryNodeList> {
|
||||
override val dataLoaderName = "CategoryForIdsDataLoader"
|
||||
override fun getDataLoader(): DataLoader<List<Int>, CategoryNodeList> = DataLoaderFactory.newDataLoader { categoryIds ->
|
||||
|
||||
override fun getDataLoader(): DataLoader<List<Int>, CategoryNodeList> =
|
||||
DataLoaderFactory.newDataLoader { categoryIds ->
|
||||
future {
|
||||
transaction {
|
||||
addLogger(Slf4jSqlDebugLogger)
|
||||
@@ -54,11 +59,14 @@ class CategoryForIdsDataLoader : KotlinDataLoader<List<Int>, CategoryNodeList> {
|
||||
|
||||
class CategoriesForMangaDataLoader : KotlinDataLoader<Int, CategoryNodeList> {
|
||||
override val dataLoaderName = "CategoriesForMangaDataLoader"
|
||||
override fun getDataLoader(): DataLoader<Int, CategoryNodeList> = DataLoaderFactory.newDataLoader<Int, CategoryNodeList> { ids ->
|
||||
|
||||
override fun getDataLoader(): DataLoader<Int, CategoryNodeList> =
|
||||
DataLoaderFactory.newDataLoader<Int, CategoryNodeList> { ids ->
|
||||
future {
|
||||
transaction {
|
||||
addLogger(Slf4jSqlDebugLogger)
|
||||
val itemsByRef = CategoryMangaTable.innerJoin(CategoryTable)
|
||||
val itemsByRef =
|
||||
CategoryMangaTable.innerJoin(CategoryTable)
|
||||
.select { CategoryMangaTable.manga inList ids }
|
||||
.map { Pair(it[CategoryMangaTable.manga].value, CategoryType(it)) }
|
||||
.groupBy { it.first }
|
||||
|
||||
@@ -25,11 +25,14 @@ import suwayomi.tachidesk.server.JavalinSetup.future
|
||||
|
||||
class ChapterDataLoader : KotlinDataLoader<Int, ChapterType?> {
|
||||
override val dataLoaderName = "ChapterDataLoader"
|
||||
override fun getDataLoader(): DataLoader<Int, ChapterType?> = DataLoaderFactory.newDataLoader<Int, ChapterType> { ids ->
|
||||
|
||||
override fun getDataLoader(): DataLoader<Int, ChapterType?> =
|
||||
DataLoaderFactory.newDataLoader<Int, ChapterType> { ids ->
|
||||
future {
|
||||
transaction {
|
||||
addLogger(Slf4jSqlDebugLogger)
|
||||
val chapters = ChapterTable.select { ChapterTable.id inList ids }
|
||||
val chapters =
|
||||
ChapterTable.select { ChapterTable.id inList ids }
|
||||
.map { ChapterType(it) }
|
||||
.associateBy { it.id }
|
||||
ids.map { chapters[it] }
|
||||
@@ -40,11 +43,14 @@ class ChapterDataLoader : KotlinDataLoader<Int, ChapterType?> {
|
||||
|
||||
class ChaptersForMangaDataLoader : KotlinDataLoader<Int, ChapterNodeList> {
|
||||
override val dataLoaderName = "ChaptersForMangaDataLoader"
|
||||
override fun getDataLoader(): DataLoader<Int, ChapterNodeList> = DataLoaderFactory.newDataLoader<Int, ChapterNodeList> { ids ->
|
||||
|
||||
override fun getDataLoader(): DataLoader<Int, ChapterNodeList> =
|
||||
DataLoaderFactory.newDataLoader<Int, ChapterNodeList> { ids ->
|
||||
future {
|
||||
transaction {
|
||||
addLogger(Slf4jSqlDebugLogger)
|
||||
val chaptersByMangaId = ChapterTable.select { ChapterTable.manga inList ids }
|
||||
val chaptersByMangaId =
|
||||
ChapterTable.select { ChapterTable.manga inList ids }
|
||||
.map { ChapterType(it) }
|
||||
.groupBy { it.mangaId }
|
||||
ids.map { (chaptersByMangaId[it] ?: emptyList()).toNodeList() }
|
||||
@@ -55,7 +61,9 @@ class ChaptersForMangaDataLoader : KotlinDataLoader<Int, ChapterNodeList> {
|
||||
|
||||
class DownloadedChapterCountForMangaDataLoader : KotlinDataLoader<Int, Int> {
|
||||
override val dataLoaderName = "DownloadedChapterCountForMangaDataLoader"
|
||||
override fun getDataLoader(): DataLoader<Int, Int> = DataLoaderFactory.newDataLoader<Int, Int> { ids ->
|
||||
|
||||
override fun getDataLoader(): DataLoader<Int, Int> =
|
||||
DataLoaderFactory.newDataLoader<Int, Int> { ids ->
|
||||
future {
|
||||
transaction {
|
||||
addLogger(Slf4jSqlDebugLogger)
|
||||
@@ -73,7 +81,9 @@ class DownloadedChapterCountForMangaDataLoader : KotlinDataLoader<Int, Int> {
|
||||
|
||||
class UnreadChapterCountForMangaDataLoader : KotlinDataLoader<Int, Int> {
|
||||
override val dataLoaderName = "UnreadChapterCountForMangaDataLoader"
|
||||
override fun getDataLoader(): DataLoader<Int, Int> = DataLoaderFactory.newDataLoader<Int, Int> { ids ->
|
||||
|
||||
override fun getDataLoader(): DataLoader<Int, Int> =
|
||||
DataLoaderFactory.newDataLoader<Int, Int> { ids ->
|
||||
future {
|
||||
transaction {
|
||||
addLogger(Slf4jSqlDebugLogger)
|
||||
@@ -91,11 +101,14 @@ class UnreadChapterCountForMangaDataLoader : KotlinDataLoader<Int, Int> {
|
||||
|
||||
class LastReadChapterForMangaDataLoader : KotlinDataLoader<Int, ChapterType?> {
|
||||
override val dataLoaderName = "LastReadChapterForMangaDataLoader"
|
||||
override fun getDataLoader(): DataLoader<Int, ChapterType?> = DataLoaderFactory.newDataLoader<Int, ChapterType?> { ids ->
|
||||
|
||||
override fun getDataLoader(): DataLoader<Int, ChapterType?> =
|
||||
DataLoaderFactory.newDataLoader<Int, ChapterType?> { ids ->
|
||||
future {
|
||||
transaction {
|
||||
addLogger(Slf4jSqlDebugLogger)
|
||||
val lastReadChaptersByMangaId = ChapterTable
|
||||
val lastReadChaptersByMangaId =
|
||||
ChapterTable
|
||||
.select { (ChapterTable.manga inList ids) and (ChapterTable.isRead eq true) }
|
||||
.orderBy(ChapterTable.sourceOrder to SortOrder.DESC)
|
||||
.groupBy { it[ChapterTable.manga].value }
|
||||
|
||||
@@ -21,11 +21,14 @@ import suwayomi.tachidesk.server.JavalinSetup.future
|
||||
|
||||
class ExtensionDataLoader : KotlinDataLoader<String, ExtensionType?> {
|
||||
override val dataLoaderName = "ExtensionDataLoader"
|
||||
override fun getDataLoader(): DataLoader<String, ExtensionType?> = DataLoaderFactory.newDataLoader { ids ->
|
||||
|
||||
override fun getDataLoader(): DataLoader<String, ExtensionType?> =
|
||||
DataLoaderFactory.newDataLoader { ids ->
|
||||
future {
|
||||
transaction {
|
||||
addLogger(Slf4jSqlDebugLogger)
|
||||
val extensions = ExtensionTable.select { ExtensionTable.pkgName inList ids }
|
||||
val extensions =
|
||||
ExtensionTable.select { ExtensionTable.pkgName inList ids }
|
||||
.map { ExtensionType(it) }
|
||||
.associateBy { it.pkgName }
|
||||
ids.map { extensions[it] }
|
||||
@@ -36,16 +39,20 @@ class ExtensionDataLoader : KotlinDataLoader<String, ExtensionType?> {
|
||||
|
||||
class ExtensionForSourceDataLoader : KotlinDataLoader<Long, ExtensionType?> {
|
||||
override val dataLoaderName = "ExtensionForSourceDataLoader"
|
||||
override fun getDataLoader(): DataLoader<Long, ExtensionType?> = DataLoaderFactory.newDataLoader { ids ->
|
||||
|
||||
override fun getDataLoader(): DataLoader<Long, ExtensionType?> =
|
||||
DataLoaderFactory.newDataLoader { ids ->
|
||||
future {
|
||||
transaction {
|
||||
addLogger(Slf4jSqlDebugLogger)
|
||||
val extensions = ExtensionTable.innerJoin(SourceTable)
|
||||
val extensions =
|
||||
ExtensionTable.innerJoin(SourceTable)
|
||||
.select { SourceTable.id inList ids }
|
||||
.toList()
|
||||
.map { Triple(it[SourceTable.id].value, it[ExtensionTable.pkgName], it) }
|
||||
.let { triples ->
|
||||
val sources = buildMap {
|
||||
val sources =
|
||||
buildMap {
|
||||
triples.forEach {
|
||||
if (!containsKey(it.second)) {
|
||||
put(it.second, ExtensionType(it.third))
|
||||
|
||||
@@ -24,11 +24,14 @@ import suwayomi.tachidesk.server.JavalinSetup.future
|
||||
|
||||
class MangaDataLoader : KotlinDataLoader<Int, MangaType?> {
|
||||
override val dataLoaderName = "MangaDataLoader"
|
||||
override fun getDataLoader(): DataLoader<Int, MangaType?> = DataLoaderFactory.newDataLoader { ids ->
|
||||
|
||||
override fun getDataLoader(): DataLoader<Int, MangaType?> =
|
||||
DataLoaderFactory.newDataLoader { ids ->
|
||||
future {
|
||||
transaction {
|
||||
addLogger(Slf4jSqlDebugLogger)
|
||||
val manga = MangaTable.select { MangaTable.id inList ids }
|
||||
val manga =
|
||||
MangaTable.select { MangaTable.id inList ids }
|
||||
.map { MangaType(it) }
|
||||
.associateBy { it.id }
|
||||
ids.map { manga[it] }
|
||||
@@ -39,11 +42,14 @@ class MangaDataLoader : KotlinDataLoader<Int, MangaType?> {
|
||||
|
||||
class MangaForCategoryDataLoader : KotlinDataLoader<Int, MangaNodeList> {
|
||||
override val dataLoaderName = "MangaForCategoryDataLoader"
|
||||
override fun getDataLoader(): DataLoader<Int, MangaNodeList> = DataLoaderFactory.newDataLoader<Int, MangaNodeList> { ids ->
|
||||
|
||||
override fun getDataLoader(): DataLoader<Int, MangaNodeList> =
|
||||
DataLoaderFactory.newDataLoader<Int, MangaNodeList> { ids ->
|
||||
future {
|
||||
transaction {
|
||||
addLogger(Slf4jSqlDebugLogger)
|
||||
val itemsByRef = if (ids.contains(0)) {
|
||||
val itemsByRef =
|
||||
if (ids.contains(0)) {
|
||||
MangaTable
|
||||
.leftJoin(CategoryMangaTable)
|
||||
.select { MangaTable.inLibrary eq true }
|
||||
@@ -54,7 +60,8 @@ class MangaForCategoryDataLoader : KotlinDataLoader<Int, MangaNodeList> {
|
||||
}
|
||||
} else {
|
||||
emptyMap()
|
||||
} + CategoryMangaTable.innerJoin(MangaTable)
|
||||
} +
|
||||
CategoryMangaTable.innerJoin(MangaTable)
|
||||
.select { CategoryMangaTable.category inList ids }
|
||||
.map { Pair(it[CategoryMangaTable.category].value, MangaType(it)) }
|
||||
.groupBy { it.first }
|
||||
@@ -68,11 +75,14 @@ class MangaForCategoryDataLoader : KotlinDataLoader<Int, MangaNodeList> {
|
||||
|
||||
class MangaForSourceDataLoader : KotlinDataLoader<Long, MangaNodeList> {
|
||||
override val dataLoaderName = "MangaForSourceDataLoader"
|
||||
override fun getDataLoader(): DataLoader<Long, MangaNodeList> = DataLoaderFactory.newDataLoader<Long, MangaNodeList> { ids ->
|
||||
|
||||
override fun getDataLoader(): DataLoader<Long, MangaNodeList> =
|
||||
DataLoaderFactory.newDataLoader<Long, MangaNodeList> { ids ->
|
||||
future {
|
||||
transaction {
|
||||
addLogger(Slf4jSqlDebugLogger)
|
||||
val mangaBySourceId = MangaTable.select { MangaTable.sourceReference inList ids }
|
||||
val mangaBySourceId =
|
||||
MangaTable.select { MangaTable.sourceReference inList ids }
|
||||
.map { MangaType(it) }
|
||||
.groupBy { it.sourceId }
|
||||
ids.map { (mangaBySourceId[it] ?: emptyList()).toNodeList() }
|
||||
@@ -83,12 +93,15 @@ class MangaForSourceDataLoader : KotlinDataLoader<Long, MangaNodeList> {
|
||||
|
||||
class MangaForIdsDataLoader : KotlinDataLoader<List<Int>, MangaNodeList> {
|
||||
override val dataLoaderName = "MangaForIdsDataLoader"
|
||||
override fun getDataLoader(): DataLoader<List<Int>, MangaNodeList> = DataLoaderFactory.newDataLoader { mangaIds ->
|
||||
|
||||
override fun getDataLoader(): DataLoader<List<Int>, MangaNodeList> =
|
||||
DataLoaderFactory.newDataLoader { mangaIds ->
|
||||
future {
|
||||
transaction {
|
||||
addLogger(Slf4jSqlDebugLogger)
|
||||
val ids = mangaIds.flatten().distinct()
|
||||
val manga = MangaTable.select { MangaTable.id inList ids }
|
||||
val manga =
|
||||
MangaTable.select { MangaTable.id inList ids }
|
||||
.map { MangaType(it) }
|
||||
mangaIds.map { mangaIds ->
|
||||
manga.filter { it.id in mangaIds }.toNodeList()
|
||||
|
||||
@@ -19,11 +19,14 @@ import suwayomi.tachidesk.server.JavalinSetup.future
|
||||
|
||||
class GlobalMetaDataLoader : KotlinDataLoader<String, GlobalMetaType?> {
|
||||
override val dataLoaderName = "GlobalMetaDataLoader"
|
||||
override fun getDataLoader(): DataLoader<String, GlobalMetaType?> = DataLoaderFactory.newDataLoader<String, GlobalMetaType?> { ids ->
|
||||
|
||||
override fun getDataLoader(): DataLoader<String, GlobalMetaType?> =
|
||||
DataLoaderFactory.newDataLoader<String, GlobalMetaType?> { ids ->
|
||||
future {
|
||||
transaction {
|
||||
addLogger(Slf4jSqlDebugLogger)
|
||||
val metasByRefId = GlobalMetaTable.select { GlobalMetaTable.key inList ids }
|
||||
val metasByRefId =
|
||||
GlobalMetaTable.select { GlobalMetaTable.key inList ids }
|
||||
.map { GlobalMetaType(it) }
|
||||
.associateBy { it.key }
|
||||
ids.map { metasByRefId[it] }
|
||||
@@ -34,11 +37,14 @@ class GlobalMetaDataLoader : KotlinDataLoader<String, GlobalMetaType?> {
|
||||
|
||||
class ChapterMetaDataLoader : KotlinDataLoader<Int, List<ChapterMetaType>> {
|
||||
override val dataLoaderName = "ChapterMetaDataLoader"
|
||||
override fun getDataLoader(): DataLoader<Int, List<ChapterMetaType>> = DataLoaderFactory.newDataLoader<Int, List<ChapterMetaType>> { ids ->
|
||||
|
||||
override fun getDataLoader(): DataLoader<Int, List<ChapterMetaType>> =
|
||||
DataLoaderFactory.newDataLoader<Int, List<ChapterMetaType>> { ids ->
|
||||
future {
|
||||
transaction {
|
||||
addLogger(Slf4jSqlDebugLogger)
|
||||
val metasByRefId = ChapterMetaTable.select { ChapterMetaTable.ref inList ids }
|
||||
val metasByRefId =
|
||||
ChapterMetaTable.select { ChapterMetaTable.ref inList ids }
|
||||
.map { ChapterMetaType(it) }
|
||||
.groupBy { it.chapterId }
|
||||
ids.map { metasByRefId[it].orEmpty() }
|
||||
@@ -49,11 +55,14 @@ class ChapterMetaDataLoader : KotlinDataLoader<Int, List<ChapterMetaType>> {
|
||||
|
||||
class MangaMetaDataLoader : KotlinDataLoader<Int, List<MangaMetaType>> {
|
||||
override val dataLoaderName = "MangaMetaDataLoader"
|
||||
override fun getDataLoader(): DataLoader<Int, List<MangaMetaType>> = DataLoaderFactory.newDataLoader<Int, List<MangaMetaType>> { ids ->
|
||||
|
||||
override fun getDataLoader(): DataLoader<Int, List<MangaMetaType>> =
|
||||
DataLoaderFactory.newDataLoader<Int, List<MangaMetaType>> { ids ->
|
||||
future {
|
||||
transaction {
|
||||
addLogger(Slf4jSqlDebugLogger)
|
||||
val metasByRefId = MangaMetaTable.select { MangaMetaTable.ref inList ids }
|
||||
val metasByRefId =
|
||||
MangaMetaTable.select { MangaMetaTable.ref inList ids }
|
||||
.map { MangaMetaType(it) }
|
||||
.groupBy { it.mangaId }
|
||||
ids.map { metasByRefId[it].orEmpty() }
|
||||
@@ -64,11 +73,14 @@ class MangaMetaDataLoader : KotlinDataLoader<Int, List<MangaMetaType>> {
|
||||
|
||||
class CategoryMetaDataLoader : KotlinDataLoader<Int, List<CategoryMetaType>> {
|
||||
override val dataLoaderName = "CategoryMetaDataLoader"
|
||||
override fun getDataLoader(): DataLoader<Int, List<CategoryMetaType>> = DataLoaderFactory.newDataLoader<Int, List<CategoryMetaType>> { ids ->
|
||||
|
||||
override fun getDataLoader(): DataLoader<Int, List<CategoryMetaType>> =
|
||||
DataLoaderFactory.newDataLoader<Int, List<CategoryMetaType>> { ids ->
|
||||
future {
|
||||
transaction {
|
||||
addLogger(Slf4jSqlDebugLogger)
|
||||
val metasByRefId = CategoryMetaTable.select { CategoryMetaTable.ref inList ids }
|
||||
val metasByRefId =
|
||||
CategoryMetaTable.select { CategoryMetaTable.ref inList ids }
|
||||
.map { CategoryMetaType(it) }
|
||||
.groupBy { it.categoryId }
|
||||
ids.map { metasByRefId[it].orEmpty() }
|
||||
|
||||
@@ -23,11 +23,14 @@ import suwayomi.tachidesk.server.JavalinSetup.future
|
||||
|
||||
class SourceDataLoader : KotlinDataLoader<Long, SourceType?> {
|
||||
override val dataLoaderName = "SourceDataLoader"
|
||||
override fun getDataLoader(): DataLoader<Long, SourceType?> = DataLoaderFactory.newDataLoader { ids ->
|
||||
|
||||
override fun getDataLoader(): DataLoader<Long, SourceType?> =
|
||||
DataLoaderFactory.newDataLoader { ids ->
|
||||
future {
|
||||
transaction {
|
||||
addLogger(Slf4jSqlDebugLogger)
|
||||
val source = SourceTable.select { SourceTable.id inList ids }
|
||||
val source =
|
||||
SourceTable.select { SourceTable.id inList ids }
|
||||
.mapNotNull { SourceType(it) }
|
||||
.associateBy { it.id }
|
||||
ids.map { source[it] }
|
||||
@@ -38,12 +41,15 @@ class SourceDataLoader : KotlinDataLoader<Long, SourceType?> {
|
||||
|
||||
class SourcesForExtensionDataLoader : KotlinDataLoader<String, SourceNodeList> {
|
||||
override val dataLoaderName = "SourcesForExtensionDataLoader"
|
||||
override fun getDataLoader(): DataLoader<String, SourceNodeList> = DataLoaderFactory.newDataLoader { ids ->
|
||||
|
||||
override fun getDataLoader(): DataLoader<String, SourceNodeList> =
|
||||
DataLoaderFactory.newDataLoader { ids ->
|
||||
future {
|
||||
transaction {
|
||||
addLogger(Slf4jSqlDebugLogger)
|
||||
|
||||
val sourcesByExtensionPkg = SourceTable.innerJoin(ExtensionTable)
|
||||
val sourcesByExtensionPkg =
|
||||
SourceTable.innerJoin(ExtensionTable)
|
||||
.select { ExtensionTable.pkgName inList ids }
|
||||
.map { Pair(it[ExtensionTable.pkgName], SourceType(it)) }
|
||||
.groupBy { it.first }
|
||||
|
||||
@@ -20,17 +20,16 @@ import kotlin.time.Duration.Companion.seconds
|
||||
class BackupMutation {
|
||||
data class RestoreBackupInput(
|
||||
val clientMutationId: String? = null,
|
||||
val backup: UploadedFile
|
||||
val backup: UploadedFile,
|
||||
)
|
||||
|
||||
data class RestoreBackupPayload(
|
||||
val clientMutationId: String?,
|
||||
val status: BackupRestoreStatus
|
||||
val status: BackupRestoreStatus,
|
||||
)
|
||||
|
||||
@OptIn(DelicateCoroutinesApi::class)
|
||||
fun restoreBackup(
|
||||
input: RestoreBackupInput
|
||||
): CompletableFuture<RestoreBackupPayload> {
|
||||
fun restoreBackup(input: RestoreBackupInput): CompletableFuture<RestoreBackupPayload> {
|
||||
val (clientMutationId, backup) = input
|
||||
|
||||
return future {
|
||||
@@ -38,7 +37,8 @@ class BackupMutation {
|
||||
ProtoBackupImport.performRestore(backup.content)
|
||||
}
|
||||
|
||||
val status = withTimeout(10.seconds) {
|
||||
val status =
|
||||
withTimeout(10.seconds) {
|
||||
ProtoBackupImport.backupRestoreState.first {
|
||||
it != ProtoBackupImport.BackupRestoreState.Idle
|
||||
}.toStatus()
|
||||
@@ -51,32 +51,33 @@ class BackupMutation {
|
||||
data class CreateBackupInput(
|
||||
val clientMutationId: String? = null,
|
||||
val includeChapters: Boolean? = null,
|
||||
val includeCategories: Boolean? = null
|
||||
val includeCategories: Boolean? = null,
|
||||
)
|
||||
|
||||
data class CreateBackupPayload(
|
||||
val clientMutationId: String?,
|
||||
val url: String
|
||||
val url: String,
|
||||
)
|
||||
fun createBackup(
|
||||
input: CreateBackupInput? = null
|
||||
): CreateBackupPayload {
|
||||
|
||||
fun createBackup(input: CreateBackupInput? = null): CreateBackupPayload {
|
||||
val filename = ProtoBackupExport.getBackupFilename()
|
||||
|
||||
val backup = ProtoBackupExport.createBackup(
|
||||
val backup =
|
||||
ProtoBackupExport.createBackup(
|
||||
BackupFlags(
|
||||
includeManga = true,
|
||||
includeCategories = input?.includeCategories ?: true,
|
||||
includeChapters = input?.includeChapters ?: true,
|
||||
includeTracking = true,
|
||||
includeHistory = true
|
||||
)
|
||||
includeHistory = true,
|
||||
),
|
||||
)
|
||||
|
||||
TemporaryFileStorage.saveFile(filename, backup)
|
||||
|
||||
return CreateBackupPayload(
|
||||
clientMutationId = input?.clientMutationId,
|
||||
url = "/api/graphql/files/backup/$filename"
|
||||
url = "/api/graphql/files/backup/$filename",
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -27,15 +27,15 @@ import suwayomi.tachidesk.manga.model.table.MangaTable
|
||||
class CategoryMutation {
|
||||
data class SetCategoryMetaInput(
|
||||
val clientMutationId: String? = null,
|
||||
val meta: CategoryMetaType
|
||||
val meta: CategoryMetaType,
|
||||
)
|
||||
|
||||
data class SetCategoryMetaPayload(
|
||||
val clientMutationId: String?,
|
||||
val meta: CategoryMetaType
|
||||
val meta: CategoryMetaType,
|
||||
)
|
||||
fun setCategoryMeta(
|
||||
input: SetCategoryMetaInput
|
||||
): SetCategoryMetaPayload {
|
||||
|
||||
fun setCategoryMeta(input: SetCategoryMetaInput): SetCategoryMetaPayload {
|
||||
val (clientMutationId, meta) = input
|
||||
|
||||
Category.modifyMeta(meta.categoryId, meta.key, meta.value)
|
||||
@@ -46,25 +46,28 @@ class CategoryMutation {
|
||||
data class DeleteCategoryMetaInput(
|
||||
val clientMutationId: String? = null,
|
||||
val categoryId: Int,
|
||||
val key: String
|
||||
val key: String,
|
||||
)
|
||||
|
||||
data class DeleteCategoryMetaPayload(
|
||||
val clientMutationId: String?,
|
||||
val meta: CategoryMetaType?,
|
||||
val category: CategoryType
|
||||
val category: CategoryType,
|
||||
)
|
||||
fun deleteCategoryMeta(
|
||||
input: DeleteCategoryMetaInput
|
||||
): DeleteCategoryMetaPayload {
|
||||
|
||||
fun deleteCategoryMeta(input: DeleteCategoryMetaInput): DeleteCategoryMetaPayload {
|
||||
val (clientMutationId, categoryId, key) = input
|
||||
|
||||
val (meta, category) = transaction {
|
||||
val meta = CategoryMetaTable.select { (CategoryMetaTable.ref eq categoryId) and (CategoryMetaTable.key eq key) }
|
||||
val (meta, category) =
|
||||
transaction {
|
||||
val meta =
|
||||
CategoryMetaTable.select { (CategoryMetaTable.ref eq categoryId) and (CategoryMetaTable.key eq key) }
|
||||
.firstOrNull()
|
||||
|
||||
CategoryMetaTable.deleteWhere { (CategoryMetaTable.ref eq categoryId) and (CategoryMetaTable.key eq key) }
|
||||
|
||||
val category = transaction {
|
||||
val category =
|
||||
transaction {
|
||||
CategoryType(CategoryTable.select { CategoryTable.id eq categoryId }.first())
|
||||
}
|
||||
|
||||
@@ -81,30 +84,35 @@ class CategoryMutation {
|
||||
data class UpdateCategoryPatch(
|
||||
val name: String? = null,
|
||||
val default: Boolean? = null,
|
||||
val includeInUpdate: IncludeInUpdate? = null
|
||||
val includeInUpdate: IncludeInUpdate? = null,
|
||||
)
|
||||
|
||||
data class UpdateCategoryPayload(
|
||||
val clientMutationId: String?,
|
||||
val category: CategoryType
|
||||
val category: CategoryType,
|
||||
)
|
||||
|
||||
data class UpdateCategoryInput(
|
||||
val clientMutationId: String? = null,
|
||||
val id: Int,
|
||||
val patch: UpdateCategoryPatch
|
||||
val patch: UpdateCategoryPatch,
|
||||
)
|
||||
|
||||
data class UpdateCategoriesPayload(
|
||||
val clientMutationId: String?,
|
||||
val categories: List<CategoryType>
|
||||
val categories: List<CategoryType>,
|
||||
)
|
||||
|
||||
data class UpdateCategoriesInput(
|
||||
val clientMutationId: String? = null,
|
||||
val ids: List<Int>,
|
||||
val patch: UpdateCategoryPatch
|
||||
val patch: UpdateCategoryPatch,
|
||||
)
|
||||
|
||||
private fun updateCategories(ids: List<Int>, patch: UpdateCategoryPatch) {
|
||||
private fun updateCategories(
|
||||
ids: List<Int>,
|
||||
patch: UpdateCategoryPatch,
|
||||
) {
|
||||
transaction {
|
||||
if (patch.name != null) {
|
||||
CategoryTable.update({ CategoryTable.id inList ids }) { update ->
|
||||
@@ -135,13 +143,14 @@ class CategoryMutation {
|
||||
|
||||
updateCategories(listOf(id), patch)
|
||||
|
||||
val category = transaction {
|
||||
val category =
|
||||
transaction {
|
||||
CategoryType(CategoryTable.select { CategoryTable.id eq id }.first())
|
||||
}
|
||||
|
||||
return UpdateCategoryPayload(
|
||||
clientMutationId = clientMutationId,
|
||||
category = category
|
||||
category = category,
|
||||
)
|
||||
}
|
||||
|
||||
@@ -150,24 +159,26 @@ class CategoryMutation {
|
||||
|
||||
updateCategories(ids, patch)
|
||||
|
||||
val categories = transaction {
|
||||
val categories =
|
||||
transaction {
|
||||
CategoryTable.select { CategoryTable.id inList ids }.map { CategoryType(it) }
|
||||
}
|
||||
|
||||
return UpdateCategoriesPayload(
|
||||
clientMutationId = clientMutationId,
|
||||
categories = categories
|
||||
categories = categories,
|
||||
)
|
||||
}
|
||||
|
||||
data class UpdateCategoryOrderPayload(
|
||||
val clientMutationId: String?,
|
||||
val categories: List<CategoryType>
|
||||
val categories: List<CategoryType>,
|
||||
)
|
||||
|
||||
data class UpdateCategoryOrderInput(
|
||||
val clientMutationId: String? = null,
|
||||
val id: Int,
|
||||
val position: Int
|
||||
val position: Int,
|
||||
)
|
||||
|
||||
fun updateCategoryOrder(input: UpdateCategoryOrderInput): UpdateCategoryOrderPayload {
|
||||
@@ -177,7 +188,8 @@ class CategoryMutation {
|
||||
}
|
||||
|
||||
transaction {
|
||||
val currentOrder = CategoryTable
|
||||
val currentOrder =
|
||||
CategoryTable
|
||||
.select { CategoryTable.id eq categoryId }
|
||||
.first()[CategoryTable.order]
|
||||
|
||||
@@ -200,13 +212,14 @@ class CategoryMutation {
|
||||
|
||||
Category.normalizeCategories()
|
||||
|
||||
val categories = transaction {
|
||||
val categories =
|
||||
transaction {
|
||||
CategoryTable.selectAll().orderBy(CategoryTable.order).map { CategoryType(it) }
|
||||
}
|
||||
|
||||
return UpdateCategoryOrderPayload(
|
||||
clientMutationId = clientMutationId,
|
||||
categories = categories
|
||||
categories = categories,
|
||||
)
|
||||
}
|
||||
|
||||
@@ -215,15 +228,15 @@ class CategoryMutation {
|
||||
val name: String,
|
||||
val order: Int? = null,
|
||||
val default: Boolean? = null,
|
||||
val includeInUpdate: IncludeInUpdate? = null
|
||||
val includeInUpdate: IncludeInUpdate? = null,
|
||||
)
|
||||
|
||||
data class CreateCategoryPayload(
|
||||
val clientMutationId: String?,
|
||||
val category: CategoryType
|
||||
val category: CategoryType,
|
||||
)
|
||||
fun createCategory(
|
||||
input: CreateCategoryInput
|
||||
): CreateCategoryPayload {
|
||||
|
||||
fun createCategory(input: CreateCategoryInput): CreateCategoryPayload {
|
||||
val (clientMutationId, name, order, default, includeInUpdate) = input
|
||||
transaction {
|
||||
require(CategoryTable.select { CategoryTable.name eq input.name }.isEmpty()) {
|
||||
@@ -239,14 +252,16 @@ class CategoryMutation {
|
||||
}
|
||||
}
|
||||
|
||||
val category = transaction {
|
||||
val category =
|
||||
transaction {
|
||||
if (order != null) {
|
||||
CategoryTable.update({ CategoryTable.order greaterEq order }) {
|
||||
it[CategoryTable.order] = CategoryTable.order + 1
|
||||
}
|
||||
}
|
||||
|
||||
val id = CategoryTable.insertAndGetId {
|
||||
val id =
|
||||
CategoryTable.insertAndGetId {
|
||||
it[CategoryTable.name] = input.name
|
||||
it[CategoryTable.order] = order ?: Int.MAX_VALUE
|
||||
if (default != null) {
|
||||
@@ -267,30 +282,33 @@ class CategoryMutation {
|
||||
|
||||
data class DeleteCategoryInput(
|
||||
val clientMutationId: String? = null,
|
||||
val categoryId: Int
|
||||
val categoryId: Int,
|
||||
)
|
||||
|
||||
data class DeleteCategoryPayload(
|
||||
val clientMutationId: String?,
|
||||
val category: CategoryType?,
|
||||
val mangas: List<MangaType>
|
||||
val mangas: List<MangaType>,
|
||||
)
|
||||
fun deleteCategory(
|
||||
input: DeleteCategoryInput
|
||||
): DeleteCategoryPayload {
|
||||
|
||||
fun deleteCategory(input: DeleteCategoryInput): DeleteCategoryPayload {
|
||||
val (clientMutationId, categoryId) = input
|
||||
if (categoryId == 0) { // Don't delete default category
|
||||
return DeleteCategoryPayload(
|
||||
clientMutationId,
|
||||
null,
|
||||
emptyList()
|
||||
emptyList(),
|
||||
)
|
||||
}
|
||||
|
||||
val (category, mangas) = transaction {
|
||||
val category = CategoryTable.select { CategoryTable.id eq categoryId }
|
||||
val (category, mangas) =
|
||||
transaction {
|
||||
val category =
|
||||
CategoryTable.select { CategoryTable.id eq categoryId }
|
||||
.firstOrNull()
|
||||
|
||||
val mangas = transaction {
|
||||
val mangas =
|
||||
transaction {
|
||||
MangaTable.innerJoin(CategoryMangaTable)
|
||||
.select { CategoryMangaTable.category eq categoryId }
|
||||
.map { MangaType(it) }
|
||||
@@ -313,30 +331,35 @@ class CategoryMutation {
|
||||
data class UpdateMangaCategoriesPatch(
|
||||
val clearCategories: Boolean? = null,
|
||||
val addToCategories: List<Int>? = null,
|
||||
val removeFromCategories: List<Int>? = null
|
||||
val removeFromCategories: List<Int>? = null,
|
||||
)
|
||||
|
||||
data class UpdateMangaCategoriesPayload(
|
||||
val clientMutationId: String?,
|
||||
val manga: MangaType
|
||||
val manga: MangaType,
|
||||
)
|
||||
|
||||
data class UpdateMangaCategoriesInput(
|
||||
val clientMutationId: String? = null,
|
||||
val id: Int,
|
||||
val patch: UpdateMangaCategoriesPatch
|
||||
val patch: UpdateMangaCategoriesPatch,
|
||||
)
|
||||
|
||||
data class UpdateMangasCategoriesPayload(
|
||||
val clientMutationId: String?,
|
||||
val mangas: List<MangaType>
|
||||
val mangas: List<MangaType>,
|
||||
)
|
||||
|
||||
data class UpdateMangasCategoriesInput(
|
||||
val clientMutationId: String? = null,
|
||||
val ids: List<Int>,
|
||||
val patch: UpdateMangaCategoriesPatch
|
||||
val patch: UpdateMangaCategoriesPatch,
|
||||
)
|
||||
|
||||
private fun updateMangas(ids: List<Int>, patch: UpdateMangaCategoriesPatch) {
|
||||
private fun updateMangas(
|
||||
ids: List<Int>,
|
||||
patch: UpdateMangaCategoriesPatch,
|
||||
) {
|
||||
transaction {
|
||||
if (patch.clearCategories == true) {
|
||||
CategoryMangaTable.deleteWhere { CategoryMangaTable.manga inList ids }
|
||||
@@ -346,10 +369,12 @@ class CategoryMutation {
|
||||
}
|
||||
}
|
||||
if (!patch.addToCategories.isNullOrEmpty()) {
|
||||
val newCategories = buildList {
|
||||
val newCategories =
|
||||
buildList {
|
||||
ids.forEach { mangaId ->
|
||||
patch.addToCategories.forEach { categoryId ->
|
||||
val existingMapping = CategoryMangaTable.select {
|
||||
val existingMapping =
|
||||
CategoryMangaTable.select {
|
||||
(CategoryMangaTable.manga eq mangaId) and (CategoryMangaTable.category eq categoryId)
|
||||
}.isNotEmpty()
|
||||
|
||||
@@ -373,13 +398,14 @@ class CategoryMutation {
|
||||
|
||||
updateMangas(listOf(id), patch)
|
||||
|
||||
val manga = transaction {
|
||||
val manga =
|
||||
transaction {
|
||||
MangaType(MangaTable.select { MangaTable.id eq id }.first())
|
||||
}
|
||||
|
||||
return UpdateMangaCategoriesPayload(
|
||||
clientMutationId = clientMutationId,
|
||||
manga = manga
|
||||
manga = manga,
|
||||
)
|
||||
}
|
||||
|
||||
@@ -388,13 +414,14 @@ class CategoryMutation {
|
||||
|
||||
updateMangas(ids, patch)
|
||||
|
||||
val mangas = transaction {
|
||||
val mangas =
|
||||
transaction {
|
||||
MangaTable.select { MangaTable.id inList ids }.map { MangaType(it) }
|
||||
}
|
||||
|
||||
return UpdateMangasCategoriesPayload(
|
||||
clientMutationId = clientMutationId,
|
||||
mangas = mangas
|
||||
mangas = mangas,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -25,30 +25,35 @@ class ChapterMutation {
|
||||
data class UpdateChapterPatch(
|
||||
val isBookmarked: Boolean? = null,
|
||||
val isRead: Boolean? = null,
|
||||
val lastPageRead: Int? = null
|
||||
val lastPageRead: Int? = null,
|
||||
)
|
||||
|
||||
data class UpdateChapterPayload(
|
||||
val clientMutationId: String?,
|
||||
val chapter: ChapterType
|
||||
val chapter: ChapterType,
|
||||
)
|
||||
|
||||
data class UpdateChapterInput(
|
||||
val clientMutationId: String? = null,
|
||||
val id: Int,
|
||||
val patch: UpdateChapterPatch
|
||||
val patch: UpdateChapterPatch,
|
||||
)
|
||||
|
||||
data class UpdateChaptersPayload(
|
||||
val clientMutationId: String?,
|
||||
val chapters: List<ChapterType>
|
||||
val chapters: List<ChapterType>,
|
||||
)
|
||||
|
||||
data class UpdateChaptersInput(
|
||||
val clientMutationId: String? = null,
|
||||
val ids: List<Int>,
|
||||
val patch: UpdateChapterPatch
|
||||
val patch: UpdateChapterPatch,
|
||||
)
|
||||
|
||||
private fun updateChapters(ids: List<Int>, patch: UpdateChapterPatch) {
|
||||
private fun updateChapters(
|
||||
ids: List<Int>,
|
||||
patch: UpdateChapterPatch,
|
||||
) {
|
||||
transaction {
|
||||
if (patch.isRead != null || patch.isBookmarked != null || patch.lastPageRead != null) {
|
||||
val now = Instant.now().epochSecond
|
||||
@@ -68,58 +73,56 @@ class ChapterMutation {
|
||||
}
|
||||
}
|
||||
|
||||
fun updateChapter(
|
||||
input: UpdateChapterInput
|
||||
): UpdateChapterPayload {
|
||||
fun updateChapter(input: UpdateChapterInput): UpdateChapterPayload {
|
||||
val (clientMutationId, id, patch) = input
|
||||
|
||||
updateChapters(listOf(id), patch)
|
||||
|
||||
val chapter = transaction {
|
||||
val chapter =
|
||||
transaction {
|
||||
ChapterType(ChapterTable.select { ChapterTable.id eq id }.first())
|
||||
}
|
||||
|
||||
return UpdateChapterPayload(
|
||||
clientMutationId = clientMutationId,
|
||||
chapter = chapter
|
||||
chapter = chapter,
|
||||
)
|
||||
}
|
||||
|
||||
fun updateChapters(
|
||||
input: UpdateChaptersInput
|
||||
): UpdateChaptersPayload {
|
||||
fun updateChapters(input: UpdateChaptersInput): UpdateChaptersPayload {
|
||||
val (clientMutationId, ids, patch) = input
|
||||
|
||||
updateChapters(ids, patch)
|
||||
|
||||
val chapters = transaction {
|
||||
val chapters =
|
||||
transaction {
|
||||
ChapterTable.select { ChapterTable.id inList ids }.map { ChapterType(it) }
|
||||
}
|
||||
|
||||
return UpdateChaptersPayload(
|
||||
clientMutationId = clientMutationId,
|
||||
chapters = chapters
|
||||
chapters = chapters,
|
||||
)
|
||||
}
|
||||
|
||||
data class FetchChaptersInput(
|
||||
val clientMutationId: String? = null,
|
||||
val mangaId: Int
|
||||
)
|
||||
data class FetchChaptersPayload(
|
||||
val clientMutationId: String?,
|
||||
val chapters: List<ChapterType>
|
||||
val mangaId: Int,
|
||||
)
|
||||
|
||||
fun fetchChapters(
|
||||
input: FetchChaptersInput
|
||||
): CompletableFuture<FetchChaptersPayload> {
|
||||
data class FetchChaptersPayload(
|
||||
val clientMutationId: String?,
|
||||
val chapters: List<ChapterType>,
|
||||
)
|
||||
|
||||
fun fetchChapters(input: FetchChaptersInput): CompletableFuture<FetchChaptersPayload> {
|
||||
val (clientMutationId, mangaId) = input
|
||||
|
||||
return future {
|
||||
Chapter.fetchChapterList(mangaId)
|
||||
}.thenApply {
|
||||
val chapters = transaction {
|
||||
val chapters =
|
||||
transaction {
|
||||
ChapterTable.select { ChapterTable.manga eq mangaId }
|
||||
.orderBy(ChapterTable.sourceOrder)
|
||||
.map { ChapterType(it) }
|
||||
@@ -127,22 +130,22 @@ class ChapterMutation {
|
||||
|
||||
FetchChaptersPayload(
|
||||
clientMutationId = clientMutationId,
|
||||
chapters = chapters
|
||||
chapters = chapters,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
data class SetChapterMetaInput(
|
||||
val clientMutationId: String? = null,
|
||||
val meta: ChapterMetaType
|
||||
val meta: ChapterMetaType,
|
||||
)
|
||||
|
||||
data class SetChapterMetaPayload(
|
||||
val clientMutationId: String?,
|
||||
val meta: ChapterMetaType
|
||||
val meta: ChapterMetaType,
|
||||
)
|
||||
fun setChapterMeta(
|
||||
input: SetChapterMetaInput
|
||||
): SetChapterMetaPayload {
|
||||
|
||||
fun setChapterMeta(input: SetChapterMetaInput): SetChapterMetaPayload {
|
||||
val (clientMutationId, meta) = input
|
||||
|
||||
Chapter.modifyChapterMeta(meta.chapterId, meta.key, meta.value)
|
||||
@@ -153,25 +156,28 @@ class ChapterMutation {
|
||||
data class DeleteChapterMetaInput(
|
||||
val clientMutationId: String? = null,
|
||||
val chapterId: Int,
|
||||
val key: String
|
||||
val key: String,
|
||||
)
|
||||
|
||||
data class DeleteChapterMetaPayload(
|
||||
val clientMutationId: String?,
|
||||
val meta: ChapterMetaType?,
|
||||
val chapter: ChapterType
|
||||
val chapter: ChapterType,
|
||||
)
|
||||
fun deleteChapterMeta(
|
||||
input: DeleteChapterMetaInput
|
||||
): DeleteChapterMetaPayload {
|
||||
|
||||
fun deleteChapterMeta(input: DeleteChapterMetaInput): DeleteChapterMetaPayload {
|
||||
val (clientMutationId, chapterId, key) = input
|
||||
|
||||
val (meta, chapter) = transaction {
|
||||
val meta = ChapterMetaTable.select { (ChapterMetaTable.ref eq chapterId) and (ChapterMetaTable.key eq key) }
|
||||
val (meta, chapter) =
|
||||
transaction {
|
||||
val meta =
|
||||
ChapterMetaTable.select { (ChapterMetaTable.ref eq chapterId) and (ChapterMetaTable.key eq key) }
|
||||
.firstOrNull()
|
||||
|
||||
ChapterMetaTable.deleteWhere { (ChapterMetaTable.ref eq chapterId) and (ChapterMetaTable.key eq key) }
|
||||
|
||||
val chapter = transaction {
|
||||
val chapter =
|
||||
transaction {
|
||||
ChapterType(ChapterTable.select { ChapterTable.id eq chapterId }.first())
|
||||
}
|
||||
|
||||
@@ -187,16 +193,16 @@ class ChapterMutation {
|
||||
|
||||
data class FetchChapterPagesInput(
|
||||
val clientMutationId: String? = null,
|
||||
val chapterId: Int
|
||||
val chapterId: Int,
|
||||
)
|
||||
|
||||
data class FetchChapterPagesPayload(
|
||||
val clientMutationId: String?,
|
||||
val pages: List<String>,
|
||||
val chapter: ChapterType
|
||||
val chapter: ChapterType,
|
||||
)
|
||||
fun fetchChapterPages(
|
||||
input: FetchChapterPagesInput
|
||||
): CompletableFuture<FetchChapterPagesPayload> {
|
||||
|
||||
fun fetchChapterPages(input: FetchChapterPagesInput): CompletableFuture<FetchChapterPagesPayload> {
|
||||
val (clientMutationId, chapterId) = input
|
||||
|
||||
return future {
|
||||
@@ -204,10 +210,11 @@ class ChapterMutation {
|
||||
}.thenApply { chapter ->
|
||||
FetchChapterPagesPayload(
|
||||
clientMutationId = clientMutationId,
|
||||
pages = List(chapter.pageCount) { index ->
|
||||
pages =
|
||||
List(chapter.pageCount) { index ->
|
||||
"/api/v1/manga/${chapter.mangaId}/chapter/${chapter.index}/page/$index"
|
||||
},
|
||||
chapter = ChapterType(chapter)
|
||||
chapter = ChapterType(chapter),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,14 +16,14 @@ import java.util.concurrent.CompletableFuture
|
||||
import kotlin.time.Duration.Companion.seconds
|
||||
|
||||
class DownloadMutation {
|
||||
|
||||
data class DeleteDownloadedChaptersInput(
|
||||
val clientMutationId: String? = null,
|
||||
val ids: List<Int>
|
||||
val ids: List<Int>,
|
||||
)
|
||||
|
||||
data class DeleteDownloadedChaptersPayload(
|
||||
val clientMutationId: String?,
|
||||
val chapters: List<ChapterType>
|
||||
val chapters: List<ChapterType>,
|
||||
)
|
||||
|
||||
fun deleteDownloadedChapters(input: DeleteDownloadedChaptersInput): DeleteDownloadedChaptersPayload {
|
||||
@@ -33,20 +33,22 @@ class DownloadMutation {
|
||||
|
||||
return DeleteDownloadedChaptersPayload(
|
||||
clientMutationId = clientMutationId,
|
||||
chapters = transaction {
|
||||
chapters =
|
||||
transaction {
|
||||
ChapterTable.select { ChapterTable.id inList chapters }
|
||||
.map { ChapterType(it) }
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
data class DeleteDownloadedChapterInput(
|
||||
val clientMutationId: String? = null,
|
||||
val id: Int
|
||||
val id: Int,
|
||||
)
|
||||
|
||||
data class DeleteDownloadedChapterPayload(
|
||||
val clientMutationId: String?,
|
||||
val chapters: ChapterType
|
||||
val chapters: ChapterType,
|
||||
)
|
||||
|
||||
fun deleteDownloadedChapter(input: DeleteDownloadedChapterInput): DeleteDownloadedChapterPayload {
|
||||
@@ -56,24 +58,24 @@ class DownloadMutation {
|
||||
|
||||
return DeleteDownloadedChapterPayload(
|
||||
clientMutationId = clientMutationId,
|
||||
chapters = transaction {
|
||||
chapters =
|
||||
transaction {
|
||||
ChapterType(ChapterTable.select { ChapterTable.id eq chapter }.first())
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
data class EnqueueChapterDownloadsInput(
|
||||
val clientMutationId: String? = null,
|
||||
val ids: List<Int>
|
||||
)
|
||||
data class EnqueueChapterDownloadsPayload(
|
||||
val clientMutationId: String?,
|
||||
val downloadStatus: DownloadStatus
|
||||
val ids: List<Int>,
|
||||
)
|
||||
|
||||
fun enqueueChapterDownloads(
|
||||
input: EnqueueChapterDownloadsInput
|
||||
): CompletableFuture<EnqueueChapterDownloadsPayload> {
|
||||
data class EnqueueChapterDownloadsPayload(
|
||||
val clientMutationId: String?,
|
||||
val downloadStatus: DownloadStatus,
|
||||
)
|
||||
|
||||
fun enqueueChapterDownloads(input: EnqueueChapterDownloadsInput): CompletableFuture<EnqueueChapterDownloadsPayload> {
|
||||
val (clientMutationId, chapters) = input
|
||||
|
||||
DownloadManager.enqueue(DownloadManager.EnqueueInput(chapters))
|
||||
@@ -81,25 +83,25 @@ class DownloadMutation {
|
||||
return future {
|
||||
EnqueueChapterDownloadsPayload(
|
||||
clientMutationId = clientMutationId,
|
||||
downloadStatus = withTimeout(30.seconds) {
|
||||
downloadStatus =
|
||||
withTimeout(30.seconds) {
|
||||
DownloadStatus(DownloadManager.status.first { it.queue.any { it.chapter.id in chapters } })
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
data class EnqueueChapterDownloadInput(
|
||||
val clientMutationId: String? = null,
|
||||
val id: Int
|
||||
)
|
||||
data class EnqueueChapterDownloadPayload(
|
||||
val clientMutationId: String?,
|
||||
val downloadStatus: DownloadStatus
|
||||
val id: Int,
|
||||
)
|
||||
|
||||
fun enqueueChapterDownload(
|
||||
input: EnqueueChapterDownloadInput
|
||||
): CompletableFuture<EnqueueChapterDownloadPayload> {
|
||||
data class EnqueueChapterDownloadPayload(
|
||||
val clientMutationId: String?,
|
||||
val downloadStatus: DownloadStatus,
|
||||
)
|
||||
|
||||
fun enqueueChapterDownload(input: EnqueueChapterDownloadInput): CompletableFuture<EnqueueChapterDownloadPayload> {
|
||||
val (clientMutationId, chapter) = input
|
||||
|
||||
DownloadManager.enqueue(DownloadManager.EnqueueInput(listOf(chapter)))
|
||||
@@ -107,25 +109,25 @@ class DownloadMutation {
|
||||
return future {
|
||||
EnqueueChapterDownloadPayload(
|
||||
clientMutationId = clientMutationId,
|
||||
downloadStatus = withTimeout(30.seconds) {
|
||||
downloadStatus =
|
||||
withTimeout(30.seconds) {
|
||||
DownloadStatus(DownloadManager.status.first { it.queue.any { it.chapter.id == chapter } })
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
data class DequeueChapterDownloadsInput(
|
||||
val clientMutationId: String? = null,
|
||||
val ids: List<Int>
|
||||
)
|
||||
data class DequeueChapterDownloadsPayload(
|
||||
val clientMutationId: String?,
|
||||
val downloadStatus: DownloadStatus
|
||||
val ids: List<Int>,
|
||||
)
|
||||
|
||||
fun dequeueChapterDownloads(
|
||||
input: DequeueChapterDownloadsInput
|
||||
): CompletableFuture<DequeueChapterDownloadsPayload> {
|
||||
data class DequeueChapterDownloadsPayload(
|
||||
val clientMutationId: String?,
|
||||
val downloadStatus: DownloadStatus,
|
||||
)
|
||||
|
||||
fun dequeueChapterDownloads(input: DequeueChapterDownloadsInput): CompletableFuture<DequeueChapterDownloadsPayload> {
|
||||
val (clientMutationId, chapters) = input
|
||||
|
||||
DownloadManager.dequeue(DownloadManager.EnqueueInput(chapters))
|
||||
@@ -133,25 +135,25 @@ class DownloadMutation {
|
||||
return future {
|
||||
DequeueChapterDownloadsPayload(
|
||||
clientMutationId = clientMutationId,
|
||||
downloadStatus = withTimeout(30.seconds) {
|
||||
downloadStatus =
|
||||
withTimeout(30.seconds) {
|
||||
DownloadStatus(DownloadManager.status.first { it.queue.none { it.chapter.id in chapters } })
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
data class DequeueChapterDownloadInput(
|
||||
val clientMutationId: String? = null,
|
||||
val id: Int
|
||||
)
|
||||
data class DequeueChapterDownloadPayload(
|
||||
val clientMutationId: String?,
|
||||
val downloadStatus: DownloadStatus
|
||||
val id: Int,
|
||||
)
|
||||
|
||||
fun dequeueChapterDownload(
|
||||
input: DequeueChapterDownloadInput
|
||||
): CompletableFuture<DequeueChapterDownloadPayload> {
|
||||
data class DequeueChapterDownloadPayload(
|
||||
val clientMutationId: String?,
|
||||
val downloadStatus: DownloadStatus,
|
||||
)
|
||||
|
||||
fun dequeueChapterDownload(input: DequeueChapterDownloadInput): CompletableFuture<DequeueChapterDownloadPayload> {
|
||||
val (clientMutationId, chapter) = input
|
||||
|
||||
DownloadManager.dequeue(DownloadManager.EnqueueInput(listOf(chapter)))
|
||||
@@ -159,19 +161,21 @@ class DownloadMutation {
|
||||
return future {
|
||||
DequeueChapterDownloadPayload(
|
||||
clientMutationId = clientMutationId,
|
||||
downloadStatus = withTimeout(30.seconds) {
|
||||
downloadStatus =
|
||||
withTimeout(30.seconds) {
|
||||
DownloadStatus(DownloadManager.status.first { it.queue.none { it.chapter.id == chapter } })
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
data class StartDownloaderInput(
|
||||
val clientMutationId: String? = null
|
||||
val clientMutationId: String? = null,
|
||||
)
|
||||
|
||||
data class StartDownloaderPayload(
|
||||
val clientMutationId: String?,
|
||||
val downloadStatus: DownloadStatus
|
||||
val downloadStatus: DownloadStatus,
|
||||
)
|
||||
|
||||
fun startDownloader(input: StartDownloaderInput): CompletableFuture<StartDownloaderPayload> {
|
||||
@@ -180,21 +184,23 @@ class DownloadMutation {
|
||||
return future {
|
||||
StartDownloaderPayload(
|
||||
input.clientMutationId,
|
||||
downloadStatus = withTimeout(30.seconds) {
|
||||
downloadStatus =
|
||||
withTimeout(30.seconds) {
|
||||
DownloadStatus(
|
||||
DownloadManager.status.first { it.status == Status.Started }
|
||||
DownloadManager.status.first { it.status == Status.Started },
|
||||
)
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
data class StopDownloaderInput(
|
||||
val clientMutationId: String? = null
|
||||
val clientMutationId: String? = null,
|
||||
)
|
||||
|
||||
data class StopDownloaderPayload(
|
||||
val clientMutationId: String?,
|
||||
val downloadStatus: DownloadStatus
|
||||
val downloadStatus: DownloadStatus,
|
||||
)
|
||||
|
||||
fun stopDownloader(input: StopDownloaderInput): CompletableFuture<StopDownloaderPayload> {
|
||||
@@ -202,21 +208,23 @@ class DownloadMutation {
|
||||
DownloadManager.stop()
|
||||
StopDownloaderPayload(
|
||||
input.clientMutationId,
|
||||
downloadStatus = withTimeout(30.seconds) {
|
||||
downloadStatus =
|
||||
withTimeout(30.seconds) {
|
||||
DownloadStatus(
|
||||
DownloadManager.status.first { it.status == Status.Stopped }
|
||||
DownloadManager.status.first { it.status == Status.Stopped },
|
||||
)
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
data class ClearDownloaderInput(
|
||||
val clientMutationId: String? = null
|
||||
val clientMutationId: String? = null,
|
||||
)
|
||||
|
||||
data class ClearDownloaderPayload(
|
||||
val clientMutationId: String?,
|
||||
val downloadStatus: DownloadStatus
|
||||
val downloadStatus: DownloadStatus,
|
||||
)
|
||||
|
||||
fun clearDownloader(input: ClearDownloaderInput): CompletableFuture<ClearDownloaderPayload> {
|
||||
@@ -224,11 +232,12 @@ class DownloadMutation {
|
||||
DownloadManager.clear()
|
||||
ClearDownloaderPayload(
|
||||
input.clientMutationId,
|
||||
downloadStatus = withTimeout(30.seconds) {
|
||||
downloadStatus =
|
||||
withTimeout(30.seconds) {
|
||||
DownloadStatus(
|
||||
DownloadManager.status.first { it.status == Status.Stopped && it.queue.isEmpty() }
|
||||
DownloadManager.status.first { it.status == Status.Stopped && it.queue.isEmpty() },
|
||||
)
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -236,11 +245,12 @@ class DownloadMutation {
|
||||
data class ReorderChapterDownloadInput(
|
||||
val clientMutationId: String? = null,
|
||||
val chapterId: Int,
|
||||
val to: Int
|
||||
val to: Int,
|
||||
)
|
||||
|
||||
data class ReorderChapterDownloadPayload(
|
||||
val clientMutationId: String?,
|
||||
val downloadStatus: DownloadStatus
|
||||
val downloadStatus: DownloadStatus,
|
||||
)
|
||||
|
||||
fun reorderChapterDownload(input: ReorderChapterDownloadInput): CompletableFuture<ReorderChapterDownloadPayload> {
|
||||
@@ -250,11 +260,12 @@ class DownloadMutation {
|
||||
return future {
|
||||
ReorderChapterDownloadPayload(
|
||||
clientMutationId,
|
||||
downloadStatus = withTimeout(30.seconds) {
|
||||
downloadStatus =
|
||||
withTimeout(30.seconds) {
|
||||
DownloadStatus(
|
||||
DownloadManager.status.first { it.queue.indexOfFirst { it.chapter.id == chapter } <= to }
|
||||
DownloadManager.status.first { it.queue.indexOfFirst { it.chapter.id == chapter } <= to },
|
||||
)
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -262,7 +273,7 @@ class DownloadMutation {
|
||||
data class DownloadAheadInput(
|
||||
val clientMutationId: String? = null,
|
||||
val mangaIds: List<Int> = emptyList(),
|
||||
val latestReadChapterIds: List<Int>? = null
|
||||
val latestReadChapterIds: List<Int>? = null,
|
||||
)
|
||||
|
||||
data class DownloadAheadPayload(val clientMutationId: String?)
|
||||
|
||||
@@ -15,31 +15,37 @@ class ExtensionMutation {
|
||||
data class UpdateExtensionPatch(
|
||||
val install: Boolean? = null,
|
||||
val update: Boolean? = null,
|
||||
val uninstall: Boolean? = null
|
||||
val uninstall: Boolean? = null,
|
||||
)
|
||||
|
||||
data class UpdateExtensionPayload(
|
||||
val clientMutationId: String?,
|
||||
val extension: ExtensionType
|
||||
val extension: ExtensionType,
|
||||
)
|
||||
|
||||
data class UpdateExtensionInput(
|
||||
val clientMutationId: String? = null,
|
||||
val id: String,
|
||||
val patch: UpdateExtensionPatch
|
||||
val patch: UpdateExtensionPatch,
|
||||
)
|
||||
|
||||
data class UpdateExtensionsPayload(
|
||||
val clientMutationId: String?,
|
||||
val extensions: List<ExtensionType>
|
||||
val extensions: List<ExtensionType>,
|
||||
)
|
||||
|
||||
data class UpdateExtensionsInput(
|
||||
val clientMutationId: String? = null,
|
||||
val ids: List<String>,
|
||||
val patch: UpdateExtensionPatch
|
||||
val patch: UpdateExtensionPatch,
|
||||
)
|
||||
|
||||
private suspend fun updateExtensions(ids: List<String>, patch: UpdateExtensionPatch) {
|
||||
val extensions = transaction {
|
||||
private suspend fun updateExtensions(
|
||||
ids: List<String>,
|
||||
patch: UpdateExtensionPatch,
|
||||
) {
|
||||
val extensions =
|
||||
transaction {
|
||||
ExtensionTable.select { ExtensionTable.pkgName inList ids }
|
||||
.map { ExtensionType(it) }
|
||||
}
|
||||
@@ -69,13 +75,14 @@ class ExtensionMutation {
|
||||
return future {
|
||||
updateExtensions(listOf(id), patch)
|
||||
}.thenApply {
|
||||
val extension = transaction {
|
||||
val extension =
|
||||
transaction {
|
||||
ExtensionType(ExtensionTable.select { ExtensionTable.pkgName eq id }.first())
|
||||
}
|
||||
|
||||
UpdateExtensionPayload(
|
||||
clientMutationId = clientMutationId,
|
||||
extension = extension
|
||||
extension = extension,
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -86,54 +93,55 @@ class ExtensionMutation {
|
||||
return future {
|
||||
updateExtensions(ids, patch)
|
||||
}.thenApply {
|
||||
val extensions = transaction {
|
||||
val extensions =
|
||||
transaction {
|
||||
ExtensionTable.select { ExtensionTable.pkgName inList ids }
|
||||
.map { ExtensionType(it) }
|
||||
}
|
||||
|
||||
UpdateExtensionsPayload(
|
||||
clientMutationId = clientMutationId,
|
||||
extensions = extensions
|
||||
extensions = extensions,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
data class FetchExtensionsInput(
|
||||
val clientMutationId: String? = null
|
||||
)
|
||||
data class FetchExtensionsPayload(
|
||||
val clientMutationId: String?,
|
||||
val extensions: List<ExtensionType>
|
||||
val clientMutationId: String? = null,
|
||||
)
|
||||
|
||||
fun fetchExtensions(
|
||||
input: FetchExtensionsInput
|
||||
): CompletableFuture<FetchExtensionsPayload> {
|
||||
data class FetchExtensionsPayload(
|
||||
val clientMutationId: String?,
|
||||
val extensions: List<ExtensionType>,
|
||||
)
|
||||
|
||||
fun fetchExtensions(input: FetchExtensionsInput): CompletableFuture<FetchExtensionsPayload> {
|
||||
val (clientMutationId) = input
|
||||
|
||||
return future {
|
||||
ExtensionsList.fetchExtensions()
|
||||
}.thenApply {
|
||||
val extensions = transaction {
|
||||
val extensions =
|
||||
transaction {
|
||||
ExtensionTable.select { ExtensionTable.name neq LocalSource.EXTENSION_NAME }
|
||||
.map { ExtensionType(it) }
|
||||
}
|
||||
|
||||
FetchExtensionsPayload(
|
||||
clientMutationId = clientMutationId,
|
||||
extensions = extensions
|
||||
extensions = extensions,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
data class InstallExternalExtensionInput(
|
||||
val clientMutationId: String? = null,
|
||||
val extensionFile: UploadedFile
|
||||
val extensionFile: UploadedFile,
|
||||
)
|
||||
|
||||
data class InstallExternalExtensionPayload(
|
||||
val clientMutationId: String?,
|
||||
val extension: ExtensionType
|
||||
val extension: ExtensionType,
|
||||
)
|
||||
|
||||
fun installExternalExtension(input: InstallExternalExtensionInput): CompletableFuture<InstallExternalExtensionPayload> {
|
||||
@@ -146,7 +154,7 @@ class ExtensionMutation {
|
||||
|
||||
InstallExternalExtensionPayload(
|
||||
clientMutationId,
|
||||
extension = ExtensionType(dbExtension)
|
||||
extension = ExtensionType(dbExtension),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,12 +14,12 @@ import kotlin.time.Duration.Companion.seconds
|
||||
|
||||
class InfoMutation {
|
||||
data class WebUIUpdateInput(
|
||||
val clientMutationId: String? = null
|
||||
val clientMutationId: String? = null,
|
||||
)
|
||||
|
||||
data class WebUIUpdatePayload(
|
||||
val clientMutationId: String?,
|
||||
val updateStatus: WebUIUpdateStatus
|
||||
val updateStatus: WebUIUpdateStatus,
|
||||
)
|
||||
|
||||
fun updateWebUI(input: WebUIUpdateInput): CompletableFuture<WebUIUpdatePayload> {
|
||||
@@ -35,14 +35,15 @@ class InfoMutation {
|
||||
return@withTimeout WebUIUpdatePayload(
|
||||
input.clientMutationId,
|
||||
WebUIUpdateStatus(
|
||||
info = WebUIUpdateInfo(
|
||||
info =
|
||||
WebUIUpdateInfo(
|
||||
channel = serverConfig.webUIChannel.value,
|
||||
tag = version,
|
||||
updateAvailable
|
||||
updateAvailable,
|
||||
),
|
||||
state = STOPPED,
|
||||
progress = 0
|
||||
)
|
||||
progress = 0,
|
||||
),
|
||||
)
|
||||
}
|
||||
try {
|
||||
@@ -53,7 +54,7 @@ class InfoMutation {
|
||||
|
||||
WebUIUpdatePayload(
|
||||
input.clientMutationId,
|
||||
updateStatus = WebInterfaceManager.status.first { it.state == DOWNLOADING }
|
||||
updateStatus = WebInterfaceManager.status.first { it.state == DOWNLOADING },
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -22,30 +22,35 @@ import java.util.concurrent.CompletableFuture
|
||||
*/
|
||||
class MangaMutation {
|
||||
data class UpdateMangaPatch(
|
||||
val inLibrary: Boolean? = null
|
||||
val inLibrary: Boolean? = null,
|
||||
)
|
||||
|
||||
data class UpdateMangaPayload(
|
||||
val clientMutationId: String?,
|
||||
val manga: MangaType
|
||||
val manga: MangaType,
|
||||
)
|
||||
|
||||
data class UpdateMangaInput(
|
||||
val clientMutationId: String? = null,
|
||||
val id: Int,
|
||||
val patch: UpdateMangaPatch
|
||||
val patch: UpdateMangaPatch,
|
||||
)
|
||||
|
||||
data class UpdateMangasPayload(
|
||||
val clientMutationId: String?,
|
||||
val mangas: List<MangaType>
|
||||
val mangas: List<MangaType>,
|
||||
)
|
||||
|
||||
data class UpdateMangasInput(
|
||||
val clientMutationId: String? = null,
|
||||
val ids: List<Int>,
|
||||
val patch: UpdateMangaPatch
|
||||
val patch: UpdateMangaPatch,
|
||||
)
|
||||
|
||||
private suspend fun updateMangas(ids: List<Int>, patch: UpdateMangaPatch) {
|
||||
private suspend fun updateMangas(
|
||||
ids: List<Int>,
|
||||
patch: UpdateMangaPatch,
|
||||
) {
|
||||
transaction {
|
||||
if (patch.inLibrary != null) {
|
||||
MangaTable.update({ MangaTable.id inList ids }) { update ->
|
||||
@@ -69,13 +74,14 @@ class MangaMutation {
|
||||
return future {
|
||||
updateMangas(listOf(id), patch)
|
||||
}.thenApply {
|
||||
val manga = transaction {
|
||||
val manga =
|
||||
transaction {
|
||||
MangaType(MangaTable.select { MangaTable.id eq id }.first())
|
||||
}
|
||||
|
||||
UpdateMangaPayload(
|
||||
clientMutationId = clientMutationId,
|
||||
manga = manga
|
||||
manga = manga,
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -86,55 +92,56 @@ class MangaMutation {
|
||||
return future {
|
||||
updateMangas(ids, patch)
|
||||
}.thenApply {
|
||||
val mangas = transaction {
|
||||
val mangas =
|
||||
transaction {
|
||||
MangaTable.select { MangaTable.id inList ids }.map { MangaType(it) }
|
||||
}
|
||||
|
||||
UpdateMangasPayload(
|
||||
clientMutationId = clientMutationId,
|
||||
mangas = mangas
|
||||
mangas = mangas,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
data class FetchMangaInput(
|
||||
val clientMutationId: String? = null,
|
||||
val id: Int
|
||||
)
|
||||
data class FetchMangaPayload(
|
||||
val clientMutationId: String?,
|
||||
val manga: MangaType
|
||||
val id: Int,
|
||||
)
|
||||
|
||||
fun fetchManga(
|
||||
input: FetchMangaInput
|
||||
): CompletableFuture<FetchMangaPayload> {
|
||||
data class FetchMangaPayload(
|
||||
val clientMutationId: String?,
|
||||
val manga: MangaType,
|
||||
)
|
||||
|
||||
fun fetchManga(input: FetchMangaInput): CompletableFuture<FetchMangaPayload> {
|
||||
val (clientMutationId, id) = input
|
||||
|
||||
return future {
|
||||
Manga.fetchManga(id)
|
||||
}.thenApply {
|
||||
val manga = transaction {
|
||||
val manga =
|
||||
transaction {
|
||||
MangaTable.select { MangaTable.id eq id }.first()
|
||||
}
|
||||
FetchMangaPayload(
|
||||
clientMutationId = clientMutationId,
|
||||
manga = MangaType(manga)
|
||||
manga = MangaType(manga),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
data class SetMangaMetaInput(
|
||||
val clientMutationId: String? = null,
|
||||
val meta: MangaMetaType
|
||||
val meta: MangaMetaType,
|
||||
)
|
||||
|
||||
data class SetMangaMetaPayload(
|
||||
val clientMutationId: String?,
|
||||
val meta: MangaMetaType
|
||||
val meta: MangaMetaType,
|
||||
)
|
||||
fun setMangaMeta(
|
||||
input: SetMangaMetaInput
|
||||
): SetMangaMetaPayload {
|
||||
|
||||
fun setMangaMeta(input: SetMangaMetaInput): SetMangaMetaPayload {
|
||||
val (clientMutationId, meta) = input
|
||||
|
||||
Manga.modifyMangaMeta(meta.mangaId, meta.key, meta.value)
|
||||
@@ -145,25 +152,28 @@ class MangaMutation {
|
||||
data class DeleteMangaMetaInput(
|
||||
val clientMutationId: String? = null,
|
||||
val mangaId: Int,
|
||||
val key: String
|
||||
val key: String,
|
||||
)
|
||||
|
||||
data class DeleteMangaMetaPayload(
|
||||
val clientMutationId: String?,
|
||||
val meta: MangaMetaType?,
|
||||
val manga: MangaType
|
||||
val manga: MangaType,
|
||||
)
|
||||
fun deleteMangaMeta(
|
||||
input: DeleteMangaMetaInput
|
||||
): DeleteMangaMetaPayload {
|
||||
|
||||
fun deleteMangaMeta(input: DeleteMangaMetaInput): DeleteMangaMetaPayload {
|
||||
val (clientMutationId, mangaId, key) = input
|
||||
|
||||
val (meta, manga) = transaction {
|
||||
val meta = MangaMetaTable.select { (MangaMetaTable.ref eq mangaId) and (MangaMetaTable.key eq key) }
|
||||
val (meta, manga) =
|
||||
transaction {
|
||||
val meta =
|
||||
MangaMetaTable.select { (MangaMetaTable.ref eq mangaId) and (MangaMetaTable.key eq key) }
|
||||
.firstOrNull()
|
||||
|
||||
MangaMetaTable.deleteWhere { (MangaMetaTable.ref eq mangaId) and (MangaMetaTable.key eq key) }
|
||||
|
||||
val manga = transaction {
|
||||
val manga =
|
||||
transaction {
|
||||
MangaType(MangaTable.select { MangaTable.id eq mangaId }.first())
|
||||
}
|
||||
|
||||
|
||||
@@ -9,18 +9,17 @@ import suwayomi.tachidesk.global.model.table.GlobalMetaTable
|
||||
import suwayomi.tachidesk.graphql.types.GlobalMetaType
|
||||
|
||||
class MetaMutation {
|
||||
|
||||
data class SetGlobalMetaInput(
|
||||
val clientMutationId: String? = null,
|
||||
val meta: GlobalMetaType
|
||||
val meta: GlobalMetaType,
|
||||
)
|
||||
|
||||
data class SetGlobalMetaPayload(
|
||||
val clientMutationId: String?,
|
||||
val meta: GlobalMetaType
|
||||
val meta: GlobalMetaType,
|
||||
)
|
||||
fun setGlobalMeta(
|
||||
input: SetGlobalMetaInput
|
||||
): SetGlobalMetaPayload {
|
||||
|
||||
fun setGlobalMeta(input: SetGlobalMetaInput): SetGlobalMetaPayload {
|
||||
val (clientMutationId, meta) = input
|
||||
|
||||
GlobalMeta.modifyMeta(meta.key, meta.value)
|
||||
@@ -30,19 +29,21 @@ class MetaMutation {
|
||||
|
||||
data class DeleteGlobalMetaInput(
|
||||
val clientMutationId: String? = null,
|
||||
val key: String
|
||||
val key: String,
|
||||
)
|
||||
|
||||
data class DeleteGlobalMetaPayload(
|
||||
val clientMutationId: String?,
|
||||
val meta: GlobalMetaType?
|
||||
val meta: GlobalMetaType?,
|
||||
)
|
||||
fun deleteGlobalMeta(
|
||||
input: DeleteGlobalMetaInput
|
||||
): DeleteGlobalMetaPayload {
|
||||
|
||||
fun deleteGlobalMeta(input: DeleteGlobalMetaInput): DeleteGlobalMetaPayload {
|
||||
val (clientMutationId, key) = input
|
||||
|
||||
val meta = transaction {
|
||||
val meta = GlobalMetaTable.select { GlobalMetaTable.key eq key }
|
||||
val meta =
|
||||
transaction {
|
||||
val meta =
|
||||
GlobalMetaTable.select { GlobalMetaTable.key eq key }
|
||||
.firstOrNull()
|
||||
|
||||
GlobalMetaTable.deleteWhere { GlobalMetaTable.key eq key }
|
||||
|
||||
@@ -11,53 +11,107 @@ import xyz.nulldev.ts.config.GlobalConfigManager
|
||||
class SettingsMutation {
|
||||
data class SetSettingsInput(
|
||||
val clientMutationId: String? = null,
|
||||
val settings: PartialSettingsType
|
||||
val settings: PartialSettingsType,
|
||||
)
|
||||
|
||||
data class SetSettingsPayload(
|
||||
val clientMutationId: String?,
|
||||
val settings: SettingsType
|
||||
val settings: SettingsType,
|
||||
)
|
||||
|
||||
private fun updateSettings(settings: Settings) {
|
||||
if (settings.ip != null) serverConfig.ip.value = settings.ip!!
|
||||
if (settings.port != null) serverConfig.port.value = settings.port!!
|
||||
|
||||
if (settings.socksProxyEnabled != null) serverConfig.socksProxyEnabled.value = settings.socksProxyEnabled!!
|
||||
if (settings.socksProxyHost != null) serverConfig.socksProxyHost.value = settings.socksProxyHost!!
|
||||
if (settings.socksProxyPort != null) serverConfig.socksProxyPort.value = settings.socksProxyPort!!
|
||||
if (settings.socksProxyEnabled != null) {
|
||||
serverConfig.socksProxyEnabled.value = settings.socksProxyEnabled!!
|
||||
}
|
||||
if (settings.socksProxyHost != null) {
|
||||
serverConfig.socksProxyHost.value = settings.socksProxyHost!!
|
||||
}
|
||||
if (settings.socksProxyPort != null) {
|
||||
serverConfig.socksProxyPort.value = settings.socksProxyPort!!
|
||||
}
|
||||
|
||||
if (settings.webUIFlavor != null) serverConfig.webUIFlavor.value = settings.webUIFlavor!!.uiName
|
||||
if (settings.initialOpenInBrowserEnabled != null) serverConfig.initialOpenInBrowserEnabled.value = settings.initialOpenInBrowserEnabled!!
|
||||
if (settings.webUIInterface != null) serverConfig.webUIInterface.value = settings.webUIInterface!!.name.lowercase()
|
||||
if (settings.electronPath != null) serverConfig.electronPath.value = settings.electronPath!!
|
||||
if (settings.webUIChannel != null) serverConfig.webUIChannel.value = settings.webUIChannel!!.name.lowercase()
|
||||
if (settings.webUIUpdateCheckInterval != null) serverConfig.webUIUpdateCheckInterval.value = settings.webUIUpdateCheckInterval!!
|
||||
if (settings.webUIFlavor != null) {
|
||||
serverConfig.webUIFlavor.value = settings.webUIFlavor!!.uiName
|
||||
}
|
||||
if (settings.initialOpenInBrowserEnabled != null) {
|
||||
serverConfig.initialOpenInBrowserEnabled.value = settings.initialOpenInBrowserEnabled!!
|
||||
}
|
||||
if (settings.webUIInterface != null) {
|
||||
serverConfig.webUIInterface.value = settings.webUIInterface!!.name.lowercase()
|
||||
}
|
||||
if (settings.electronPath != null) {
|
||||
serverConfig.electronPath.value = settings.electronPath!!
|
||||
}
|
||||
if (settings.webUIChannel != null) {
|
||||
serverConfig.webUIChannel.value = settings.webUIChannel!!.name.lowercase()
|
||||
}
|
||||
if (settings.webUIUpdateCheckInterval != null) {
|
||||
serverConfig.webUIUpdateCheckInterval.value = settings.webUIUpdateCheckInterval!!
|
||||
}
|
||||
|
||||
if (settings.downloadAsCbz != null) serverConfig.downloadAsCbz.value = settings.downloadAsCbz!!
|
||||
if (settings.downloadsPath != null) serverConfig.downloadsPath.value = settings.downloadsPath!!
|
||||
if (settings.autoDownloadNewChapters != null) serverConfig.autoDownloadNewChapters.value = settings.autoDownloadNewChapters!!
|
||||
if (settings.downloadAsCbz != null) {
|
||||
serverConfig.downloadAsCbz.value = settings.downloadAsCbz!!
|
||||
}
|
||||
if (settings.downloadsPath != null) {
|
||||
serverConfig.downloadsPath.value = settings.downloadsPath!!
|
||||
}
|
||||
if (settings.autoDownloadNewChapters != null) {
|
||||
serverConfig.autoDownloadNewChapters.value = settings.autoDownloadNewChapters!!
|
||||
}
|
||||
|
||||
if (settings.maxSourcesInParallel != null) serverConfig.maxSourcesInParallel.value = settings.maxSourcesInParallel!!
|
||||
if (settings.maxSourcesInParallel != null) {
|
||||
serverConfig.maxSourcesInParallel.value = settings.maxSourcesInParallel!!
|
||||
}
|
||||
|
||||
if (settings.excludeUnreadChapters != null) serverConfig.excludeUnreadChapters.value = settings.excludeUnreadChapters!!
|
||||
if (settings.excludeNotStarted != null) serverConfig.excludeNotStarted.value = settings.excludeNotStarted!!
|
||||
if (settings.excludeCompleted != null) serverConfig.excludeCompleted.value = settings.excludeCompleted!!
|
||||
if (settings.globalUpdateInterval != null) serverConfig.globalUpdateInterval.value = settings.globalUpdateInterval!!
|
||||
if (settings.excludeUnreadChapters != null) {
|
||||
serverConfig.excludeUnreadChapters.value = settings.excludeUnreadChapters!!
|
||||
}
|
||||
if (settings.excludeNotStarted != null) {
|
||||
serverConfig.excludeNotStarted.value = settings.excludeNotStarted!!
|
||||
}
|
||||
if (settings.excludeCompleted != null) {
|
||||
serverConfig.excludeCompleted.value = settings.excludeCompleted!!
|
||||
}
|
||||
if (settings.globalUpdateInterval != null) {
|
||||
serverConfig.globalUpdateInterval.value = settings.globalUpdateInterval!!
|
||||
}
|
||||
|
||||
if (settings.basicAuthEnabled != null) serverConfig.basicAuthEnabled.value = settings.basicAuthEnabled!!
|
||||
if (settings.basicAuthUsername != null) serverConfig.basicAuthUsername.value = settings.basicAuthUsername!!
|
||||
if (settings.basicAuthPassword != null) serverConfig.basicAuthPassword.value = settings.basicAuthPassword!!
|
||||
if (settings.basicAuthEnabled != null) {
|
||||
serverConfig.basicAuthEnabled.value = settings.basicAuthEnabled!!
|
||||
}
|
||||
if (settings.basicAuthUsername != null) {
|
||||
serverConfig.basicAuthUsername.value = settings.basicAuthUsername!!
|
||||
}
|
||||
if (settings.basicAuthPassword != null) {
|
||||
serverConfig.basicAuthPassword.value = settings.basicAuthPassword!!
|
||||
}
|
||||
|
||||
if (settings.debugLogsEnabled != null) serverConfig.debugLogsEnabled.value = settings.debugLogsEnabled!!
|
||||
if (settings.systemTrayEnabled != null) serverConfig.systemTrayEnabled.value = settings.systemTrayEnabled!!
|
||||
if (settings.debugLogsEnabled != null) {
|
||||
serverConfig.debugLogsEnabled.value = settings.debugLogsEnabled!!
|
||||
}
|
||||
if (settings.systemTrayEnabled != null) {
|
||||
serverConfig.systemTrayEnabled.value = settings.systemTrayEnabled!!
|
||||
}
|
||||
|
||||
if (settings.backupPath != null) serverConfig.backupPath.value = settings.backupPath!!
|
||||
if (settings.backupTime != null) serverConfig.backupTime.value = settings.backupTime!!
|
||||
if (settings.backupInterval != null) serverConfig.backupInterval.value = settings.backupInterval!!
|
||||
if (settings.backupTTL != null) serverConfig.backupTTL.value = settings.backupTTL!!
|
||||
if (settings.backupPath != null) {
|
||||
serverConfig.backupPath.value = settings.backupPath!!
|
||||
}
|
||||
if (settings.backupTime != null) {
|
||||
serverConfig.backupTime.value = settings.backupTime!!
|
||||
}
|
||||
if (settings.backupInterval != null) {
|
||||
serverConfig.backupInterval.value = settings.backupInterval!!
|
||||
}
|
||||
if (settings.backupTTL != null) {
|
||||
serverConfig.backupTTL.value = settings.backupTTL!!
|
||||
}
|
||||
|
||||
if (settings.localSourcePath != null) serverConfig.localSourcePath.value = settings.localSourcePath!!
|
||||
if (settings.localSourcePath != null) {
|
||||
serverConfig.localSourcePath.value = settings.localSourcePath!!
|
||||
}
|
||||
}
|
||||
|
||||
fun setSettings(input: SetSettingsInput): SetSettingsPayload {
|
||||
@@ -72,7 +126,7 @@ class SettingsMutation {
|
||||
|
||||
data class ResetSettingsPayload(
|
||||
val clientMutationId: String?,
|
||||
val settings: SettingsType
|
||||
val settings: SettingsType,
|
||||
)
|
||||
|
||||
fun resetSettings(input: ResetSettingsInput): ResetSettingsPayload {
|
||||
|
||||
@@ -20,39 +20,39 @@ import suwayomi.tachidesk.server.JavalinSetup.future
|
||||
import java.util.concurrent.CompletableFuture
|
||||
|
||||
class SourceMutation {
|
||||
|
||||
enum class FetchSourceMangaType {
|
||||
SEARCH,
|
||||
POPULAR,
|
||||
LATEST
|
||||
LATEST,
|
||||
}
|
||||
|
||||
data class FetchSourceMangaInput(
|
||||
val clientMutationId: String? = null,
|
||||
val source: Long,
|
||||
val type: FetchSourceMangaType,
|
||||
val page: Int,
|
||||
val query: String? = null,
|
||||
val filters: List<FilterChange>? = null
|
||||
val filters: List<FilterChange>? = null,
|
||||
)
|
||||
|
||||
data class FetchSourceMangaPayload(
|
||||
val clientMutationId: String?,
|
||||
val mangas: List<MangaType>,
|
||||
val hasNextPage: Boolean
|
||||
val hasNextPage: Boolean,
|
||||
)
|
||||
|
||||
fun fetchSourceManga(
|
||||
input: FetchSourceMangaInput
|
||||
): CompletableFuture<FetchSourceMangaPayload> {
|
||||
fun fetchSourceManga(input: FetchSourceMangaInput): CompletableFuture<FetchSourceMangaPayload> {
|
||||
val (clientMutationId, sourceId, type, page, query, filters) = input
|
||||
|
||||
return future {
|
||||
val source = GetCatalogueSource.getCatalogueSourceOrNull(sourceId)!!
|
||||
val mangasPage = when (type) {
|
||||
val mangasPage =
|
||||
when (type) {
|
||||
FetchSourceMangaType.SEARCH -> {
|
||||
source.getSearchManga(
|
||||
page = page,
|
||||
query = query.orEmpty(),
|
||||
filters = updateFilterList(source, filters)
|
||||
filters = updateFilterList(source, filters),
|
||||
)
|
||||
}
|
||||
FetchSourceMangaType.POPULAR -> {
|
||||
@@ -66,7 +66,8 @@ class SourceMutation {
|
||||
|
||||
val mangaIds = mangasPage.insertOrGet(sourceId)
|
||||
|
||||
val mangas = transaction {
|
||||
val mangas =
|
||||
transaction {
|
||||
MangaTable.select { MangaTable.id inList mangaIds }
|
||||
.map { MangaType(it) }
|
||||
}.sortedBy {
|
||||
@@ -76,7 +77,7 @@ class SourceMutation {
|
||||
FetchSourceMangaPayload(
|
||||
clientMutationId = clientMutationId,
|
||||
mangas = mangas,
|
||||
hasNextPage = mangasPage.hasNextPage
|
||||
hasNextPage = mangasPage.hasNextPage,
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -87,21 +88,21 @@ class SourceMutation {
|
||||
val checkBoxState: Boolean? = null,
|
||||
val editTextState: String? = null,
|
||||
val listState: String? = null,
|
||||
val multiSelectState: List<String>? = null
|
||||
val multiSelectState: List<String>? = null,
|
||||
)
|
||||
|
||||
data class UpdateSourcePreferenceInput(
|
||||
val clientMutationId: String? = null,
|
||||
val source: Long,
|
||||
val change: SourcePreferenceChange
|
||||
)
|
||||
data class UpdateSourcePreferencePayload(
|
||||
val clientMutationId: String?,
|
||||
val preferences: List<Preference>
|
||||
val change: SourcePreferenceChange,
|
||||
)
|
||||
|
||||
fun updateSourcePreference(
|
||||
input: UpdateSourcePreferenceInput
|
||||
): UpdateSourcePreferencePayload {
|
||||
data class UpdateSourcePreferencePayload(
|
||||
val clientMutationId: String?,
|
||||
val preferences: List<Preference>,
|
||||
)
|
||||
|
||||
fun updateSourcePreference(input: UpdateSourcePreferenceInput): UpdateSourcePreferencePayload {
|
||||
val (clientMutationId, sourceId, change) = input
|
||||
|
||||
Source.setSourcePreference(sourceId, change.position, "") { preference ->
|
||||
@@ -117,7 +118,7 @@ class SourceMutation {
|
||||
|
||||
return UpdateSourcePreferencePayload(
|
||||
clientMutationId = clientMutationId,
|
||||
preferences = Source.getSourcePreferencesRaw(sourceId).map { preferenceOf(it) }
|
||||
preferences = Source.getSourcePreferencesRaw(sourceId).map { preferenceOf(it) },
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user