move migration lib outside of Tachidesk

This commit is contained in:
Aria Moradi
2021-08-05 22:53:31 +04:30
parent d05c447fe4
commit c69b954ffd
17 changed files with 40 additions and 241 deletions
+5
View File
@@ -44,11 +44,16 @@ dependencies {
// current database driver
implementation("com.h2database:h2:1.4.200")
// Exposed Migrations
val exposedMigrationsVersion = "5611dbec5a"
implementation("com.github.Suwayomi:exposed-migrations:$exposedMigrationsVersion")
// tray icon
implementation("com.dorkbox:SystemTray:4.1")
implementation("com.dorkbox:Utilities:1.9")
// dependencies of Tachiyomi extensions, some are duplicate, keeping it here for reference
implementation("com.github.inorichi.injekt:injekt-core:65b0440")
implementation("com.squareup.okhttp3:okhttp:4.9.1")
@@ -13,7 +13,6 @@ import org.kodein.di.DI
import org.kodein.di.bind
import org.kodein.di.conf.global
import org.kodein.di.singleton
import suwayomi.tachidesk.server.BuildConfig
import suwayomi.tachidesk.server.database.databaseUp
import suwayomi.tachidesk.server.util.AppMutex.handleAppMutex
import suwayomi.tachidesk.server.util.SystemTray.systemTray
@@ -7,13 +7,14 @@ package suwayomi.tachidesk.server.database
* 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.loadMigrationsFrom
import de.neonew.exposed.migrations.runMigrations
import org.jetbrains.exposed.sql.Database
import org.kodein.di.DI
import org.kodein.di.conf.global
import org.kodein.di.instance
import suwayomi.tachidesk.server.ApplicationDirs
import suwayomi.tachidesk.server.database.migration.lib.loadMigrationsFrom
import suwayomi.tachidesk.server.database.migration.lib.runMigrations
import suwayomi.tachidesk.server.ServerConfig
object DBManager {
val db by lazy {
@@ -27,6 +28,6 @@ fun databaseUp() {
val db = DBManager.db
db.useNestedTransactions = true
val migrations = loadMigrationsFrom("suwayomi.tachidesk.server.database.migration")
val migrations = loadMigrationsFrom("suwayomi.tachidesk.server.database.migration", ServerConfig::class.java)
runMigrations(migrations)
}
@@ -7,12 +7,12 @@ package suwayomi.tachidesk.server.database.migration
* 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.Migration
import eu.kanade.tachiyomi.source.model.SManga
import org.jetbrains.exposed.dao.id.IdTable
import org.jetbrains.exposed.dao.id.IntIdTable
import org.jetbrains.exposed.sql.SchemaUtils
import org.jetbrains.exposed.sql.transactions.transaction
import suwayomi.tachidesk.server.database.migration.lib.Migration
@Suppress("ClassName", "unused")
class M0001_Initial : Migration() {
@@ -7,9 +7,9 @@ package suwayomi.tachidesk.server.database.migration
* 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.Migration
import org.jetbrains.exposed.sql.transactions.TransactionManager
import org.jetbrains.exposed.sql.vendors.currentDialect
import suwayomi.tachidesk.server.database.migration.lib.Migration
@Suppress("ClassName", "unused")
class M0002_ChapterTableIndexRename : Migration() {
@@ -7,9 +7,9 @@ package suwayomi.tachidesk.server.database.migration
* 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.Migration
import org.jetbrains.exposed.sql.transactions.TransactionManager
import org.jetbrains.exposed.sql.vendors.currentDialect
import suwayomi.tachidesk.server.database.migration.lib.Migration
@Suppress("ClassName", "unused")
class M0003_DefaultCategory : Migration() {
@@ -1,17 +1,17 @@
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.Migration
import org.jetbrains.exposed.dao.id.IdTable
import org.jetbrains.exposed.dao.id.IntIdTable
import org.jetbrains.exposed.sql.SchemaUtils
import org.jetbrains.exposed.sql.transactions.transaction
import suwayomi.tachidesk.server.database.migration.lib.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/. */
class M0004_AnimeTablesBatch1 : Migration() {
private class AnimeExtensionTable : IntIdTable() {
@@ -1,17 +1,17 @@
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.Migration
import eu.kanade.tachiyomi.animesource.model.SAnime
import org.jetbrains.exposed.dao.id.IntIdTable
import org.jetbrains.exposed.sql.SchemaUtils
import org.jetbrains.exposed.sql.transactions.transaction
import suwayomi.tachidesk.server.database.migration.lib.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/. */
class M0005_AnimeTablesBatch2 : Migration() {
private class AnimeTable : IntIdTable() {
@@ -1,17 +1,17 @@
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.Migration
import org.jetbrains.exposed.dao.id.IntIdTable
import org.jetbrains.exposed.sql.SchemaUtils
import org.jetbrains.exposed.sql.transactions.transaction
import suwayomi.tachidesk.anime.model.table.AnimeTable
import suwayomi.tachidesk.server.database.migration.lib.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/. */
class M0006_AnimeTablesBatch3 : Migration() {
private class EpisodeTable : IntIdTable() {
@@ -7,9 +7,9 @@ package suwayomi.tachidesk.server.database.migration
* 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.Migration
import org.jetbrains.exposed.sql.transactions.TransactionManager
import org.jetbrains.exposed.sql.vendors.currentDialect
import suwayomi.tachidesk.server.database.migration.lib.Migration
@Suppress("ClassName", "unused")
class M0007_ChapterIsDownloaded : Migration() {
@@ -7,9 +7,9 @@ package suwayomi.tachidesk.server.database.migration
* 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.Migration
import org.jetbrains.exposed.sql.transactions.TransactionManager
import org.jetbrains.exposed.sql.vendors.currentDialect
import suwayomi.tachidesk.server.database.migration.lib.Migration
@Suppress("ClassName", "unused")
class M0008_ChapterPageCount : Migration() {
@@ -7,9 +7,9 @@ package suwayomi.tachidesk.server.database.migration
* 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.Migration
import org.jetbrains.exposed.sql.transactions.TransactionManager
import org.jetbrains.exposed.sql.vendors.currentDialect
import suwayomi.tachidesk.server.database.migration.lib.Migration
@Suppress("ClassName", "unused")
class M0009_ChapterLastReadAt : Migration() {
@@ -1,12 +1,12 @@
package suwayomi.tachidesk.server.database.migration
import de.neonew.exposed.migrations.Migration
import org.jetbrains.exposed.dao.id.IntIdTable
import org.jetbrains.exposed.sql.ReferenceOption
import org.jetbrains.exposed.sql.SchemaUtils
import org.jetbrains.exposed.sql.transactions.transaction
import suwayomi.tachidesk.manga.model.table.ChapterTable
import suwayomi.tachidesk.manga.model.table.MangaTable
import suwayomi.tachidesk.server.database.migration.lib.Migration
/*
* Copyright (C) Contributors to the Suwayomi project
@@ -1,21 +0,0 @@
MIT License
Copyright (c) 2020 Andreas Mausch
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
@@ -1,25 +0,0 @@
package suwayomi.tachidesk.server.database.migration.lib
/*
* 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/. */
// originally licenced under MIT by Andreas Mausch, Changes are licenced under Mozilla Public License, v. 2.0.
// adopted from: https://gitlab.com/andreas-mausch/exposed-migrations/-/tree/4bf853c18a24d0170eda896ddbb899cb01233595
abstract class Migration {
val name: String
val version: Int
init {
val groups = Regex("^M(\\d+)_(.*)$").matchEntire(this::class.simpleName!!)?.groupValues
?: throw IllegalArgumentException("Migration class name doesn't match convention")
version = groups[1].toInt()
name = groups[2]
}
abstract fun run()
}
@@ -1,37 +0,0 @@
package suwayomi.tachidesk.server.database.migration.lib
/*
* 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/. */
// originally licenced under MIT by Andreas Mausch, Changes are licenced under Mozilla Public License, v. 2.0.
// adopted from: https://gitlab.com/andreas-mausch/exposed-migrations/-/tree/4bf853c18a24d0170eda896ddbb899cb01233595
import org.jetbrains.exposed.dao.IntEntity
import org.jetbrains.exposed.dao.IntEntityClass
import org.jetbrains.exposed.dao.id.EntityID
import org.jetbrains.exposed.dao.id.IdTable
import org.jetbrains.exposed.sql.`java-time`.timestamp
object MigrationsTable : IdTable<Int>() {
override val id = integer("version").entityId()
override val primaryKey = PrimaryKey(id)
val name = varchar("name", length = 400)
val executedAt = timestamp("executed_at")
init {
index(true, name)
}
}
class MigrationEntity(id: EntityID<Int>) : IntEntity(id) {
companion object : IntEntityClass<MigrationEntity>(MigrationsTable)
var version by MigrationsTable.id
var name by MigrationsTable.name
var executedAt by MigrationsTable.executedAt
}
@@ -1,123 +0,0 @@
package suwayomi.tachidesk.server.database.migration.lib
/*
* 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/. */
// originally licenced under MIT by Andreas Mausch, Changes are licenced under Mozilla Public License, v. 2.0.
// adopted from: https://gitlab.com/andreas-mausch/exposed-migrations/-/tree/4bf853c18a24d0170eda896ddbb899cb01233595
import mu.KotlinLogging
import org.jetbrains.exposed.dao.id.EntityID
import org.jetbrains.exposed.sql.Database
import org.jetbrains.exposed.sql.SchemaUtils.create
import org.jetbrains.exposed.sql.exists
import org.jetbrains.exposed.sql.transactions.TransactionManager
import org.jetbrains.exposed.sql.transactions.transaction
import suwayomi.tachidesk.server.ServerConfig
import java.nio.file.FileSystems
import java.nio.file.Files
import java.nio.file.Paths
import java.time.Clock
import java.time.Instant.now
import kotlin.io.path.ExperimentalPathApi
import kotlin.io.path.isDirectory
import kotlin.io.path.name
import kotlin.streams.toList
private val logger = KotlinLogging.logger {}
fun runMigrations(migrations: List<Migration>, database: Database = TransactionManager.defaultDatabase!!, clock: Clock = Clock.systemUTC()) {
checkVersions(migrations)
logger.info { "Running migrations on database ${database.url}" }
val latestVersion = transaction(database) {
createTableIfNotExists(database)
MigrationEntity.all().maxByOrNull { it.version }?.version?.value
}
logger.info { "Database version before migrations: $latestVersion" }
migrations
.sortedBy { it.version }
.filter { shouldRun(latestVersion, it) }
.forEach {
logger.info { "Running migration version ${it.version}: ${it.name}" }
transaction(database) {
it.run()
MigrationEntity.new {
version = EntityID(it.version, MigrationsTable)
name = it.name
executedAt = now(clock)
}
}
}
logger.info { "Migrations finished successfully" }
}
@OptIn(ExperimentalPathApi::class)
private fun getTopLevelClasses(packageName: String): List<Class<*>> {
ServerConfig::class.java.getResource("/" + packageName.replace('.', '/'))
val path = "/" + packageName.replace('.', '/')
val uri = ServerConfig::class.java.getResource(path).toURI()
return when (uri.scheme) {
"jar" -> {
val fileSystem = FileSystems.newFileSystem(uri, emptyMap<String, Any>())
fileSystem.getPath(path)
}
else -> Paths.get(uri)
}.let { Files.walk(it, 1) }
.toList()
.filterNot { it.isDirectory() || it.name.contains('$') } // '$' means it's not a top level class
.filter { it.name.endsWith(".class") }
.map { Class.forName("$packageName.${it.name.substringBefore(".class")}") }
}
@Suppress("UnstableApiUsage")
fun loadMigrationsFrom(packageName: String): List<Migration> {
return getTopLevelClasses(packageName)
.map {
logger.debug("found Migration class ${it.name}")
val clazz = it.getDeclaredConstructor().newInstance()
if (clazz is Migration)
clazz
else
throw RuntimeException("found a class that's not a Migration")
}
}
private fun checkVersions(migrations: List<Migration>) {
val sorted = migrations.map { it.version }.sorted()
if ((1..migrations.size).toList() != sorted) {
throw IllegalStateException("List of migrations version is not consecutive: $sorted")
}
}
private fun createTableIfNotExists(database: Database) {
if (MigrationsTable.exists()) {
return
}
val tableNames = database.dialect.allTablesNames()
when (tableNames.isEmpty()) {
true -> {
logger.info { "Empty database found, creating table for migrations" }
create(MigrationsTable)
}
false -> throw IllegalStateException("Tried to run migrations against a non-empty database without a Migrations table. This is not supported.")
}
}
private fun shouldRun(latestVersion: Int?, migration: Migration): Boolean {
val run = latestVersion?.let { migration.version > it } ?: true
if (!run) {
logger.debug { "Skipping migration version ${migration.version}: ${migration.name}" }
}
return run
}