[#1496] Image conversion (#1505)

* [#1496] First conversion attempt

* [#1496] Configurable conversion

* Fix: allow nested configs (map)

* [#1496] Support explicit `none` conversion

* Use MimeUtils for provided download

* [1496] Support image conversion on load for downloaded images

* Lint

* [#1496] Support conversion on fresh download as well

Previous commit was only for already downloaded images, now also for
fresh and cached

* [#1496] Refactor: Move where conversion for download happens

* Rewrite config handling, improve custom types

* Lint

* Add format to pages mutation

* Lint

* Standardize url encode

* Lint

* Config: Allow additional conversion parameters

* Implement conversion quality parameter

* Lint

* Implement a conversion util to allow fallback readers

* Add downloadConversions to api and backup, fix updateValue issues

* Lint

* Minor cleanup

* Update libs.versions.toml

---------

Co-authored-by: Syer10 <syer10@users.noreply.github.com>
This commit is contained in:
Constantin Piber
2025-07-14 23:51:18 +02:00
committed by GitHub
parent 09c950a890
commit df0078b725
24 changed files with 464 additions and 167 deletions

View File

@@ -10,10 +10,11 @@ package xyz.nulldev.ts.config
import ch.qos.logback.classic.Level
import com.typesafe.config.Config
import com.typesafe.config.ConfigFactory
import com.typesafe.config.ConfigObject
import com.typesafe.config.ConfigValue
import com.typesafe.config.ConfigValueFactory
import com.typesafe.config.parser.ConfigDocument
import com.typesafe.config.parser.ConfigDocumentFactory
import io.github.config4k.toConfig
import io.github.oshai.kotlinlogging.KotlinLogging
import kotlinx.coroutines.sync.Mutex
import kotlinx.coroutines.sync.withLock
@@ -113,7 +114,7 @@ open class ConfigManager {
) {
mutex.withLock {
val actualValue = if (value is Enum<*>) value.name else value
val configValue = ConfigValueFactory.fromAnyRef(actualValue)
val configValue = actualValue.toConfig("internal").getValue("internal")
updateUserConfigFile(path, configValue)
internalConfig = internalConfig.withValue(path, configValue)
@@ -142,8 +143,13 @@ open class ConfigManager {
val serverConfig = ConfigFactory.parseResources("server-reference.conf")
val userConfig = getUserConfig()
val hasMissingSettings = serverConfig.entrySet().any { !userConfig.hasPath(it.key) }
val hasOutdatedSettings = userConfig.entrySet().any { !serverConfig.hasPath(it.key) }
// NOTE: if more than 1 dot is included, that's a nested setting, which we need to filter out here
val refKeys =
serverConfig.root().entries.flatMap {
(it.value as? ConfigObject)?.entries?.map { e -> "${it.key}.${e.key}" }.orEmpty()
}
val hasMissingSettings = refKeys.any { !userConfig.hasPath(it) }
val hasOutdatedSettings = userConfig.entrySet().any { !refKeys.contains(it.key) && it.key.count { c -> c == '.' } <= 1 }
val isUserConfigOutdated = hasMissingSettings || hasOutdatedSettings
if (!isUserConfigOutdated) {
return
@@ -159,7 +165,8 @@ open class ConfigManager {
.filter {
serverConfig.hasPath(
it.key,
)
) ||
it.key.count { c -> c == '.' } > 1
}.forEach { newUserConfigDoc = newUserConfigDoc.withValue(it.key, it.value) }
newUserConfigDoc =

View File

@@ -8,9 +8,13 @@ package xyz.nulldev.ts.config
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
import com.typesafe.config.Config
import com.typesafe.config.ConfigException
import com.typesafe.config.ConfigFactory
import com.typesafe.config.ConfigValueFactory
import io.github.config4k.ClassContainer
import io.github.config4k.TypeReference
import io.github.config4k.getValue
import io.github.config4k.readers.SelectReader
import io.github.config4k.toConfig
import kotlin.reflect.KProperty
/**
@@ -26,7 +30,7 @@ abstract class ConfigModule(
*/
abstract class SystemPropertyOverridableConfigModule(
getConfig: () -> Config,
moduleName: String,
val moduleName: String,
) : ConfigModule(getConfig) {
val overridableConfig = SystemPropertyOverrideDelegate(getConfig, moduleName)
}
@@ -46,20 +50,31 @@ class SystemPropertyOverrideDelegate(
val combined =
System.getProperty(
"$CONFIG_PREFIX.$moduleName.${property.name}",
if (T::class.simpleName == "List") {
ConfigValueFactory.fromAnyRef(configValue).render()
} else {
configValue.toString()
},
configValue!!
.toConfig("internal")
.root()
.render()
.removePrefix("internal="),
)
val combinedConfig =
try {
ConfigFactory.parseString(combined)
} catch (_: ConfigException) {
ConfigFactory.parseString("internal=$combined")
}
return when (T::class.simpleName) {
"Int" -> combined.toInt()
"Boolean" -> combined.toBoolean()
"Double" -> combined.toDouble()
"List" -> ConfigFactory.parseString("internal=" + combined).getStringList("internal").orEmpty()
// add more types as needed
else -> combined // covers String
} as T
val genericType = object : TypeReference<T>() {}.genericType()
val clazz = ClassContainer(T::class, genericType)
val reader = SelectReader.getReader(clazz)
val path = property.name
val result = reader(combinedConfig, "internal")
return try {
result as T
} catch (e: Exception) {
throw result
?.let { e }
?: ConfigException.BadPath(path, "take a look at your config")
}
}
}

View File

@@ -47,5 +47,5 @@ dependencies {
// OpenJDK lacks native JPEG encoder and native WEBP decoder
implementation(libs.bundles.twelvemonkeys)
implementation(libs.sejda.webp)
implementation(libs.imageio.webp)
}