mirror of
https://github.com/simplex-chat/simplex-chat.git
synced 2026-05-25 02:05:40 +00:00
android, desktop: handle situation when not all databases created before shut down (#4294)
* android, desktop: handle situation when not all databases created before shut down * more logic of choosing whether to delete databases or not * comment * rename * refactoring
This commit is contained in:
committed by
GitHub
parent
ddeaa1c7c3
commit
0d3928bd51
@@ -52,6 +52,7 @@ object ChatModel {
|
||||
val chatDbStatus = mutableStateOf<DBMigrationResult?>(null)
|
||||
val ctrlInitInProgress = mutableStateOf(false)
|
||||
val dbMigrationInProgress = mutableStateOf(false)
|
||||
val incompleteInitializedDbRemoved = mutableStateOf(false)
|
||||
val chats = mutableStateListOf<Chat>()
|
||||
// map of connections network statuses, key is agent connection id
|
||||
val networkStatuses = mutableStateMapOf<String, NetworkStatus>()
|
||||
|
||||
+7
@@ -176,6 +176,12 @@ class AppPreferences {
|
||||
val selfDestruct = mkBoolPreference(SHARED_PREFS_SELF_DESTRUCT, false)
|
||||
val selfDestructDisplayName = mkStrPreference(SHARED_PREFS_SELF_DESTRUCT_DISPLAY_NAME, null)
|
||||
|
||||
// This flag is set when database is first initialized and resets only when the database is removed.
|
||||
// This is needed for recover from incomplete initialization when only one database file is created.
|
||||
// If false - the app will clear database folder on missing file and re-initialize.
|
||||
// Note that this situation can only happen if passphrase for the first database is incorrect because, otherwise, backend will re-create second database automatically
|
||||
val newDatabaseInitialized = mkBoolPreference(SHARED_PREFS_NEW_DATABASE_INITIALIZED, false)
|
||||
|
||||
val currentTheme = mkStrPreference(SHARED_PREFS_CURRENT_THEME, DefaultTheme.SYSTEM_THEME_NAME)
|
||||
val systemDarkTheme = mkStrPreference(SHARED_PREFS_SYSTEM_DARK_THEME, DefaultTheme.SIMPLEX.themeName)
|
||||
val currentThemeIds = mkMapPreference(SHARED_PREFS_CURRENT_THEME_IDs, mapOf(), encode = {
|
||||
@@ -361,6 +367,7 @@ class AppPreferences {
|
||||
private const val SHARED_PREFS_ENCRYPTED_SELF_DESTRUCT_PASSPHRASE = "EncryptedSelfDestructPassphrase"
|
||||
private const val SHARED_PREFS_INITIALIZATION_VECTOR_SELF_DESTRUCT_PASSPHRASE = "InitializationVectorSelfDestructPassphrase"
|
||||
private const val SHARED_PREFS_ENCRYPTION_STARTED_AT = "EncryptionStartedAt"
|
||||
private const val SHARED_PREFS_NEW_DATABASE_INITIALIZED = "NewDatabaseInitialized"
|
||||
private const val SHARED_PREFS_CONFIRM_DB_UPGRADES = "ConfirmDBUpgrades"
|
||||
private const val SHARED_PREFS_SELF_DESTRUCT = "LocalAuthenticationSelfDestruct"
|
||||
private const val SHARED_PREFS_SELF_DESTRUCT_DISPLAY_NAME = "LocalAuthenticationSelfDestructDisplayName"
|
||||
|
||||
@@ -88,8 +88,23 @@ suspend fun initChatController(useKey: String? = null, confirmMigrations: Migrat
|
||||
chatModel.chatDbStatus.value = res
|
||||
if (res != DBMigrationResult.OK) {
|
||||
Log.d(TAG, "Unable to migrate successfully: $res")
|
||||
if (!appPrefs.newDatabaseInitialized.get() && DatabaseUtils.hasOnlyOneDatabase(dataDir.absolutePath)) {
|
||||
if (chatModel.incompleteInitializedDbRemoved.value) {
|
||||
Log.d(TAG, "Incomplete initialized databases were removed but after repeated migration only one database exists again, not trying to remove again")
|
||||
} else {
|
||||
val dbPath = dbAbsolutePrefixPath
|
||||
File(dbPath + "_chat.db").delete()
|
||||
File(dbPath + "_agent.db").delete()
|
||||
chatModel.incompleteInitializedDbRemoved.value = true
|
||||
Log.d(TAG, "Incomplete initialized databases were removed for the first time, repeating migration")
|
||||
chatModel.ctrlInitInProgress.value = false
|
||||
initChatController(useKey, confirmMigrations, startChat)
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
appPrefs.newDatabaseInitialized.set(true)
|
||||
chatModel.incompleteInitializedDbRemoved.value = false
|
||||
platform.androidRestartNetworkObserver()
|
||||
controller.apiSetAppFilePaths(
|
||||
appFilesDir.absolutePath,
|
||||
|
||||
+36
-7
@@ -22,8 +22,11 @@ import chat.simplex.common.platform.*
|
||||
import chat.simplex.common.ui.theme.*
|
||||
import chat.simplex.common.views.helpers.*
|
||||
import chat.simplex.common.views.usersettings.AppVersionText
|
||||
import chat.simplex.common.views.usersettings.SettingsActionItem
|
||||
import chat.simplex.res.MR
|
||||
import dev.icerock.moko.resources.StringResource
|
||||
import dev.icerock.moko.resources.compose.painterResource
|
||||
import dev.icerock.moko.resources.compose.stringResource
|
||||
import kotlinx.coroutines.*
|
||||
import kotlinx.datetime.Clock
|
||||
import java.io.File
|
||||
@@ -106,7 +109,7 @@ fun DatabaseErrorView(
|
||||
}
|
||||
}
|
||||
is DBMigrationResult.ErrorMigration -> when (val err = status.migrationError) {
|
||||
is MigrationError.Upgrade ->
|
||||
is MigrationError.Upgrade -> {
|
||||
DatabaseErrorDetails(MR.strings.database_upgrade) {
|
||||
TextButton({ callRunChat(confirmMigrations = MigrationConfirmation.YesUp) }, Modifier.align(Alignment.CenterHorizontally), enabled = !progressIndicator.value) {
|
||||
Text(generalGetString(MR.strings.upgrade_and_open_chat))
|
||||
@@ -116,7 +119,9 @@ fun DatabaseErrorView(
|
||||
MigrationsText(err.upMigrations.map { it.upName })
|
||||
AppVersionText()
|
||||
}
|
||||
is MigrationError.Downgrade ->
|
||||
OpenDatabaseDirectoryButton()
|
||||
}
|
||||
is MigrationError.Downgrade -> {
|
||||
DatabaseErrorDetails(MR.strings.database_downgrade) {
|
||||
TextButton({ callRunChat(confirmMigrations = MigrationConfirmation.YesUpDown) }, Modifier.align(Alignment.CenterHorizontally), enabled = !progressIndicator.value) {
|
||||
Text(generalGetString(MR.strings.downgrade_and_open_chat))
|
||||
@@ -127,29 +132,41 @@ fun DatabaseErrorView(
|
||||
MigrationsText(err.downMigrations)
|
||||
AppVersionText()
|
||||
}
|
||||
is MigrationError.Error ->
|
||||
OpenDatabaseDirectoryButton()
|
||||
}
|
||||
is MigrationError.Error -> {
|
||||
DatabaseErrorDetails(MR.strings.incompatible_database_version) {
|
||||
FileNameText(status.dbFile)
|
||||
Text(String.format(generalGetString(MR.strings.error_with_info), mtrErrorDescription(err.mtrError)))
|
||||
}
|
||||
OpenDatabaseDirectoryButton()
|
||||
}
|
||||
}
|
||||
is DBMigrationResult.ErrorSQL ->
|
||||
is DBMigrationResult.ErrorSQL -> {
|
||||
DatabaseErrorDetails(MR.strings.database_error) {
|
||||
FileNameText(status.dbFile)
|
||||
Text(String.format(generalGetString(MR.strings.error_with_info), status.migrationSQLError))
|
||||
}
|
||||
is DBMigrationResult.ErrorKeychain ->
|
||||
OpenDatabaseDirectoryButton()
|
||||
}
|
||||
is DBMigrationResult.ErrorKeychain -> {
|
||||
DatabaseErrorDetails(MR.strings.keychain_error) {
|
||||
Text(generalGetString(MR.strings.cannot_access_keychain))
|
||||
}
|
||||
is DBMigrationResult.InvalidConfirmation ->
|
||||
OpenDatabaseDirectoryButton()
|
||||
}
|
||||
is DBMigrationResult.InvalidConfirmation -> {
|
||||
DatabaseErrorDetails(MR.strings.invalid_migration_confirmation) {
|
||||
// this can only happen if incorrect parameter is passed
|
||||
}
|
||||
is DBMigrationResult.Unknown ->
|
||||
OpenDatabaseDirectoryButton()
|
||||
}
|
||||
is DBMigrationResult.Unknown -> {
|
||||
DatabaseErrorDetails(MR.strings.database_error) {
|
||||
Text(String.format(generalGetString(MR.strings.unknown_database_error_with_info), status.json))
|
||||
}
|
||||
OpenDatabaseDirectoryButton()
|
||||
}
|
||||
is DBMigrationResult.OK -> {}
|
||||
null -> {}
|
||||
}
|
||||
@@ -294,6 +311,18 @@ private fun ColumnScope.SaveAndOpenButton(enabled: Boolean, onClick: () -> Unit)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun OpenDatabaseDirectoryButton() {
|
||||
if (appPlatform.isDesktop) {
|
||||
Spacer(Modifier.padding(top = DEFAULT_PADDING))
|
||||
SettingsActionItem(
|
||||
painterResource(MR.images.ic_folder_open),
|
||||
stringResource(MR.strings.open_database_folder),
|
||||
::desktopOpenDatabaseDir
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun ColumnScope.OpenChatButton(enabled: Boolean, onClick: () -> Unit) {
|
||||
TextButton(onClick, Modifier.align(Alignment.CenterHorizontally), enabled = enabled) {
|
||||
|
||||
+2
@@ -18,6 +18,7 @@ import androidx.compose.ui.text.*
|
||||
import androidx.compose.ui.text.font.FontWeight
|
||||
import androidx.compose.ui.unit.dp
|
||||
import chat.simplex.common.model.*
|
||||
import chat.simplex.common.model.ChatController.appPrefs
|
||||
import chat.simplex.common.model.ChatModel.controller
|
||||
import chat.simplex.common.model.ChatModel.updatingChatsMutex
|
||||
import chat.simplex.common.ui.theme.*
|
||||
@@ -492,6 +493,7 @@ fun deleteChatDatabaseFilesAndState() {
|
||||
wallpapersDir.deleteRecursively()
|
||||
wallpapersDir.mkdirs()
|
||||
DatabaseUtils.ksDatabasePassword.remove()
|
||||
appPrefs.newDatabaseInitialized.set(false)
|
||||
controller.appPrefs.storeDBPassphrase.set(true)
|
||||
controller.ctrl = null
|
||||
|
||||
|
||||
+7
-4
@@ -39,22 +39,25 @@ object DatabaseUtils {
|
||||
}
|
||||
}
|
||||
|
||||
private fun hasDatabase(rootDir: String): Boolean =
|
||||
File(rootDir + File.separator + chatDatabaseFileName).exists() && File(rootDir + File.separator + agentDatabaseFileName).exists()
|
||||
private fun hasAtLeastOneDatabase(rootDir: String): Boolean =
|
||||
File(rootDir + File.separator + chatDatabaseFileName).exists() || File(rootDir + File.separator + agentDatabaseFileName).exists()
|
||||
|
||||
fun hasOnlyOneDatabase(rootDir: String): Boolean =
|
||||
File(rootDir + File.separator + chatDatabaseFileName).exists() != File(rootDir + File.separator + agentDatabaseFileName).exists()
|
||||
|
||||
fun useDatabaseKey(): String {
|
||||
Log.d(TAG, "useDatabaseKey ${appPreferences.storeDBPassphrase.get()}")
|
||||
var dbKey = ""
|
||||
val useKeychain = appPreferences.storeDBPassphrase.get()
|
||||
if (useKeychain) {
|
||||
if (!hasDatabase(dataDir.absolutePath)) {
|
||||
if (!hasAtLeastOneDatabase(dataDir.absolutePath)) {
|
||||
dbKey = randomDatabasePassword()
|
||||
ksDatabasePassword.set(dbKey)
|
||||
appPreferences.initialRandomDBPassphrase.set(true)
|
||||
} else {
|
||||
dbKey = ksDatabasePassword.get() ?: ""
|
||||
}
|
||||
} else if (appPlatform.isDesktop && !hasDatabase(dataDir.absolutePath)) {
|
||||
} else if (appPlatform.isDesktop && !hasAtLeastOneDatabase(dataDir.absolutePath)) {
|
||||
// In case of database was deleted by hand
|
||||
dbKey = randomDatabasePassword()
|
||||
ksDatabasePassword.set(dbKey)
|
||||
|
||||
Reference in New Issue
Block a user