Compare commits

..

10 Commits

Author SHA1 Message Date
Constantin Piber
16a14e6ac2 Pin CEF version to one known to work with KCEF (#2027)
Fixes problems like
```
java.lang.ClassNotFoundException: org.cef.callback.CefResourceReadCallback_N
```
and
```
Exception in thread "Thread-584" java.lang.NoSuchMethodError: open
```
2026-05-14 11:45:30 -04:00
Constantin Piber
a2f29ec9dc Reset update-flag on uninstall (#2025)
* Reset update-flag on uninstall

If there is an update available when the extension is uninstalled, the
table will still have the update flag, which makes no sense if it is not
installed.

Example:
```
{
  "pkgName": "eu.kanade.tachiyomi.extension.en.comix",
  "name": "Comix",
  "lang": "en",
  "versionCode": 20,
  "versionName": "1.4.20",
  "iconUrl": "/api/v1/extension/icon/tachiyomi-en.comix-v1.4.20.apk",
  "repo": "<hidden>",
  "isNsfw": true,
  "isInstalled": false,
  "isObsolete": false,
  "hasUpdate": true,
  "__typename": "ExtensionType"
},
```

* Update changelog
2026-05-14 11:44:59 -04:00
Mitchell Syer
82df985201 Crash on startup if an unrecoverable error happens (#2019)
* Crash on startup if an unrecoverable error happens

* Changelog
2026-05-14 11:44:52 -04:00
renovate[bot]
740db4f1ab Update javalin to v7.2.2 (#2026)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-05-14 11:44:34 -04:00
renovate[bot]
c4711dec00 Update dependency com.github.junrar:junrar to v7.6.0 (#2022)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-05-14 11:44:18 -04:00
renovate[bot]
75d8d172aa Update dependency org.slf4j:slf4j-api to v2.0.18 (#2017)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-05-14 11:44:00 -04:00
renovate[bot]
81fb8c395d Update Gradle to v9.5.1 (#2015)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-05-14 11:43:46 -04:00
Mitchell Syer
e93efa9627 Fix Database Types as Needed (#2020) 2026-05-12 19:59:06 -04:00
Mitchell Syer
03a95e6652 Fix New Databases (#2016)
* Standardize toSqlName

* Rename Meta Key db field since KEY is now a reserved name in H2

* Changelog entry

* Use toSqlName

* Forgot this key

* Catch any exception
2026-05-12 17:22:35 -04:00
renovate[bot]
c117d380a3 Update exposed to v1 (major) (#1868)
* Update exposed to v1

* Update Exposed

* Add Kotlinx.DateTime extensions

* Update H2

* Review comments

---------

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: Syer10 <syer10@users.noreply.github.com>
2026-05-12 12:53:41 -04:00
20 changed files with 205 additions and 87 deletions

View File

@@ -67,7 +67,7 @@ jobs:
export LD_PRELOAD="$(pwd)/scripts/resources/catch_abort.so" export LD_PRELOAD="$(pwd)/scripts/resources/catch_abort.so"
JAR=$(ls ./server/build/*.jar| head -1) JAR=$(ls ./server/build/*.jar| head -1)
set +e set +e
timeout 30s java -DcrashOnFailedMigration=true \ timeout 30s java \
-Dsuwayomi.tachidesk.config.server.systemTrayEnabled=false \ -Dsuwayomi.tachidesk.config.server.systemTrayEnabled=false \
-Dsuwayomi.tachidesk.config.server.initialOpenInBrowserEnabled=false \ -Dsuwayomi.tachidesk.config.server.initialOpenInBrowserEnabled=false \
-Dsuwayomi.tachidesk.config.server.databaseType=POSTGRESQL \ -Dsuwayomi.tachidesk.config.server.databaseType=POSTGRESQL \
@@ -83,7 +83,7 @@ jobs:
exit "$ecode" exit "$ecode"
fi fi
timeout 30s java -DcrashOnFailedMigration=true \ timeout 30s java \
-Dsuwayomi.tachidesk.config.server.systemTrayEnabled=false \ -Dsuwayomi.tachidesk.config.server.systemTrayEnabled=false \
-Dsuwayomi.tachidesk.config.server.initialOpenInBrowserEnabled=false \ -Dsuwayomi.tachidesk.config.server.initialOpenInBrowserEnabled=false \
-jar "$JAR" -jar "$JAR"

View File

@@ -10,13 +10,15 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).
- . - .
### Changed ### Changed
- . - (Database/H2) Use the latest H2 database engine
- (Startup) Crash on startup if an unrecoverable error happens
### Fixed ### Fixed
- (CloudFlareInterceptor) Don't send the `cf_clearance` cookie back to Flaresolverr - (CloudFlareInterceptor) Don't send the `cf_clearance` cookie back to Flaresolverr
- (WebUI) Handle serving non-default webui with "bundled" - (WebUI) Handle serving non-default webui with "bundled"
- (WebUI) Wait until WebUI is ready to open in browser - (WebUI) Wait until WebUI is ready to open in browser
- (Downloads) Truncate filenames by byte length to prevent "File name too long" IO errors - (Downloads) Truncate filenames by byte length to prevent "File name too long" IO errors
- (Extension) Do not indicate an update is available when the extension is not installed
## [v2.2.2100] + [WebUI: v20260508.01] - 2026-05-08 ## [v2.2.2100] + [WebUI: v20260508.01] - 2026-05-08

View File

@@ -4,7 +4,7 @@ coroutines = "1.11.0"
serialization = "1.11.0" serialization = "1.11.0"
jvmTarget = "21" jvmTarget = "21"
okhttp = "5.3.2" # Major version is locked by Tachiyomi extensions okhttp = "5.3.2" # Major version is locked by Tachiyomi extensions
javalin = "7.2.0" javalin = "7.2.2"
jte = "3.2.4" jte = "3.2.4"
jackson = "3.1.3" # jackson version locked by javalin, ref: `io.javalin.core.util.OptionalDependency` jackson = "3.1.3" # jackson version locked by javalin, ref: `io.javalin.core.util.OptionalDependency`
exposed = "1.2.0" exposed = "1.2.0"
@@ -37,7 +37,7 @@ serialization-xml-core = { module = "io.github.pdvrieze.xmlutil:core", version.r
serialization-xml = { module = "io.github.pdvrieze.xmlutil:serialization-jvm", version.ref = "xmlserialization" } serialization-xml = { module = "io.github.pdvrieze.xmlutil:serialization-jvm", version.ref = "xmlserialization" }
# Logging # Logging
slf4japi = "org.slf4j:slf4j-api:2.0.17" slf4japi = "org.slf4j:slf4j-api:2.0.18"
logback = "ch.qos.logback:logback-classic:1.5.32" logback = "ch.qos.logback:logback-classic:1.5.32"
kotlinlogging = "io.github.oshai:kotlin-logging-jvm:8.0.02" kotlinlogging = "io.github.oshai:kotlin-logging-jvm:8.0.02"
@@ -116,7 +116,7 @@ appdirs = "ca.gosyer:kotlin-multiplatform-appdirs:2.0.0"
cache4k = "io.github.reactivecircus.cache4k:cache4k:0.14.0" cache4k = "io.github.reactivecircus.cache4k:cache4k:0.14.0"
zip4j = "net.lingala.zip4j:zip4j:2.11.6" zip4j = "net.lingala.zip4j:zip4j:2.11.6"
commonscompress = "org.apache.commons:commons-compress:1.28.0" commonscompress = "org.apache.commons:commons-compress:1.28.0"
junrar = "com.github.junrar:junrar:7.5.10" junrar = "com.github.junrar:junrar:7.6.0"
# AES/CBC/PKCS7Padding Cypher provider # AES/CBC/PKCS7Padding Cypher provider
bouncycastle = "org.bouncycastle:bcprov-jdk18on:1.84" bouncycastle = "org.bouncycastle:bcprov-jdk18on:1.84"

View File

@@ -1,6 +1,6 @@
distributionBase=GRADLE_USER_HOME distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-9.5.0-bin.zip distributionUrl=https\://services.gradle.org/distributions/gradle-9.5.1-bin.zip
networkTimeout=10000 networkTimeout=10000
retries=0 retries=0
retryBackOffMs=500 retryBackOffMs=500

View File

@@ -13,6 +13,6 @@ import org.jetbrains.exposed.v1.core.dao.id.IntIdTable
* Metadata storage for clients, server/global level. * Metadata storage for clients, server/global level.
*/ */
object GlobalMetaTable : IntIdTable() { object GlobalMetaTable : IntIdTable() {
val key = varchar("key", 256) val key = varchar("meta_key", 256)
val value = varchar("value", 4096) val value = varchar("value", 4096)
} }

View File

@@ -358,6 +358,7 @@ object Extension {
} else { } else {
ExtensionTable.update({ ExtensionTable.pkgName eq pkgName }) { ExtensionTable.update({ ExtensionTable.pkgName eq pkgName }) {
it[isInstalled] = false it[isInstalled] = false
it[hasUpdate] = false
} }
} }

View File

@@ -15,7 +15,7 @@ import suwayomi.tachidesk.manga.model.table.CategoryMetaTable.ref
* Metadata storage for clients, about Category with id == [ref]. * Metadata storage for clients, about Category with id == [ref].
*/ */
object CategoryMetaTable : IntIdTable() { object CategoryMetaTable : IntIdTable() {
val key = varchar("key", 256) val key = varchar("meta_key", 256)
val value = varchar("value", 4096) val value = varchar("value", 4096)
val ref = reference("category_ref", CategoryTable, ReferenceOption.CASCADE) val ref = reference("category_ref", CategoryTable, ReferenceOption.CASCADE)
} }

View File

@@ -26,7 +26,7 @@ import suwayomi.tachidesk.manga.model.table.ChapterMetaTable.ref
* } * }
*/ */
object ChapterMetaTable : IntIdTable() { object ChapterMetaTable : IntIdTable() {
val key = varchar("key", 256) val key = varchar("meta_key", 256)
val value = varchar("value", 4096) val value = varchar("value", 4096)
val ref = reference("chapter_ref", ChapterTable, ReferenceOption.CASCADE) val ref = reference("chapter_ref", ChapterTable, ReferenceOption.CASCADE)
} }

View File

@@ -26,7 +26,7 @@ import suwayomi.tachidesk.manga.model.table.MangaMetaTable.ref
* } * }
*/ */
object MangaMetaTable : IntIdTable() { object MangaMetaTable : IntIdTable() {
val key = varchar("key", 256) val key = varchar("meta_key", 256)
val value = varchar("value", 4096) val value = varchar("value", 4096)
val ref = reference("manga_ref", MangaTable, ReferenceOption.CASCADE) val ref = reference("manga_ref", MangaTable, ReferenceOption.CASCADE)
} }

View File

@@ -14,7 +14,7 @@ import suwayomi.tachidesk.manga.model.table.SourceMetaTable.ref
* Metadata storage for clients, about Source with id == [ref]. * Metadata storage for clients, about Source with id == [ref].
*/ */
object SourceMetaTable : IntIdTable() { object SourceMetaTable : IntIdTable() {
val key = varchar("key", 256) val key = varchar("meta_key", 256)
val value = varchar("value", 4096) val value = varchar("value", 4096)
val ref = long("source_ref") val ref = long("source_ref")
} }

View File

@@ -5,6 +5,8 @@ import android.content.Context
import io.github.oshai.kotlinlogging.KotlinLogging import io.github.oshai.kotlinlogging.KotlinLogging
import suwayomi.tachidesk.manga.impl.update.IUpdater import suwayomi.tachidesk.manga.impl.update.IUpdater
import suwayomi.tachidesk.server.database.H2Migration import suwayomi.tachidesk.server.database.H2Migration
import suwayomi.tachidesk.server.util.ExitCode
import suwayomi.tachidesk.server.util.shutdownApp
import uy.kohesive.injekt.Injekt import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get import uy.kohesive.injekt.api.get
import java.io.File import java.io.File
@@ -99,31 +101,35 @@ private val MIGRATIONS =
fun runMigrations(applicationDirs: ApplicationDirs) { fun runMigrations(applicationDirs: ApplicationDirs) {
val logger = KotlinLogging.logger("Migration") val logger = KotlinLogging.logger("Migration")
try {
val migrationPreferences =
Injekt
.get<Application>()
.getSharedPreferences(
"migrations",
Context.MODE_PRIVATE,
)
val version = migrationPreferences.getInt("version", 0)
val migrationPreferences = logger.info { "Running migrations, previous version $version, target version ${MIGRATIONS.size}" }
Injekt
.get<Application>()
.getSharedPreferences(
"migrations",
Context.MODE_PRIVATE,
)
val version = migrationPreferences.getInt("version", 0)
logger.info { "Running migrations, previous version $version, target version ${MIGRATIONS.size}" } MIGRATIONS.forEachIndexed { index, (migrationName, migrationFunction) ->
val migrationVersion = index + 1
MIGRATIONS.forEachIndexed { index, (migrationName, migrationFunction) -> val isMigrationRequired = version < migrationVersion
val migrationVersion = index + 1 if (!isMigrationRequired) {
logger.info { "Skipping migration version $migrationVersion: $migrationName" }
return@forEachIndexed
}
val isMigrationRequired = version < migrationVersion logger.info { "Running migration version $migrationVersion: $migrationName" }
if (!isMigrationRequired) {
logger.info { "Skipping migration version $migrationVersion: $migrationName" } migrationFunction(applicationDirs)
return@forEachIndexed
migrationPreferences.edit().putInt("version", migrationVersion).apply()
} }
} catch (e: Exception) {
logger.info { "Running migration version $migrationVersion: $migrationName" } logger.error(e) { "Failed to run migrations" }
shutdownApp(ExitCode.MigrationsRunFailure)
migrationFunction(applicationDirs)
migrationPreferences.edit().putInt("version", migrationVersion).apply()
} }
} }

View File

@@ -366,6 +366,7 @@ fun applicationSetup() {
} }
} catch (e: Exception) { } catch (e: Exception) {
logger.error(e) { "Exception while creating initial server.conf" } logger.error(e) { "Exception while creating initial server.conf" }
shutdownApp(ExitCode.SetupConfFileFailed)
} }
// copy local source icon // copy local source icon
@@ -378,6 +379,7 @@ fun applicationSetup() {
} }
} catch (e: Exception) { } catch (e: Exception) {
logger.error(e) { "Exception while copying Local source's icon" } logger.error(e) { "Exception while copying Local source's icon" }
shutdownApp(ExitCode.LocalSourceIconCopyFailure)
} }
// fixes #119 , ref: // fixes #119 , ref:
@@ -395,7 +397,12 @@ fun applicationSetup() {
databaseUp() databaseUp()
LocalSource.register() try {
LocalSource.register()
} catch (e: Exception) {
logger.error(e) { "Failed to setup LocalSource" }
shutdownApp(ExitCode.LocalSourceSetupFailure)
}
serverConfig.subscribeTo( serverConfig.subscribeTo(
combine<Any, DatabaseSettings>( combine<Any, DatabaseSettings>(
@@ -525,7 +532,7 @@ fun applicationSetup() {
} }
} }
} }
download { github() } download { github { release("jbr-release-21.0.10b1163.108") } }
settings { settings {
windowlessRenderingEnabled = true windowlessRenderingEnabled = true
cachePath = (Path(applicationDirs.dataRoot) / "cache/kcef").toString() cachePath = (Path(applicationDirs.dataRoot) / "cache/kcef").toString()

View File

@@ -27,7 +27,6 @@ import suwayomi.tachidesk.server.util.ExitCode
import suwayomi.tachidesk.server.util.shutdownApp import suwayomi.tachidesk.server.util.shutdownApp
import uy.kohesive.injekt.Injekt import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get import uy.kohesive.injekt.api.get
import java.sql.SQLException
import kotlin.time.Duration.Companion.minutes import kotlin.time.Duration.Companion.minutes
import kotlin.time.Duration.Companion.seconds import kotlin.time.Duration.Companion.seconds
@@ -184,10 +183,8 @@ fun databaseUp() {
} }
val migrations = loadMigrationsFrom("suwayomi.tachidesk.server.database.migration", ServerConfig::class.java) val migrations = loadMigrationsFrom("suwayomi.tachidesk.server.database.migration", ServerConfig::class.java)
runMigrations(migrations) runMigrations(migrations)
} catch (e: SQLException) { } catch (e: Exception) {
logger.error(e) { "Error up-to-database migration" } logger.error(e) { "Error up-to-database migration" }
if (System.getProperty("crashOnFailedMigration").toBoolean()) { shutdownApp(ExitCode.DbMigrationFailure)
shutdownApp(ExitCode.DbMigrationFailure)
}
} }
} }

View File

@@ -9,9 +9,12 @@ import java.net.URLClassLoader
import java.nio.file.Path import java.nio.file.Path
import kotlin.io.path.Path import kotlin.io.path.Path
import kotlin.io.path.absolutePathString import kotlin.io.path.absolutePathString
import kotlin.io.path.bufferedReader
import kotlin.io.path.bufferedWriter
import kotlin.io.path.copyTo import kotlin.io.path.copyTo
import kotlin.io.path.createDirectories import kotlin.io.path.createDirectories
import kotlin.io.path.deleteExisting import kotlin.io.path.deleteExisting
import kotlin.io.path.deleteIfExists
import kotlin.io.path.div import kotlin.io.path.div
import kotlin.io.path.exists import kotlin.io.path.exists
import kotlin.io.path.name import kotlin.io.path.name
@@ -46,6 +49,10 @@ object H2Migration {
} }
val script = Path("$dbBase.${h2Old.substringAfterLast('.')}.sql") val script = Path("$dbBase.${h2Old.substringAfterLast('.')}.sql")
script.deleteIfExists()
val modifiedScript = Path("$dbBase.${h2Old.substringAfterLast('.')}.modified.sql")
modifiedScript.deleteIfExists()
// Backup original database. // Backup original database.
val backup = Path("$dbBase.mv.db.${h2Old.substringAfterLast('.')}.backup") val backup = Path("$dbBase.mv.db.${h2Old.substringAfterLast('.')}.backup")
@@ -72,19 +79,32 @@ object H2Migration {
libsDir.resolve("h2-$h2New.bin"), libsDir.resolve("h2-$h2New.bin"),
) )
// Delete attempted migration if failed previously
val newDatabase = Path(rootDir, "database.${h2New.substringAfterLast('.')}.mv.db")
newDatabase.deleteIfExists()
val modifiedNewDatabase = Path(rootDir, "database.${h2Old.substringAfterLast('.')}.modified.${h2New.substringAfterLast('.')}.mv.db")
modifiedNewDatabase.deleteIfExists()
runMigrationTool( runMigrationTool(
migrationJar = migrationJar, migrationJar = migrationJar,
libsDir = libsDir, libsDir = libsDir,
mvStore = mvStore, mvStore = mvStore,
script = script, script = script,
modifiedScript = modifiedScript,
h2Old = h2Old, h2Old = h2Old,
h2New = h2New, h2New = h2New,
) )
// Move database to proper path // Move database to proper path
val newDatabase = Path(rootDir, "database.${h2New.substringAfterLast('.')}.mv.db") if (modifiedNewDatabase.exists()) {
newDatabase.copyTo(mvStore, overwrite = true) modifiedNewDatabase.copyTo(mvStore, overwrite = true)
newDatabase.deleteExisting() modifiedNewDatabase.deleteExisting()
newDatabase.deleteIfExists()
} else {
newDatabase.copyTo(mvStore, overwrite = true)
newDatabase.deleteExisting()
}
logger.info { "H2 migration completed successfully." } logger.info { "H2 migration completed successfully." }
} }
@@ -123,6 +143,7 @@ object H2Migration {
libsDir: Path, libsDir: Path,
mvStore: Path, mvStore: Path,
script: Path, script: Path,
modifiedScript: Path,
h2Old: String, h2Old: String,
h2New: String, h2New: String,
) { ) {
@@ -136,32 +157,77 @@ object H2Migration {
val main = val main =
clazz.getMethod("main", Array<String>::class.java) clazz.getMethod("main", Array<String>::class.java)
main.invoke( try {
null, main.invoke(
arrayOf( null,
// h2 driver dir arrayOf(
"-l", // h2 driver dir
libsDir.absolutePathString(), "-l",
// from version libsDir.absolutePathString(),
"-f", // from version
h2Old, "-f",
// to version h2Old,
"-t", // to version
h2New, "-t",
// user h2New,
"-u", // user
"", "-u",
// password "",
"-p", // password
"", "-p",
// database.mv.db "",
"-d", // database.mv.db
mvStore.absolutePathString(), "-d",
// database backup in SQL mvStore.absolutePathString(),
"-s", // database backup in SQL
script.absolutePathString(), "-s",
), script.absolutePathString(),
) ),
)
} catch (e: Exception) {
// Modify raw .sql file as needed for compatibility
if (e.stackTraceToString().contains("Unknown data type: \"DATETIME\"; SQL statement:") && script.exists()) {
script.bufferedReader().use { reader ->
modifiedScript.bufferedWriter().use { writer ->
reader.forEachLine { line ->
writer.write(
line.replace(
" \"EXECUTED_AT\" DATETIME(9) NOT NULL",
" \"EXECUTED_AT\" TIMESTAMP(9) NOT NULL",
),
)
writer.newLine()
}
}
}
main.invoke(
null,
arrayOf(
// h2 driver dir
"-l",
libsDir.absolutePathString(),
// from version
"-f",
h2Old,
// to version
"-t",
h2New,
// user
"-u",
"",
// password
"-p",
"",
// database.mv.db
"-d",
modifiedScript.absolutePathString(),
),
)
} else {
throw e
}
}
} }
} }
} }

View File

@@ -10,17 +10,10 @@ package suwayomi.tachidesk.server.database.migration
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */ * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
import de.neonew.exposed.migrations.helpers.SQLMigration import de.neonew.exposed.migrations.helpers.SQLMigration
import org.jetbrains.exposed.v1.jdbc.transactions.TransactionManager import suwayomi.tachidesk.server.database.migration.helpers.toSqlName
@Suppress("ClassName", "unused") @Suppress("ClassName", "unused")
class M0023_CategoryMetaRefFix : SQLMigration() { class M0023_CategoryMetaRefFix : SQLMigration() {
fun String.toSqlName(): String =
TransactionManager.defaultDatabase!!.identifierManager.let {
it.quoteIfNecessary(
it.inProperCase(this),
)
}
private val CategoryMetaTable by lazy { "CategoryMeta".toSqlName() } private val CategoryMetaTable by lazy { "CategoryMeta".toSqlName() }
private val CategoryRefColumn by lazy { "category_ref".toSqlName() } private val CategoryRefColumn by lazy { "category_ref".toSqlName() }
private val CategoryTable by lazy { "Category".toSqlName() } private val CategoryTable by lazy { "Category".toSqlName() }

View File

@@ -8,6 +8,7 @@ package suwayomi.tachidesk.server.database.migration
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */ * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
import de.neonew.exposed.migrations.helpers.SQLMigration import de.neonew.exposed.migrations.helpers.SQLMigration
import suwayomi.tachidesk.server.database.migration.helpers.toSqlName
@Suppress("ClassName", "unused") @Suppress("ClassName", "unused")
class M0049_FixDuplicatedMetas : SQLMigration() { class M0049_FixDuplicatedMetas : SQLMigration() {
@@ -15,7 +16,7 @@ class M0049_FixDuplicatedMetas : SQLMigration() {
table: String, table: String,
refColumn: String? = null, refColumn: String? = null,
): String { ): String {
val groupBy = listOfNotNull(refColumn, "KEY").joinToString(", ") val groupBy = listOfNotNull(refColumn, "KEY".toSqlName()).joinToString(", ")
return """ return """
DELETE FROM $table DELETE FROM $table
@@ -30,10 +31,11 @@ class M0049_FixDuplicatedMetas : SQLMigration() {
""".trimIndent() """.trimIndent()
} }
override val sql: String = override val sql: String by lazy {
createMigrationForTable("CATEGORYMETA", "CATEGORY_REF") + createMigrationForTable("CATEGORYMETA", "CATEGORY_REF") +
createMigrationForTable("CHAPTERMETA", "CHAPTER_REF") + createMigrationForTable("CHAPTERMETA", "CHAPTER_REF") +
createMigrationForTable("GLOBALMETA") + createMigrationForTable("GLOBALMETA") +
createMigrationForTable("MANGAMETA", "MANGA_REF") + createMigrationForTable("MANGAMETA", "MANGA_REF") +
createMigrationForTable("SOURCEMETA", "SOURCE_REF") createMigrationForTable("SOURCEMETA", "SOURCE_REF")
}
} }

View File

@@ -0,0 +1,38 @@
package suwayomi.tachidesk.server.database.migration
/*
* Copyright (C) Contributors to the Suwayomi project
*
* This Source Code Form is subject to the terms of the Mozilla Public
* 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 de.neonew.exposed.migrations.helpers.SQLMigration
import suwayomi.tachidesk.graphql.types.DatabaseType
import suwayomi.tachidesk.server.database.migration.helpers.toSqlName
import suwayomi.tachidesk.server.serverConfig
@Suppress("ClassName", "unused")
class M0055_RenameMetaKeys : SQLMigration() {
fun postgresRename(table: String): String =
"ALTER TABLE $table " +
"RENAME COLUMN " + "KEY".toSqlName() + " TO META_KEY;"
fun h2Rename(table: String): String =
"ALTER TABLE $table " +
"ALTER COLUMN " + "KEY".toSqlName() + " RENAME TO META_KEY;"
fun createRenameMigration(table: String): String =
when (serverConfig.databaseType.value) {
DatabaseType.H2 -> h2Rename(table.toSqlName())
DatabaseType.POSTGRESQL -> postgresRename(table.toSqlName())
}
override val sql: String by lazy {
createRenameMigration("CATEGORYMETA") +
createRenameMigration("CHAPTERMETA") +
createRenameMigration("GLOBALMETA") +
createRenameMigration("MANGAMETA") +
createRenameMigration("SOURCEMETA")
}
}

View File

@@ -1,17 +1,9 @@
package suwayomi.tachidesk.server.database.migration.helpers package suwayomi.tachidesk.server.database.migration.helpers
import de.neonew.exposed.migrations.helpers.SQLMigration import de.neonew.exposed.migrations.helpers.SQLMigration
import org.jetbrains.exposed.v1.jdbc.transactions.TransactionManager
import suwayomi.tachidesk.graphql.types.DatabaseType import suwayomi.tachidesk.graphql.types.DatabaseType
import suwayomi.tachidesk.server.serverConfig import suwayomi.tachidesk.server.serverConfig
fun String.toSqlName(): String =
TransactionManager.current().db.identifierManager.let {
it.quoteIfNecessary(
it.inProperCase(this),
)
}
abstract class RenameFieldMigration( abstract class RenameFieldMigration(
tableName: String, tableName: String,
originalName: String, originalName: String,

View File

@@ -0,0 +1,10 @@
package suwayomi.tachidesk.server.database.migration.helpers
import org.jetbrains.exposed.v1.jdbc.transactions.TransactionManager
fun String.toSqlName(): String =
TransactionManager.current().db.identifierManager.let {
it.quoteIfNecessary(
it.inProperCase(this),
)
}

View File

@@ -21,6 +21,10 @@ enum class ExitCode(
WebUISetupFailure(3), WebUISetupFailure(3),
ConfigMigrationMisconfiguredFailure(4), ConfigMigrationMisconfiguredFailure(4),
DbMigrationFailure(5), DbMigrationFailure(5),
SetupConfFileFailed(6),
LocalSourceIconCopyFailure(7),
LocalSourceSetupFailure(8),
MigrationsRunFailure(9),
} }
fun shutdownApp(exitCode: ExitCode) { fun shutdownApp(exitCode: ExitCode) {