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:
Mitchell Syer
2023-10-06 23:38:39 -04:00
committed by GitHub
parent 3cd3cb0186
commit 849acfca3d
277 changed files with 6709 additions and 5090 deletions

11
.editorconfig Normal file
View 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

View File

@@ -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

View File

@@ -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 {

View File

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

View File

@@ -5,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 }
}

View File

@@ -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())
}

View File

@@ -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) {

View File

@@ -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)

View File

@@ -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!")

View File

@@ -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 {

View File

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

View File

@@ -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) {

View File

@@ -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",
)
}
}

View File

@@ -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
}

View File

@@ -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") }
}
}

View File

@@ -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") }
}
}

View File

@@ -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") }
}
}

View File

@@ -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()
}

View File

@@ -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

View File

@@ -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)
}

View File

@@ -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()) {

View File

@@ -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

View File

@@ -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)
}

View File

@@ -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()

View File

@@ -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())!!

View File

@@ -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")

View File

@@ -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"))
}
}
}

View File

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

View File

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

View File

@@ -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"}

View File

@@ -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",
)
}
}

View File

@@ -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())

View File

@@ -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.

View File

@@ -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)

View File

@@ -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
}

View File

@@ -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)

View File

@@ -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)

View File

@@ -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)

View File

@@ -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)
}

View File

@@ -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

View File

@@ -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,
)
}

View File

@@ -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

View File

@@ -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)

View File

@@ -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" }

View File

@@ -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

View File

@@ -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))

View File

@@ -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)

View File

@@ -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")
}

View File

@@ -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)
}

View File

@@ -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()
}

View File

@@ -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

View File

@@ -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))
}

View File

@@ -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) {

View File

@@ -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
}
}

View File

@@ -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)

View File

@@ -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))
}

View File

@@ -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> {

View File

@@ -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) {

View File

@@ -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)

View File

@@ -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> {

View File

@@ -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 {

View File

@@ -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,
)

View File

@@ -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 {

View File

@@ -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())
}

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -2,5 +2,5 @@ package eu.kanade.tachiyomi.source.model
enum class UpdateStrategy {
ALWAYS_UPDATE,
ONLY_FETCH_ONCE
ONLY_FETCH_ONCE,
}

View File

@@ -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.

View File

@@ -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

View File

@@ -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.
*

View File

@@ -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
}

View File

@@ -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()
}

View File

@@ -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")

View File

@@ -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
}

View File

@@ -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

View File

@@ -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)
}
},
)
}

View File

@@ -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)
}
},
)
}

View File

@@ -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,
)
}
}

View File

@@ -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,
),
)
}
}

View File

@@ -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()

View File

@@ -23,7 +23,7 @@ object GraphQLController {
ctx.future(
future {
server.execute(ctx)
}
},
)
}

View File

@@ -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 }

View File

@@ -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 }

View File

@@ -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))

View File

@@ -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()

View File

@@ -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() }

View File

@@ -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 }

View File

@@ -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",
)
}
}

View File

@@ -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,
)
}
}

View File

@@ -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),
)
}
}

View File

@@ -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?)

View File

@@ -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),
)
}
}

View File

@@ -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 },
)
}
}

View File

@@ -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())
}

View File

@@ -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 }

View File

@@ -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 {

View File

@@ -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