mirror of
https://github.com/mihonapp/mihon.git
synced 2026-10-16 13:48:09 -07:00
Show failed migrations instead of waiting on them forever
This commit is contained in:
@@ -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 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 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 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
|
## [v0.21.0] - 2026-10-12
|
||||||
### Added
|
### Added
|
||||||
|
|||||||
@@ -179,6 +179,9 @@ class MainActivity : BaseActivity() {
|
|||||||
Migrator.awaitAndRelease()
|
Migrator.awaitAndRelease()
|
||||||
isAppMigrating.value = false
|
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
|
// Do not let the launcher create a new activity http://stackoverflow.com/questions/16283079
|
||||||
if (!isTaskRoot) {
|
if (!isTaskRoot) {
|
||||||
|
|||||||
@@ -1,9 +1,11 @@
|
|||||||
package mihon.core.migration
|
package mihon.core.migration
|
||||||
|
|
||||||
|
import kotlinx.coroutines.CancellationException
|
||||||
import kotlinx.coroutines.CompletableDeferred
|
import kotlinx.coroutines.CompletableDeferred
|
||||||
import kotlinx.coroutines.CoroutineScope
|
import kotlinx.coroutines.CoroutineScope
|
||||||
import kotlinx.coroutines.Deferred
|
import kotlinx.coroutines.Deferred
|
||||||
import kotlinx.coroutines.async
|
import kotlinx.coroutines.async
|
||||||
|
import logcat.LogPriority
|
||||||
import tachiyomi.core.common.util.system.logcat
|
import tachiyomi.core.common.util.system.logcat
|
||||||
|
|
||||||
class MigrationJobFactory(
|
class MigrationJobFactory(
|
||||||
@@ -20,7 +22,18 @@ class MigrationJobFactory(
|
|||||||
}
|
}
|
||||||
async {
|
async {
|
||||||
val prev = acc.await()
|
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 {
|
} else {
|
||||||
logcat {
|
logcat {
|
||||||
|
|||||||
@@ -1,16 +1,29 @@
|
|||||||
package mihon.data.database
|
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.AppScope
|
||||||
import dev.zacsweers.metro.ContributesBinding
|
import dev.zacsweers.metro.ContributesBinding
|
||||||
import dev.zacsweers.metro.Inject
|
import dev.zacsweers.metro.Inject
|
||||||
import dev.zacsweers.metro.SingleIn
|
import dev.zacsweers.metro.SingleIn
|
||||||
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.flow.MutableStateFlow
|
import kotlinx.coroutines.flow.MutableStateFlow
|
||||||
|
import kotlinx.coroutines.withContext
|
||||||
import mihon.domain.database.repository.DatabaseRepository
|
import mihon.domain.database.repository.DatabaseRepository
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
@SingleIn(AppScope::class)
|
@SingleIn(AppScope::class)
|
||||||
@ContributesBinding(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 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 kotlin.collections.List;
|
||||||
import kotlinx.serialization.json.JsonObject;
|
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
|
-- table - manga
|
||||||
-- ---------------------------------------------------------------------------------------------------------
|
-- ---------------------------------------------------------------------------------------------------------
|
||||||
|
|||||||
@@ -9,4 +9,9 @@ interface DatabaseRepository {
|
|||||||
* app update.
|
* app update.
|
||||||
*/
|
*/
|
||||||
val isMigrating: StateFlow<Boolean>
|
val isMigrating: StateFlow<Boolean>
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Opens the database, migrating it if needed, and throws what made the migration fail.
|
||||||
|
*/
|
||||||
|
suspend fun awaitOpen()
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user