commit e53a3f4086e7eced2cf8cf8915ffa89e366f9d3d
parent e1901e03d089af68f9ec4094d0f50cfe5d2f2be9
Author: Marc Stibane <marc@taler.net>
Date: Wed, 26 Aug 2026 15:07:51 +0200
migrateDatabase (w.i.p.)
Diffstat:
5 files changed, 167 insertions(+), 14 deletions(-)
diff --git a/TalerWallet1/Controllers/Controller.swift b/TalerWallet1/Controllers/Controller.swift
@@ -1,5 +1,5 @@
/*
- * This file is part of GNU Taler, ©2022-25 Taler Systems S.A.
+ * This file is part of GNU Taler, ©2022-26 Taler Systems S.A.
* See LICENSE.md
*/
/**
@@ -21,6 +21,7 @@ enum BackendState: Equatable {
case none
case instantiated
case initing
+ case update
case ready
case error(EquatableError)
@@ -32,6 +33,8 @@ enum BackendState: Equatable {
return true
case (.initing, .initing):
return true
+ case (.update, .update):
+ return true
case (.ready, .ready):
return true
case (.error(let lhsError), .error(let rhsError)):
@@ -545,7 +548,7 @@ class Controller: ObservableObject {
backendState = .initing
do {
let walletCore = WalletCore.shared
- let response = try await model.initWalletCore(setTesting: setTesting, migrateNativeDb: MIGRATE_DB)
+ let response = try await model.initWalletCore(setTesting: setTesting)
walletCore.versionInfo = response.versionInfo
walletCore.nativeDB = (response.databaseBackend == "sqlite")
if developerMode {
@@ -584,13 +587,15 @@ class Controller: ObservableObject {
}
}
#endif
- DispatchQueue.main.asyncAfter(deadline: .now() + delay) {
- self.backendState = .ready // dismiss the launch animation
+ let launchDelay: TimeInterval = walletCore.nativeDB ? 0.1 : delay
+ DispatchQueue.main.asyncAfter(deadline: .now() + launchDelay) {
+ // dismiss the launch animation
+ self.backendState = walletCore.nativeDB ? .ready : .update
}
await loadBalances(CallStack(), model)
} catch { // rethrows
self.logger.error("\(error.localizedDescription)")
- backendState = .error(error.toEquatableError()) // ❗️Yikes app cannot continue
+ backendState = .error(error.toEquatableError()) // ❗️Yikes app cannot continue
throw error
}
} else {
diff --git a/TalerWallet1/Controllers/PublicConstants.swift b/TalerWallet1/Controllers/PublicConstants.swift
@@ -9,13 +9,10 @@ import Foundation
#if TALER_WALLET
let TALER_LOGO = "taler-logo-2023-blue"
-let MIGRATE_DB = false
#elseif TALER_NIGHTLY
let TALER_LOGO = "taler-logo-2023-black"
-let MIGRATE_DB = true
#else // GNU_TALER
let TALER_LOGO = "taler-logo-2023-red"
-let MIGRATE_DB = false
#endif
let TALER_LOGO_FULL = "taler-logo-full"
let TALER_LOGO_QR = "taler-logo-qr"
diff --git a/TalerWallet1/Model/WalletModel.swift b/TalerWallet1/Model/WalletModel.swift
@@ -366,7 +366,8 @@ fileprivate struct Builtin: Encodable {
}
fileprivate struct Features: Encodable {
- var migrateNativeDb: Bool // needed only once to migrate the DB
+ var migrateNativeDb: Bool // obsolete - use migrateDatabase() instead
+ var useNativeDb: Bool
}
fileprivate struct Config: Encodable {
@@ -383,7 +384,7 @@ fileprivate struct ConfigRequest: WalletBackendFormattedRequest {
func args() -> Args {
let testing = Testing(devModeActive: setTesting)
let builtin = Builtin(exchanges: [])
- let features = Features(migrateNativeDb: false)
+ let features = Features(migrateNativeDb: false, useNativeDb: true)
let config = Config(testing: testing, builtin: builtin, features: features)
return Args(config: config)
}
@@ -414,13 +415,12 @@ struct InitResponse: Decodable {
fileprivate struct InitRequest: WalletBackendFormattedRequest {
var persistentStoragePath: String
var setTesting: Bool
- var migrateNativeDb: Bool
var operation: String { "init" }
func args() -> Args {
let testing = Testing(devModeActive: setTesting)
let builtin = Builtin(exchanges: [])
- let features = Features(migrateNativeDb: migrateNativeDb)
+ let features = Features(migrateNativeDb: false, useNativeDb: true)
let config = Config(testing: testing, builtin: builtin, features: features)
return Args(persistentStoragePath: persistentStoragePath,
// cryptoWorkerType: "sync", qtart can ONLY use sync, that's the default anyway
@@ -441,10 +441,10 @@ fileprivate struct InitRequest: WalletBackendFormattedRequest {
extension WalletModel {
/// initalize Wallet-Core. Might do networking
- nonisolated func initWalletCore(setTesting: Bool, migrateNativeDb: Bool, viewHandles: Bool = false) async throws -> InitResponse {
+ nonisolated func initWalletCore(setTesting: Bool, viewHandles: Bool = false) async throws -> InitResponse {
let dbPath = try dbPath()
// logger.debug("dbPath: \(dbPath)")
- let request = InitRequest(persistentStoragePath: dbPath, setTesting: setTesting, migrateNativeDb: migrateNativeDb)
+ let request = InitRequest(persistentStoragePath: dbPath, setTesting: setTesting)
let response = try await sendRequest(request, viewHandles: viewHandles) // no Delay
return response
}
@@ -540,6 +540,28 @@ extension WalletModel {
}
}
// MARK: -
+/// A request to migrate the Wallet-core DB from indexed to native sqlite.
+fileprivate struct MigrateRequest: WalletBackendFormattedRequest {
+ var operation: String { "migrateDatabase" }
+ func args() -> Args { Args(progressToken: operation) }
+
+ struct Args: Encodable {
+ var progressToken: String
+ }
+ struct Response: Decodable {} // no result - instead there are Notifications
+}
+
+extension WalletModel {
+ /// reset Wallet-Core
+ nonisolated func migrateDatabase(viewHandles: Bool = false) async throws {
+ let request = MigrateRequest()
+ let controller = Controller.shared
+ controller.progressOperation = request.operation
+ controller.progressToken = request.operation
+ _ = try await sendRequest(request, viewHandles: viewHandles)
+ }
+}
+// MARK: -
/// A request to reset Wallet-core to a virgin DB. WILL DESTROY ALL COINS
fileprivate struct ResetRequest: WalletBackendFormattedRequest {
var operation: String { "clearDb" }
diff --git a/TalerWallet1/Views/Main/MainView.swift b/TalerWallet1/Views/Main/MainView.swift
@@ -226,6 +226,7 @@ struct MainView: View {
// show launch animation until either ready or error
switch controller.backendState {
case .ready: mainContent
+ case .update: MigrationView()
case .error(let error):
ErrorView(stack.push("mainGroup"),
title: String(localized: "Launch error"),
diff --git a/TalerWallet1/Views/Main/MigrationView.swift b/TalerWallet1/Views/Main/MigrationView.swift
@@ -0,0 +1,128 @@
+/*
+ * This file is part of GNU Taler, ©2022-26 Taler Systems S.A.
+ * See LICENSE.md
+ */
+/**
+ * @author Marc Stibane
+ */
+import SwiftUI
+import SymLog
+import taler_swift
+
+struct MigrationView: View {
+
+ @Environment(\.colorScheme) private var colorScheme
+ @Environment(\.colorSchemeContrast) private var colorSchemeContrast
+ @EnvironmentObject private var controller: Controller
+ @EnvironmentObject private var model: WalletModel
+#if DEBUG
+ @AppStorage("developerMode") var developerMode: Bool = true
+#else
+ @AppStorage("developerMode") var developerMode: Bool = false
+#endif
+ @AppStorage("preferredColorScheme") var preferredColorScheme: Int = 0
+ @AppStorage("myListStyle") var myListStyle: MyListStyle = .automatic
+
+ @State private var isMigrating: Bool = false
+ @State private var fakeMigration: Bool = false
+ @State private var isFinished: Bool = false
+ @State private var progress: Double = 0
+
+ func buttonAction() {
+ if !isMigrating {
+ Task {
+// try? await model.migrateDatabase()
+ }
+ withAnimation {
+ self.isMigrating = true
+ }
+ }
+ }
+
+ func fakeAction() {
+ if !isMigrating {
+ fakeMigration = true
+ withAnimation {
+ self.isMigrating = true
+ }
+ }
+ }
+
+ func cancelAction() {
+ self.isMigrating = false
+ // TODO: cancel migration
+ finishAction()
+ }
+
+ func finishAction() {
+ withAnimation {
+ controller.backendState = .ready
+ }
+ }
+ var body: some View {
+ List {
+ Section(header: Text("Update database")) {
+ if isFinished {
+ Text("Finished")
+ .talerFont(.title)
+ .task {
+ DispatchQueue.main.asyncAfter(deadline: .now() + 2) {
+ finishAction()
+ }
+ }
+ } else if isMigrating {
+ if fakeMigration {
+ Text("Faking migration")
+ .talerFont(.title)
+ .task {
+ for x in 0...100 {
+ await Task.sleep(20_000_000)
+ self.progress = Double(x) / 100
+ }
+ withAnimation {
+ isFinished = true
+ }
+ }
+ } else {
+ Text("Updating the database:")
+ .talerFont(.title)
+ }
+ ProgressView(value: progress)
+ Button("Cancel", action: cancelAction)
+ .buttonStyle(TalerButtonStyle(type: .bordered))
+ .padding()
+ } else {
+ Text("The money database needs to be updated.")
+ .talerFont(.title)
+ Text("This might take a while...")
+ .talerFont(.body)
+
+ Button("Update now", action: buttonAction)
+ .buttonStyle(TalerButtonStyle(type: .bordered, disabled: isMigrating))
+ .disabled(isMigrating)
+ .padding()
+ if developerMode {
+ Button("Fake update", action: fakeAction)
+ .buttonStyle(TalerButtonStyle(type: .bordered, disabled: isMigrating))
+ .disabled(isMigrating)
+ .padding()
+ }
+ Button("Update later", action: finishAction)
+ .buttonStyle(TalerButtonStyle(type: .bordered))
+ .padding()
+ }
+ }
+ .listRowSeparator(.hidden)
+ }
+ .listStyle(myListStyle.style).anyView
+ .talerFont(.title2)
+ .background(FullBackground())
+ }
+}
+
+
+struct MigrationView_Previews: PreviewProvider {
+ static var previews: some View {
+ MigrationView()
+ }
+}