Migrator improvements (#588)
(cherry picked from commit 0265c16eb239518d52b7e9fb4200b5b003418d5d) # Conflicts: # app/build.gradle.kts # app/src/main/java/eu/kanade/tachiyomi/ui/main/MainActivity.kt
This commit is contained in:
@@ -5,6 +5,9 @@ interface Migration {
|
||||
|
||||
suspend operator fun invoke(migrationContext: MigrationContext): Boolean
|
||||
|
||||
val isAlways: Boolean
|
||||
get() = version == ALWAYS
|
||||
|
||||
companion object {
|
||||
const val ALWAYS = -1f
|
||||
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
package mihon.core.migration
|
||||
|
||||
typealias MigrationCompletedListener = () -> Unit
|
||||
@@ -2,7 +2,7 @@ package mihon.core.migration
|
||||
|
||||
import uy.kohesive.injekt.Injekt
|
||||
|
||||
class MigrationContext {
|
||||
class MigrationContext(val dryrun: Boolean) {
|
||||
|
||||
inline fun <reified T> get(): T? {
|
||||
return Injekt.getInstanceOrNull(T::class.java)
|
||||
|
||||
@@ -0,0 +1,30 @@
|
||||
package mihon.core.migration
|
||||
|
||||
import kotlinx.coroutines.CompletableDeferred
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Deferred
|
||||
import kotlinx.coroutines.async
|
||||
import tachiyomi.core.common.util.system.logcat
|
||||
|
||||
class MigrationJobFactory(
|
||||
private val migrationContext: MigrationContext,
|
||||
private val scope: CoroutineScope
|
||||
) {
|
||||
|
||||
@SuppressWarnings("MaxLineLength")
|
||||
fun create(migrations: List<Migration>): Deferred<Boolean> = with(scope) {
|
||||
return migrations.sortedBy { it.version }
|
||||
.fold(CompletableDeferred(true)) { acc: Deferred<Boolean>, migration: Migration ->
|
||||
if (!migrationContext.dryrun) {
|
||||
logcat { "Running migration: { name = ${migration::class.simpleName}, version = ${migration.version} }" }
|
||||
async {
|
||||
val prev = acc.await()
|
||||
migration(migrationContext) || prev
|
||||
}
|
||||
} else {
|
||||
logcat { "(Dry-run) Running migration: { name = ${migration::class.simpleName}, version = ${migration.version} }" }
|
||||
CompletableDeferred(true)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
package mihon.core.migration
|
||||
|
||||
import kotlinx.coroutines.CompletableDeferred
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Deferred
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
interface MigrationStrategy {
|
||||
operator fun invoke(migrations: List<Migration>): Deferred<Boolean>
|
||||
}
|
||||
|
||||
class DefaultMigrationStrategy(
|
||||
private val migrationJobFactory: MigrationJobFactory,
|
||||
private val migrationCompletedListener: MigrationCompletedListener,
|
||||
private val scope: CoroutineScope
|
||||
) : MigrationStrategy {
|
||||
|
||||
override operator fun invoke(migrations: List<Migration>): Deferred<Boolean> = with(scope) {
|
||||
if (migrations.isEmpty()) {
|
||||
return@with CompletableDeferred(false)
|
||||
}
|
||||
|
||||
val chain = migrationJobFactory.create(migrations)
|
||||
|
||||
launch {
|
||||
if (chain.await()) migrationCompletedListener()
|
||||
}.start()
|
||||
|
||||
chain
|
||||
}
|
||||
}
|
||||
|
||||
class InitialMigrationStrategy(private val strategy: DefaultMigrationStrategy) : MigrationStrategy {
|
||||
|
||||
override operator fun invoke(migrations: List<Migration>): Deferred<Boolean> {
|
||||
return strategy(migrations.filter { it.isAlways })
|
||||
}
|
||||
}
|
||||
|
||||
class NoopMigrationStrategy(val state: Boolean) : MigrationStrategy {
|
||||
|
||||
override fun invoke(migrations: List<Migration>): Deferred<Boolean> {
|
||||
return CompletableDeferred(state)
|
||||
}
|
||||
}
|
||||
|
||||
class VersionRangeMigrationStrategy(
|
||||
private val versions: IntRange,
|
||||
private val strategy: DefaultMigrationStrategy
|
||||
) : MigrationStrategy {
|
||||
|
||||
override operator fun invoke(migrations: List<Migration>): Deferred<Boolean> {
|
||||
return strategy(migrations.filter { it.isAlways || it.version.toInt() in versions })
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
package mihon.core.migration
|
||||
|
||||
class MigrationStrategyFactory(
|
||||
private val factory: MigrationJobFactory,
|
||||
private val migrationCompletedListener: MigrationCompletedListener,
|
||||
) {
|
||||
|
||||
fun create(old: Int, new: Int): MigrationStrategy {
|
||||
val versions = (old + 1)..new
|
||||
val strategy = when {
|
||||
old == 0 -> InitialMigrationStrategy(
|
||||
strategy = DefaultMigrationStrategy(factory, migrationCompletedListener, Migrator.scope),
|
||||
)
|
||||
|
||||
old >= new -> NoopMigrationStrategy(false)
|
||||
else -> VersionRangeMigrationStrategy(
|
||||
versions = versions,
|
||||
strategy = DefaultMigrationStrategy(factory, migrationCompletedListener, Migrator.scope),
|
||||
)
|
||||
}
|
||||
return strategy
|
||||
}
|
||||
}
|
||||
@@ -1,53 +1,41 @@
|
||||
package mihon.core.migration
|
||||
|
||||
import kotlinx.coroutines.CompletableDeferred
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Deferred
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.Job
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import tachiyomi.core.common.util.system.logcat
|
||||
|
||||
object Migrator {
|
||||
|
||||
@SuppressWarnings("ReturnCount")
|
||||
fun migrate(
|
||||
private var result: Deferred<Boolean>? = null
|
||||
val scope = CoroutineScope(Dispatchers.Main + Job())
|
||||
|
||||
fun initialize(
|
||||
old: Int,
|
||||
new: Int,
|
||||
migrations: List<Migration>,
|
||||
dryrun: Boolean = false,
|
||||
onMigrationComplete: () -> Unit
|
||||
): Boolean {
|
||||
val migrationContext = MigrationContext()
|
||||
|
||||
if (old == 0) {
|
||||
return migrationContext.migrate(
|
||||
migrations = migrations.filter { it.isAlways() },
|
||||
dryrun = dryrun
|
||||
)
|
||||
.also { onMigrationComplete() }
|
||||
}
|
||||
|
||||
if (old >= new) {
|
||||
return false
|
||||
}
|
||||
|
||||
return migrationContext.migrate(
|
||||
migrations = migrations.filter { it.isAlways() || it.version.toInt() in (old + 1)..new },
|
||||
dryrun = dryrun
|
||||
)
|
||||
.also { onMigrationComplete() }
|
||||
) {
|
||||
val migrationContext = MigrationContext(dryrun)
|
||||
val migrationJobFactory = MigrationJobFactory(migrationContext, scope)
|
||||
val migrationStrategyFactory = MigrationStrategyFactory(migrationJobFactory, onMigrationComplete)
|
||||
val strategy = migrationStrategyFactory.create(old, new)
|
||||
result = strategy(migrations)
|
||||
}
|
||||
|
||||
private fun Migration.isAlways() = version == Migration.ALWAYS
|
||||
suspend fun await(): Boolean {
|
||||
val result = result ?: CompletableDeferred(false)
|
||||
return result.await()
|
||||
}
|
||||
|
||||
@SuppressWarnings("MaxLineLength")
|
||||
private fun MigrationContext.migrate(migrations: List<Migration>, dryrun: Boolean): Boolean {
|
||||
return migrations.sortedBy { it.version }
|
||||
.map { migration ->
|
||||
if (!dryrun) {
|
||||
logcat { "Running migration: { name = ${migration::class.simpleName}, version = ${migration.version} }" }
|
||||
runBlocking { migration(this@migrate) }
|
||||
} else {
|
||||
logcat { "(Dry-run) Running migration: { name = ${migration::class.simpleName}, version = ${migration.version} }" }
|
||||
true
|
||||
}
|
||||
}
|
||||
.reduce { acc, b -> acc || b }
|
||||
fun release() {
|
||||
result = null
|
||||
}
|
||||
|
||||
fun awaitAndRelease(): Boolean = runBlocking {
|
||||
await().also { release() }
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user