diff --git a/src/app/boot/app_controller.nim b/src/app/boot/app_controller.nim index 82b7dd68e9..c5ac3baf7b 100644 --- a/src/app/boot/app_controller.nim +++ b/src/app/boot/app_controller.nim @@ -35,10 +35,18 @@ import ../global/global_singleton # This will be removed later once we move to c++ and handle there async things # and improved some services, like EventsService which should implement # provider/subscriber principe, similar we should have SettingsService. +import ../../constants import ../core/[main] import eventemitter import status/[fleet] import ../profile/core as profile +import ../chat/core as chat +import ../wallet/v1/core as wallet +import ../wallet/v2/core as walletV2 +import ../node/core as node +import ../utilsView/core as utilsView +import ../provider/core as provider +import ../keycard/core as keycard import status/types/[account, setting] ################################################# @@ -103,9 +111,15 @@ type mainModule: main_module.AccessInterface ################################################# - # At the end of refactoring this will be moved to - # appropriate place or removed: + # At the end of refactoring this will be moved to appropriate place or removed: profile: ProfileController + wallet: wallet.WalletController + wallet2: walletV2.WalletController + chat: ChatController + node: NodeController + utilsController: UtilsController + provider: Web3ProviderController + keycard: KeycardController ################################################# ################################################# @@ -123,12 +137,25 @@ proc mainDidLoad*(self: AppController) ################################################# ################################################# -# At the end of refactoring this will be moved to -# appropriate place or removed: +# At the end of refactoring this will be moved to appropriate place or removed: proc connect(self: AppController) = self.statusFoundation.status.events.once("loginCompleted") do(a: Args): var args = AccountArgs(a) + self.statusFoundation.status.startMessenger() self.profile.init(args.account) + self.wallet.init() + self.wallet2.init() + self.provider.init() + self.chat.init() + self.utilsController.init() + self.node.init() + self.wallet.onLogin() + + self.statusFoundation.status.events.once("nodeStopped") do(a: Args): + # TODO: remove this once accounts are not tracked in the AccountsModel + self.statusFoundation.status.reset() + # 2. Re-init controllers that don't require a running node + self.keycard.init() ################################################# proc newAppController*(statusFoundation: StatusFoundation): AppController = @@ -202,9 +229,15 @@ proc newAppController*(statusFoundation: StatusFoundation): AppController = ) ################################################# - # At the end of refactoring this will be moved to - # appropriate place or removed: + # At the end of refactoring this will be moved to appropriate place or removed: result.profile = profile.newController(statusFoundation.status, statusFoundation, changeLanguage) + result.wallet = wallet.newController(statusFoundation.status, statusFoundation) + result.wallet2 = walletV2.newController(statusFoundation.status, statusFoundation) + result.chat = chat.newController(statusFoundation.status, statusFoundation, OPENURI) + result.node = node.newController(statusFoundation) + result.utilsController = utilsView.newController(statusFoundation.status, statusFoundation) + result.provider = provider.newController(statusFoundation.status) + result.keycard = keycard.newController(statusFoundation.status) result.connect() ################################################# @@ -230,9 +263,15 @@ proc delete*(self: AppController) = self.mainModule.delete ################################################# - # At the end of refactoring this will be moved to - # appropriate place or removed: + # At the end of refactoring this will be moved to appropriate place or removed: self.profile.delete + self.wallet.delete + self.wallet2.delete + self.chat.delete + self.node.delete + self.utilsController.delete + self.provider.delete + self.keycard.delete ################################################# self.localAppSettingsVariant.delete @@ -255,9 +294,15 @@ proc delete*(self: AppController) = proc startupDidLoad*(self: AppController) = ################################################# - # At the end of refactoring this will be moved to - # appropriate place or removed: + # At the end of refactoring this will be moved to appropriate place or removed: singletonInstance.engine.setRootContextProperty("profileModel", self.profile.variant) + singletonInstance.engine.setRootContextProperty("walletModel", self.wallet.variant) + singletonInstance.engine.setRootContextProperty("walletV2Model", self.wallet2.variant) + singletonInstance.engine.setRootContextProperty("chatsModel", self.chat.variant) + singletonInstance.engine.setRootContextProperty("nodeModel", self.node.variant) + singletonInstance.engine.setRootContextProperty("utilsModel", self.utilsController.variant) + singletonInstance.engine.setRootContextProperty("web3Provider", self.provider.variant) + singletonInstance.engine.setRootContextProperty("keycardModel", self.keycard.variant) ################################################# singletonInstance.engine.setRootContextProperty("localAppSettings", self.localAppSettingsVariant) @@ -275,6 +320,11 @@ proc mainDidLoad*(self: AppController) = self.mainModule.checkForStoringPassword() proc start*(self: AppController) = + ################################################# + # At the end of refactoring this will be moved to appropriate place or removed: + self.keycard.init() + ################################################# + self.accountsService.init() self.startupModule.load() diff --git a/src/app/core/main.nim b/src/app/core/main.nim index e8a4f9bbeb..65c268ae49 100644 --- a/src/app/core/main.nim +++ b/src/app/core/main.nim @@ -1,4 +1,5 @@ import NimQml, chronicles, task_runner +import ../../constants import status/status as status_lib_status import ./tasks/marathon, @@ -18,19 +19,23 @@ type StatusFoundation* = ref object mailserverController*: MailserverController mailserverWorker*: MailserverWorker -proc newStatusFoundation*(status: Status): StatusFoundation = +proc newStatusFoundation*(fleetConfig: string): StatusFoundation = result = StatusFoundation() - result.status = status - result.mailserverController = newMailserverController(status) + + result.status = newStatusInstance(fleetConfig) + result.status.initNode(STATUSGODIR, KEYSTOREDIR) + + result.mailserverController = newMailserverController(result.status.events) result.mailserverWorker = newMailserverWorker(cast[ByteAddress](result.mailserverController.vptr)) result.threadpool = newThreadPool() result.marathon = newMarathon(result.mailserverWorker) - result.signalsManager = newSignalsManager(status.events) + result.signalsManager = newSignalsManager(result.status.events) proc delete*(self: StatusFoundation) = self.threadpool.teardown() self.marathon.teardown() self.signalsManager.delete() + self.status.reset() proc onLoggedIn*(self: StatusFoundation) = self.marathon.onLoggedIn() diff --git a/src/app/core/tasks/marathon/mailserver/controller.nim b/src/app/core/tasks/marathon/mailserver/controller.nim index da2f1b2406..04ed3c7ef1 100644 --- a/src/app/core/tasks/marathon/mailserver/controller.nim +++ b/src/app/core/tasks/marathon/mailserver/controller.nim @@ -1,8 +1,13 @@ +import # std libs + strutils + import # vendor libs chronicles, NimQml, json_serialization -import # status-desktop libs - status/status, ../../common as task_runner_common +import events +import ../../common as task_runner_common + +import eventemitter logScope: topics = "mailserver controller" @@ -14,11 +19,11 @@ logScope: ################################################################################ QtObject: type MailserverController* = ref object of QObject - status*: Status + events: EventEmitter - proc newMailserverController*(status: Status): MailserverController = + proc newMailserverController*(events: EventEmitter): MailserverController = new(result) - result.status = status + result.events = events result.setup() proc setup(self: MailserverController) = @@ -26,3 +31,8 @@ QtObject: proc delete*(self: MailserverController) = self.QObject.delete + + proc receiveEvent(self: MailserverController, eventTuple: string) {.slot.} = + let event = Json.decode(eventTuple, tuple[name: string, arg: MailserverArgs]) + trace "forwarding event from long-running mailserver task to the main thread", event=eventTuple + self.events.emit(event.name, event.arg) \ No newline at end of file diff --git a/src/app/node/core.nim b/src/app/node/core.nim index aa45c2aeff..efc764b60b 100644 --- a/src/app/node/core.nim +++ b/src/app/node/core.nim @@ -11,15 +11,13 @@ type NodeController* = ref object statusFoundation: StatusFoundation view*: NodeView variant*: QVariant - networkAccessMananger*: QNetworkAccessManager isWakuV2: bool -proc newController*(statusFoundation: StatusFoundation, nam: QNetworkAccessManager): NodeController = +proc newController*(statusFoundation: StatusFoundation): NodeController = result = NodeController() result.statusFoundation = statusFoundation result.view = newNodeView(statusFoundation) result.variant = newQVariant(result.view) - result.networkAccessMananger = nam proc delete*(self: NodeController) = delete self.variant diff --git a/src/app/profile/core.nim b/src/app/profile/core.nim index 396a6b09b8..135b354caf 100644 --- a/src/app/profile/core.nim +++ b/src/app/profile/core.nim @@ -1,4 +1,4 @@ -import NimQml, json, strutils, sugar, sequtils, tables +import NimQml, json, tables import json_serialization import status/[status, settings] import status/contacts as status_contacts diff --git a/src/nim_status_client.nim b/src/nim_status_client.nim index b9cc92bd54..f3e4e4e733 100644 --- a/src/nim_status_client.nim +++ b/src/nim_status_client.nim @@ -1,126 +1,45 @@ import NimQml, chronicles, os, strformat, times, md5, json -import app/chat/core as chat -import app/wallet/v1/core as wallet -import app/wallet/v2/core as walletV2 -import app/node/core as node -import app/utilsView/core as utilsView -import app/keycard/core as keycard -import status/types/[account] import status_go -import status/status as statuslib -import eventemitter import app/core/main import constants import app/global/global_singleton import app/boot/app_controller +logScope: + topics = "status-app" var signalsManagerQObjPointer: pointer -logScope: - topics = "main" +proc isExperimental(): string = + result = if getEnv("EXPERIMENTAL") == "1": "1" else: "0" # value explicity passed to avoid trusting input -proc mainProc() = - if defined(macosx) and defined(production): - setCurrentDir(getAppDir()) +proc determineResourcePath(): string = + result = if defined(windows) and defined(production): "/../resources/resources.rcc" else: "/../resources.rcc" - ensureDirectories(DATADIR, TMPDIR, LOGDIR) +proc determineFleetsPath(): string = + result = if defined(windows) and defined(production): "/../resources/fleets.json" else: "/../fleets.json" - let fleets = - if defined(windows) and defined(production): - "/../resources/fleets.json" - else: - "/../fleets.json" - - let - fleetConfig = readFile(joinPath(getAppDir(), fleets)) - status = statuslib.newStatusInstance(fleetConfig) - - let statusFoundation = newStatusFoundation(status) - defer: statusFoundation.delete() - - status.initNode(STATUSGODIR, KEYSTOREDIR) - - let uiScaleFilePath = joinPath(DATADIR, "ui-scale") - enableHDPI(uiScaleFilePath) - initializeOpenGL() - - let app = newQGuiApplication() - defer: app.delete() - - let appController = newAppController(statusFoundation) - defer: appController.delete() - - let resources = - if defined(windows) and defined(production): - "/../resources/resources.rcc" - else: - "/../resources.rcc" - QResource.registerResource(app.applicationDirPath & resources) - - var eventStr = "" +proc determineOpenUri(): string = if OPENURI.len > 0: - eventStr = $(%* { "uri": OPENURI }) - let singleInstance = newSingleInstance($toMD5(DATADIR), eventStr) - defer: singleInstance.delete() - if singleInstance.secondInstance(): - info "Terminating the app as the second instance" - quit() + result = $(%* { "uri": OPENURI }) - let statusAppIcon = - if defined(production): - if defined(macosx): - "" # not used in macOS - elif defined(windows): - "/../resources/status.svg" - else: - "/../status.svg" +proc determineStatusAppIconPath(): string = + if defined(production): + if defined(macosx): + return "" # not used in macOS + elif defined(windows): + return "/../resources/status.svg" else: - if defined(macosx): - "" # not used in macOS - else: - "/../status-dev.svg" - - if not defined(macosx): - app.icon(app.applicationDirPath & statusAppIcon) - - let networkAccessFactory = newQNetworkAccessManagerFactory(TMPDIR & "netcache") - - singletonInstance.engine.addImportPath("qrc:/./StatusQ/src") - singletonInstance.engine.addImportPath("qrc:/./imports") - singletonInstance.engine.setNetworkAccessManagerFactory(networkAccessFactory) - singletonInstance.engine.setRootContextProperty("uiScaleFilePath", newQVariant(uiScaleFilePath)) - - # Register events objects - let dockShowAppEvent = newStatusDockShowAppEventObject(singletonInstance.engine) - defer: dockShowAppEvent.delete() - let osThemeEvent = newStatusOSThemeEventObject(singletonInstance.engine) - defer: osThemeEvent.delete() - app.installEventFilter(dockShowAppEvent) - app.installEventFilter(osThemeEvent) - - let netAccMgr = newQNetworkAccessManager(singletonInstance.engine.getNetworkAccessManager()) - - status.events.on("network:connected") do(e: Args): - # This is a workaround for Qt bug https://bugreports.qt.io/browse/QTBUG-55180 - # that was apparently reintroduced in 5.14.1 Unfortunately, the only workaround - # that could be found uses obsolete properties and methods - # (https://doc.qt.io/qt-5/qnetworkaccessmanager-obsolete.html), so this will - # need to be something we keep in mind when upgrading to Qt 6. - # The workaround is to manually set the NetworkAccessible property of the - # QNetworkAccessManager once peers have dropped (network connection is lost). - netAccMgr.clearConnectionCache() - netAccMgr.setNetworkAccessible(NetworkAccessibility.Accessible) - - - # We need this global variable in order to be able to access the application - # from the non-closure callback passed to `statusgo_backend.setSignalEventCallback` - signalsManagerQObjPointer = cast[pointer](statusFoundation.signalsManager.vptr) - defer: - signalsManagerQObjPointer = nil + return "/../status.svg" + else: + if defined(macosx): + return "" # not used in macOS + else: + return "/../status-dev.svg" +proc prepareLogging() = when compiles(defaultChroniclesStream.output.writer): defaultChroniclesStream.output.writer = proc (logLevel: LogLevel, msg: LogOutputStr) {.gcsafe, raises: [Defect].} = @@ -133,95 +52,7 @@ proc mainProc() = let logFile = fmt"app_{getTime().toUnix}.log" discard defaultChroniclesStream.outputs[1].open(LOGDIR & logFile, fmAppend) - var wallet = wallet.newController(status, statusFoundation) - defer: wallet.delete() - singletonInstance.engine.setRootContextProperty("walletModel", wallet.variant) - - var wallet2 = walletV2.newController(status, statusFoundation) - defer: wallet2.delete() - singletonInstance.engine.setRootContextProperty("walletV2Model", wallet2.variant) - - var chat = chat.newController(status, statusFoundation, OPENURI) - defer: chat.delete() - singletonInstance.engine.setRootContextProperty("chatsModel", chat.variant) - - var node = node.newController(statusFoundation, netAccMgr) - defer: node.delete() - singletonInstance.engine.setRootContextProperty("nodeModel", node.variant) - - var utilsController = utilsView.newController(status, statusFoundation) - defer: utilsController.delete() - singletonInstance.engine.setRootContextProperty("utilsModel", utilsController.variant) - - var keycard = keycard.newController(status) - defer: keycard.delete() - - status.events.once("loginCompleted") do(a: Args): - var args = AccountArgs(a) - - # At the end of refactoring all this will be in the AppController class. - status.startMessenger() - wallet.init() - wallet2.init() - chat.init() - utilsController.init() - node.init() - - wallet.onLogin() - - # this should be the last defer in the scope - defer: - info "Status app is shutting down..." - singletonInstance.delete() - - singletonInstance.engine.setRootContextProperty("keycardModel", keycard.variant) - singletonInstance.engine.setRootContextProperty("singleInstance", newQVariant(singleInstance)) - - let isExperimental = if getEnv("EXPERIMENTAL") == "1": "1" else: "0" # value explicity passed to avoid trusting input - let experimentalFlag = newQVariant(isExperimental) - singletonInstance.engine.setRootContextProperty("isExperimental", experimentalFlag) - - # Initialize only controllers whose init functions - # do not need a running node - proc initControllers() = - keycard.init() - - initControllers() - - # Handle node.stopped signal when user has logged out - status.events.once("nodeStopped") do(a: Args): - # TODO: remove this once accounts are not tracked in the AccountsModel - status.reset() - - # 1. Reset controller data - # TODO: implement all controller resets - # chat.reset() - # node.reset() - # wallet.reset() - # wallet2.reset() - # profile.reset() - - # 2. Re-init controllers that don't require a running node - initControllers() - - var signalsManagerQVariant = newQVariant(statusFoundation.signalsManager) - defer: signalsManagerQVariant.delete() - var mailserverControllerQVariant = newQVariant(statusFoundation.mailserverController) - defer: mailserverControllerQVariant.delete() - - singletonInstance.engine.setRootContextProperty("signals", signalsManagerQVariant) - singletonInstance.engine.setRootContextProperty("mailserver", mailserverControllerQVariant) - - var prValue = newQVariant(if defined(production): true else: false) - singletonInstance.engine.setRootContextProperty("production", prValue) - - # # We're applying default language before we load qml. Also we're aware that - # # switch language at runtime will have some impact to cpu usage. - # # https://doc.qt.io/archives/qtjambi-4.5.2_01/com/trolltech/qt/qtjambi-linguist-programmers.html - # changeLanguage("en") - - appController.start() - +proc setupRemoteSignalsHandling() = # Please note that this must use the `cdecl` calling convention because # it will be passed as a regular C function to statusgo_backend. This means that # we cannot capture any local variables here (we must rely on globals) @@ -231,9 +62,84 @@ proc mainProc() = status_go.setSignalEventCallback(callback) - # Qt main event loop is entered here - # The termination of the loop will be performed when exit() or quit() is called - info "Starting application..." +proc mainProc() = + if defined(macosx) and defined(production): + setCurrentDir(getAppDir()) + + ensureDirectories(DATADIR, TMPDIR, LOGDIR) + + let isExperimental = isExperimental() + let resourcesPath = determineResourcePath() + let fleetsPath = determineFleetsPath() + let openUri = determineOpenUri() + let statusAppIconPath = determineStatusAppIconPath() + + let fleetConfig = readFile(joinPath(getAppDir(), fleetsPath)) + let statusFoundation = newStatusFoundation(fleetConfig) + let uiScaleFilePath = joinPath(DATADIR, "ui-scale") + enableHDPI(uiScaleFilePath) + initializeOpenGL() + + let app = newQGuiApplication() + let appController = newAppController(statusFoundation) + let singleInstance = newSingleInstance($toMD5(DATADIR), openUri) + let networkAccessFactory = newQNetworkAccessManagerFactory(TMPDIR & "netcache") + + let isProductionQVariant = newQVariant(if defined(production): true else: false) + let isExperimentalQVariant = newQVariant(isExperimental) + let signalsManagerQVariant = newQVariant(statusFoundation.signalsManager) + let mailserverControllerQVariant = newQVariant(statusFoundation.mailserverController) + + QResource.registerResource(app.applicationDirPath & resourcesPath) + # Register events objects + let dockShowAppEvent = newStatusDockShowAppEventObject(singletonInstance.engine) + let osThemeEvent = newStatusOSThemeEventObject(singletonInstance.engine) + # We need this global variable in order to be able to access the application + # from the non-closure callback passed to `statusgo_backend.setSignalEventCallback` + signalsManagerQObjPointer = cast[pointer](statusFoundation.signalsManager.vptr) + setupRemoteSignalsHandling() + + if not defined(macosx): + app.icon(app.applicationDirPath & statusAppIconPath) + + prepareLogging() + + singletonInstance.engine.addImportPath("qrc:/./StatusQ/src") + singletonInstance.engine.addImportPath("qrc:/./imports") + singletonInstance.engine.setNetworkAccessManagerFactory(networkAccessFactory) + singletonInstance.engine.setRootContextProperty("uiScaleFilePath", newQVariant(uiScaleFilePath)) + singletonInstance.engine.setRootContextProperty("singleInstance", newQVariant(singleInstance)) + singletonInstance.engine.setRootContextProperty("isExperimental", isExperimentalQVariant) + singletonInstance.engine.setRootContextProperty("signals", signalsManagerQVariant) + singletonInstance.engine.setRootContextProperty("mailserver", mailserverControllerQVariant) + singletonInstance.engine.setRootContextProperty("production", isProductionQVariant) + + app.installEventFilter(dockShowAppEvent) + app.installEventFilter(osThemeEvent) + + defer: + info "shutting down..." + signalsManagerQObjPointer = nil + isProductionQVariant.delete() + isExperimentalQVariant.delete() + signalsManagerQVariant.delete() + mailserverControllerQVariant.delete() + networkAccessFactory.delete() + dockShowAppEvent.delete() + osThemeEvent.delete() + statusFoundation.delete() + appController.delete() + singleInstance.delete() + app.delete() + + # Checks below must be always after "defer", in case anything fails destructors will freed a memory. + if singleInstance.secondInstance(): + info "Terminating the app as the second instance" + quit() + + info "starting application controller..." + appController.start() + info "starting application..." app.exec() when isMainModule: