taler-ios

iOS apps for GNU Taler (wallet)
Log | Files | Refs | README | LICENSE

commit e53a3f4086e7eced2cf8cf8915ffa89e366f9d3d
parent e1901e03d089af68f9ec4094d0f50cfe5d2f2be9
Author: Marc Stibane <marc@taler.net>
Date:   Wed, 26 Aug 2026 15:07:51 +0200

migrateDatabase (w.i.p.)

Diffstat:
MTalerWallet1/Controllers/Controller.swift | 15++++++++++-----
MTalerWallet1/Controllers/PublicConstants.swift | 3---
MTalerWallet1/Model/WalletModel.swift | 34++++++++++++++++++++++++++++------
MTalerWallet1/Views/Main/MainView.swift | 1+
ATalerWallet1/Views/Main/MigrationView.swift | 128+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
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() + } +}