2022-09-07 12:49:41 +01:00
|
|
|
//
|
|
|
|
// DatabaseErrorView.swift
|
|
|
|
// SimpleX (iOS)
|
|
|
|
//
|
|
|
|
// Created by Evgeny on 04/09/2022.
|
|
|
|
// Copyright © 2022 SimpleX Chat. All rights reserved.
|
|
|
|
//
|
|
|
|
|
|
|
|
import SwiftUI
|
|
|
|
import SimpleXChat
|
|
|
|
|
|
|
|
struct DatabaseErrorView: View {
|
|
|
|
@EnvironmentObject var m: ChatModel
|
2024-11-27 20:32:18 +00:00
|
|
|
@EnvironmentObject var theme: AppTheme
|
2022-09-08 17:36:16 +01:00
|
|
|
@State var status: DBMigrationResult
|
2022-09-07 12:49:41 +01:00
|
|
|
@State private var dbKey = ""
|
2023-04-12 12:22:55 +02:00
|
|
|
@State private var storedDBKey = kcDatabasePassword.get()
|
2022-09-07 12:49:41 +01:00
|
|
|
@State private var useKeychain = storeDBPassphraseGroupDefault.get()
|
2022-09-17 16:41:20 +04:00
|
|
|
@State private var showRestoreDbButton = false
|
2023-03-27 18:34:48 +01:00
|
|
|
@State private var starting = false
|
2022-09-07 12:49:41 +01:00
|
|
|
|
|
|
|
var body: some View {
|
2023-03-27 18:34:48 +01:00
|
|
|
ZStack {
|
|
|
|
databaseErrorView().disabled(starting)
|
|
|
|
if starting {
|
|
|
|
ProgressView().scaleEffect(2)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2025-05-11 14:15:14 +01:00
|
|
|
private func databaseErrorView() -> some View {
|
2024-11-27 20:32:18 +00:00
|
|
|
VStack(alignment: .center, spacing: 20) {
|
2022-09-07 12:49:41 +01:00
|
|
|
switch status {
|
|
|
|
case let .errorNotADatabase(dbFile):
|
|
|
|
if useKeychain && storedDBKey != nil && storedDBKey != "" {
|
2023-03-27 18:34:48 +01:00
|
|
|
titleText("Wrong database passphrase")
|
2022-09-07 12:49:41 +01:00
|
|
|
Text("Database passphrase is different from saved in the keychain.")
|
2024-11-27 20:32:18 +00:00
|
|
|
.font(.callout)
|
|
|
|
.foregroundColor(theme.colors.secondary)
|
|
|
|
.multilineTextAlignment(.center)
|
|
|
|
.padding(.horizontal, 25)
|
|
|
|
|
2022-09-08 17:36:16 +01:00
|
|
|
databaseKeyField(onSubmit: saveAndRunChat)
|
2024-11-27 20:32:18 +00:00
|
|
|
Spacer()
|
|
|
|
VStack(spacing: 10) {
|
|
|
|
saveAndOpenButton()
|
|
|
|
fileNameText(dbFile)
|
|
|
|
}
|
2022-09-07 12:49:41 +01:00
|
|
|
} else {
|
2023-03-27 18:34:48 +01:00
|
|
|
titleText("Encrypted database")
|
2022-09-07 12:49:41 +01:00
|
|
|
Text("Database passphrase is required to open chat.")
|
2024-11-27 20:32:18 +00:00
|
|
|
.font(.callout)
|
|
|
|
.foregroundColor(theme.colors.secondary)
|
|
|
|
.multilineTextAlignment(.center)
|
|
|
|
.padding(.horizontal, 25)
|
|
|
|
.padding(.bottom, 5)
|
|
|
|
|
2022-09-07 12:49:41 +01:00
|
|
|
if useKeychain {
|
2022-09-08 17:36:16 +01:00
|
|
|
databaseKeyField(onSubmit: saveAndRunChat)
|
2024-11-27 20:32:18 +00:00
|
|
|
Spacer()
|
2022-09-07 12:49:41 +01:00
|
|
|
saveAndOpenButton()
|
|
|
|
} else {
|
2023-03-27 18:34:48 +01:00
|
|
|
databaseKeyField(onSubmit: { runChat() })
|
2024-11-27 20:32:18 +00:00
|
|
|
Spacer()
|
2022-09-07 12:49:41 +01:00
|
|
|
openChatButton()
|
|
|
|
}
|
|
|
|
}
|
2023-03-27 18:34:48 +01:00
|
|
|
case let .errorMigration(dbFile, migrationError):
|
|
|
|
switch migrationError {
|
|
|
|
case let .upgrade(upMigrations):
|
|
|
|
titleText("Database upgrade")
|
|
|
|
migrationsText(upMigrations.map(\.upName))
|
2024-11-27 20:32:18 +00:00
|
|
|
Spacer()
|
|
|
|
VStack(spacing: 10) {
|
|
|
|
Button("Upgrade and open chat") {
|
|
|
|
runChat(confirmMigrations: .yesUp)
|
|
|
|
}.buttonStyle(OnboardingButtonStyle(isDisabled: false))
|
|
|
|
fileNameText(dbFile)
|
|
|
|
}
|
2023-03-27 18:34:48 +01:00
|
|
|
case let .downgrade(downMigrations):
|
|
|
|
titleText("Database downgrade")
|
2024-11-27 20:32:18 +00:00
|
|
|
Text("Warning: you may lose some data!")
|
|
|
|
.bold()
|
|
|
|
.padding(.horizontal, 25)
|
|
|
|
.multilineTextAlignment(.center)
|
|
|
|
|
2023-03-27 18:34:48 +01:00
|
|
|
migrationsText(downMigrations)
|
2024-11-27 20:32:18 +00:00
|
|
|
Spacer()
|
|
|
|
VStack(spacing: 10) {
|
|
|
|
Button("Downgrade and open chat") {
|
|
|
|
runChat(confirmMigrations: .yesUpDown)
|
|
|
|
}.buttonStyle(OnboardingButtonStyle(isDisabled: false))
|
|
|
|
fileNameText(dbFile)
|
|
|
|
}
|
2023-03-27 18:34:48 +01:00
|
|
|
case let .migrationError(mtrError):
|
|
|
|
titleText("Incompatible database version")
|
2024-11-27 20:32:18 +00:00
|
|
|
fileNameText(dbFile, font: .callout)
|
|
|
|
errorView(Text(mtrErrorDescription(mtrError)))
|
2023-03-27 18:34:48 +01:00
|
|
|
}
|
|
|
|
case let .errorSQL(dbFile, migrationSQLError):
|
|
|
|
titleText("Database error")
|
2024-11-27 20:32:18 +00:00
|
|
|
fileNameText(dbFile, font: .callout)
|
|
|
|
errorView(Text("Error: \(migrationSQLError)"))
|
2022-09-07 12:49:41 +01:00
|
|
|
case .errorKeychain:
|
2023-03-27 18:34:48 +01:00
|
|
|
titleText("Keychain error")
|
2024-11-27 20:32:18 +00:00
|
|
|
errorView(Text("Cannot access keychain to save database password"))
|
2023-03-27 18:34:48 +01:00
|
|
|
case .invalidConfirmation:
|
|
|
|
// this can only happen if incorrect parameter is passed
|
2024-11-27 20:32:18 +00:00
|
|
|
titleText("Invalid migration confirmation")
|
|
|
|
errorView()
|
|
|
|
|
2022-09-07 12:49:41 +01:00
|
|
|
case let .unknown(json):
|
2023-03-27 18:34:48 +01:00
|
|
|
titleText("Database error")
|
2024-11-27 20:32:18 +00:00
|
|
|
errorView(Text("Unknown database error: \(json)"))
|
2022-09-07 12:49:41 +01:00
|
|
|
case .ok:
|
|
|
|
EmptyView()
|
|
|
|
}
|
2022-09-17 16:41:20 +04:00
|
|
|
if showRestoreDbButton {
|
2024-11-27 20:32:18 +00:00
|
|
|
Spacer()
|
2022-09-17 16:41:20 +04:00
|
|
|
Text("The attempt to change database passphrase was not completed.")
|
2024-11-27 20:32:18 +00:00
|
|
|
.multilineTextAlignment(.center)
|
|
|
|
.padding(.horizontal, 25)
|
|
|
|
.font(.footnote)
|
|
|
|
|
2022-09-17 16:41:20 +04:00
|
|
|
restoreDbButton()
|
|
|
|
}
|
2022-09-07 12:49:41 +01:00
|
|
|
}
|
2024-11-27 20:32:18 +00:00
|
|
|
.padding(.horizontal, 25)
|
|
|
|
.padding(.top, 75)
|
|
|
|
.padding(.bottom, 25)
|
2023-03-27 18:34:48 +01:00
|
|
|
.frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .topLeading)
|
2022-09-17 16:41:20 +04:00
|
|
|
.onAppear() { showRestoreDbButton = shouldShowRestoreDbButton() }
|
2022-09-08 17:36:16 +01:00
|
|
|
}
|
|
|
|
|
2024-11-27 20:32:18 +00:00
|
|
|
private func titleText(_ s: LocalizedStringKey) -> some View {
|
|
|
|
Text(s).font(.largeTitle).bold().multilineTextAlignment(.center)
|
2023-03-27 18:34:48 +01:00
|
|
|
}
|
|
|
|
|
2024-11-27 20:32:18 +00:00
|
|
|
private func fileNameText(_ f: String, font: Font = .caption) -> Text {
|
|
|
|
Text("File: \((f as NSString).lastPathComponent)").font(font)
|
2023-03-27 18:34:48 +01:00
|
|
|
}
|
|
|
|
|
2024-11-27 20:32:18 +00:00
|
|
|
private func migrationsText(_ ms: [String]) -> some View {
|
2025-03-07 16:50:44 +04:00
|
|
|
(Text("Migrations:").font(.subheadline) + textNewLine + Text(ms.joined(separator: "\n")).font(.caption))
|
2024-11-27 20:32:18 +00:00
|
|
|
.multilineTextAlignment(.center)
|
|
|
|
.padding(.horizontal, 25)
|
2023-03-27 18:34:48 +01:00
|
|
|
}
|
|
|
|
|
2022-09-08 17:36:16 +01:00
|
|
|
private func databaseKeyField(onSubmit: @escaping () -> Void) -> some View {
|
2023-03-22 15:58:01 +00:00
|
|
|
PassphraseField(key: $dbKey, placeholder: "Enter passphrase…", valid: validKey(dbKey), onSubmit: onSubmit)
|
2024-11-27 20:32:18 +00:00
|
|
|
.padding(.vertical, 10)
|
|
|
|
.padding(.horizontal)
|
|
|
|
.background(
|
|
|
|
RoundedRectangle(cornerRadius: 10, style: .continuous)
|
|
|
|
.fill(Color(uiColor: .tertiarySystemFill))
|
|
|
|
)
|
2022-09-08 17:36:16 +01:00
|
|
|
}
|
2022-09-07 12:49:41 +01:00
|
|
|
|
|
|
|
private func saveAndOpenButton() -> some View {
|
|
|
|
Button("Save passphrase and open chat") {
|
2022-09-08 17:36:16 +01:00
|
|
|
saveAndRunChat()
|
2022-09-07 12:49:41 +01:00
|
|
|
}
|
2024-11-27 20:32:18 +00:00
|
|
|
.buttonStyle(OnboardingButtonStyle(isDisabled: false))
|
2022-09-07 12:49:41 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
private func openChatButton() -> some View {
|
|
|
|
Button("Open chat") {
|
2022-09-08 17:36:16 +01:00
|
|
|
runChat()
|
|
|
|
}
|
2024-11-27 20:32:18 +00:00
|
|
|
.buttonStyle(OnboardingButtonStyle(isDisabled: false))
|
2022-09-08 17:36:16 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
private func saveAndRunChat() {
|
2023-04-12 12:22:55 +02:00
|
|
|
if kcDatabasePassword.set(dbKey) {
|
2022-09-08 17:36:16 +01:00
|
|
|
storeDBPassphraseGroupDefault.set(true)
|
|
|
|
initialRandomDBPassphraseGroupDefault.set(false)
|
|
|
|
}
|
|
|
|
runChat()
|
|
|
|
}
|
|
|
|
|
2023-03-27 18:34:48 +01:00
|
|
|
private func runChat(confirmMigrations: MigrationConfirmation? = nil) {
|
|
|
|
starting = true
|
|
|
|
DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) {
|
|
|
|
runChatSync(confirmMigrations: confirmMigrations)
|
|
|
|
starting = false
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
private func runChatSync(confirmMigrations: MigrationConfirmation? = nil) {
|
2022-09-08 17:36:16 +01:00
|
|
|
do {
|
2022-09-24 09:28:22 +01:00
|
|
|
resetChatCtrl()
|
2024-01-03 03:20:05 +07:00
|
|
|
try initializeChat(start: m.v3DBMigration.startChat, confirmStart: m.v3DBMigration.startChat && AppChatState.shared.value == .stopped, dbKey: useKeychain ? nil : dbKey, confirmMigrations: confirmMigrations)
|
2022-09-08 17:36:16 +01:00
|
|
|
if let s = m.chatDbStatus {
|
|
|
|
status = s
|
|
|
|
let am = AlertManager.shared
|
|
|
|
switch s {
|
2023-03-27 18:34:48 +01:00
|
|
|
case .invalidConfirmation:
|
|
|
|
am.showAlert(Alert(title: Text(String("Invalid migration confirmation"))))
|
2022-09-08 17:36:16 +01:00
|
|
|
case .errorNotADatabase:
|
|
|
|
am.showAlertMsg(
|
|
|
|
title: "Wrong passphrase!",
|
|
|
|
message: "Enter correct passphrase."
|
|
|
|
)
|
|
|
|
case .errorKeychain:
|
|
|
|
am.showAlertMsg(title: "Keychain error")
|
2023-03-27 18:34:48 +01:00
|
|
|
case let .errorSQL(_, error):
|
2022-09-08 17:36:16 +01:00
|
|
|
am.showAlert(Alert(
|
|
|
|
title: Text("Database error"),
|
|
|
|
message: Text(error)
|
|
|
|
))
|
|
|
|
case let .unknown(error):
|
|
|
|
am.showAlert(Alert(
|
|
|
|
title: Text("Unknown error"),
|
|
|
|
message: Text(error)
|
|
|
|
))
|
2023-03-27 18:34:48 +01:00
|
|
|
case .errorMigration: ()
|
2022-09-08 17:36:16 +01:00
|
|
|
case .ok: ()
|
|
|
|
}
|
2022-09-07 12:49:41 +01:00
|
|
|
}
|
2022-09-08 17:36:16 +01:00
|
|
|
} catch let error {
|
|
|
|
logger.error("initializeChat \(responseError(error))")
|
2022-09-07 12:49:41 +01:00
|
|
|
}
|
|
|
|
}
|
2022-09-17 16:41:20 +04:00
|
|
|
|
|
|
|
private func shouldShowRestoreDbButton() -> Bool {
|
|
|
|
if !encryptionStartedDefault.get() { return false }
|
|
|
|
let startedAt = encryptionStartedAtDefault.get()
|
|
|
|
// In case there is a small difference between saved encryptionStartedAt time and last modified timestamp on a file
|
|
|
|
let safeDiffInTime = TimeInterval(10)
|
|
|
|
return hasBackup(newerThan: startedAt - safeDiffInTime)
|
|
|
|
}
|
|
|
|
|
|
|
|
private func restoreDbButton() -> some View {
|
|
|
|
Button() {
|
|
|
|
AlertManager.shared.showAlert(Alert(
|
|
|
|
title: Text("Restore database backup?"),
|
|
|
|
message: Text("Please enter the previous password after restoring database backup. This action can not be undone."),
|
|
|
|
primaryButton: .destructive(Text("Restore")) {
|
|
|
|
restoreDb()
|
|
|
|
},
|
|
|
|
secondaryButton: .cancel()
|
|
|
|
))
|
|
|
|
} label: {
|
2024-11-27 20:32:18 +00:00
|
|
|
Text("Restore database backup")
|
2022-09-17 16:41:20 +04:00
|
|
|
}
|
2024-11-27 20:32:18 +00:00
|
|
|
.buttonStyle(OnboardingButtonStyle(isDisabled: false))
|
2022-09-17 16:41:20 +04:00
|
|
|
}
|
|
|
|
|
|
|
|
private func restoreDb() {
|
|
|
|
do {
|
|
|
|
try restoreBackup()
|
|
|
|
showRestoreDbButton = false
|
|
|
|
encryptionStartedDefault.set(false)
|
|
|
|
} catch {
|
|
|
|
AlertManager.shared.showAlert(Alert(
|
|
|
|
title: Text("Restore database error"),
|
|
|
|
message: Text(error.localizedDescription)
|
|
|
|
))
|
|
|
|
}
|
|
|
|
}
|
2024-11-27 20:32:18 +00:00
|
|
|
|
|
|
|
private func errorView(_ s: Text? = nil) -> some View {
|
|
|
|
VStack(spacing: 35) {
|
|
|
|
Image(systemName: "exclamationmark.triangle.fill")
|
|
|
|
.resizable()
|
|
|
|
.frame(width: 50, height: 50)
|
|
|
|
.foregroundColor(.red)
|
|
|
|
|
|
|
|
if let text = s {
|
|
|
|
text
|
|
|
|
.multilineTextAlignment(.center)
|
|
|
|
.font(.footnote)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
.padding()
|
|
|
|
.frame(maxWidth: .infinity)
|
|
|
|
}
|
2022-09-07 12:49:41 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
struct DatabaseErrorView_Previews: PreviewProvider {
|
|
|
|
static var previews: some View {
|
|
|
|
DatabaseErrorView(status: .errorNotADatabase(dbFile: "simplex_v1_chat.db"))
|
|
|
|
}
|
|
|
|
}
|