Show failed migrations instead of waiting on them forever

This commit is contained in:
AntsyLich
2026-10-12 13:56:33 +06:00
parent a999e49f2f
commit 9dbb7148c5
6 changed files with 50 additions and 2 deletions
+1
View File
@@ -44,6 +44,7 @@ The format is a modified version of [Keep a Changelog](https://keepachangelog.co
- Fixed the app closing when WebView's page process crashed ([@AntsyLich](https://github.com/AntsyLich))
- Fixed installing or updating an extension sometimes cancelling the install of another one ([@AntsyLich](https://github.com/AntsyLich))
- Fixed updating from v0.20.x taking up to hours on libraries with many duplicate entries ([@AntsyLich](https://github.com/AntsyLich))
- Fixed the app staying on the updating screen for good when updating its data failed, it now shows what went wrong, and updating failing on leftover data of deleted entries ([@AntsyLich](https://github.com/AntsyLich))
## [v0.21.0] - 2026-10-12
### Added
@@ -179,6 +179,9 @@ class MainActivity : BaseActivity() {
Migrator.awaitAndRelease()
isAppMigrating.value = false
}
// Uncaught on purpose: a failed database migration otherwise leaves the migrating screen up for good,
// while the crash screen tells what went wrong and lets the logs be shared
lifecycleScope.launch { databaseRepository.awaitOpen() }
// Do not let the launcher create a new activity http://stackoverflow.com/questions/16283079
if (!isTaskRoot) {
@@ -1,9 +1,11 @@
package mihon.core.migration
import kotlinx.coroutines.CancellationException
import kotlinx.coroutines.CompletableDeferred
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Deferred
import kotlinx.coroutines.async
import logcat.LogPriority
import tachiyomi.core.common.util.system.logcat
class MigrationJobFactory(
@@ -20,7 +22,18 @@ class MigrationJobFactory(
}
async {
val prev = acc.await()
migration(migrationContext) || prev
// A failure would cancel the rest of the chain and leave the app waiting on it for good
val ran = try {
migration(migrationContext)
} catch (e: CancellationException) {
throw e
} catch (e: Exception) {
logcat(LogPriority.ERROR, e) {
"Migration failed: { name = ${migration::class.simpleName}, version = ${migration.version} }"
}
false
}
ran || prev
}
} else {
logcat {
@@ -1,16 +1,29 @@
package mihon.data.database
import app.cash.sqldelight.db.QueryResult
import app.cash.sqldelight.db.SqlDriver
import dev.zacsweers.metro.AppScope
import dev.zacsweers.metro.ContributesBinding
import dev.zacsweers.metro.Inject
import dev.zacsweers.metro.SingleIn
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.withContext
import mihon.domain.database.repository.DatabaseRepository
@Inject
@SingleIn(AppScope::class)
@ContributesBinding(AppScope::class)
class DatabaseRepositoryImpl : DatabaseRepository {
class DatabaseRepositoryImpl(
// Lazy since the driver reports its migration state here
private val driver: Lazy<SqlDriver>,
) : DatabaseRepository {
override val isMigrating = MutableStateFlow(false)
override suspend fun awaitOpen() {
withContext(Dispatchers.IO) {
driver.value.executeQuery(null, "SELECT 1", { QueryResult.Value(Unit) }, 0).await()
}
}
}
@@ -4,6 +4,19 @@ import kotlin.String;
import kotlin.collections.List;
import kotlinx.serialization.json.JsonObject;
-- ---------------------------------------------------------------------------------------------------------
-- orphans
-- ---------------------------------------------------------------------------------------------------------
-- Rows pointing at an entry, chapter or category that no longer exists were written while foreign keys weren't
-- enforced. Nothing can reach them, and copied over they'd fail the foreign key check that ends the migration.
DELETE FROM chapters WHERE manga_id NOT IN (SELECT _id FROM mangas);
DELETE FROM history WHERE chapter_id NOT IN (SELECT _id FROM chapters);
DELETE FROM mangas_categories
WHERE manga_id NOT IN (SELECT _id FROM mangas) OR category_id NOT IN (SELECT _id FROM categories);
DELETE FROM manga_sync WHERE manga_id NOT IN (SELECT _id FROM mangas);
DELETE FROM excluded_scanlators WHERE manga_id NOT IN (SELECT _id FROM mangas);
-- ---------------------------------------------------------------------------------------------------------
-- table - manga
-- ---------------------------------------------------------------------------------------------------------
@@ -9,4 +9,9 @@ interface DatabaseRepository {
* app update.
*/
val isMigrating: StateFlow<Boolean>
/**
* Opens the database, migrating it if needed, and throws what made the migration fail.
*/
suspend fun awaitOpen()
}