From 13543ae14f75835cc45664c1baf823cb790861d1 Mon Sep 17 00:00:00 2001 From: Jonathan Rainville Date: Mon, 20 Dec 2021 13:21:21 -0500 Subject: [PATCH] refactor(about): refactor version fetch --- src/app/boot/app_controller.nim | 10 +- src/app/modules/main/module.nim | 2 +- .../main/profile_section/about/controller.nim | 20 +++- .../about/controller_interface.nim | 3 + .../profile_section/about/io_interface.nim | 6 ++ .../main/profile_section/about/module.nim | 18 +++- .../main/profile_section/about/view.nim | 24 ++++- .../modules/main/profile_section/module.nim | 4 +- src/app_service/service/about/async_tasks.nim | 27 ++++++ src/app_service/service/about/dto.nim | 0 src/app_service/service/about/service.nim | 93 ++++++++++++++----- .../service/about/service_interface.nim | 22 ----- src/app_service/service/about/update.nim | 55 +++++++++++ src/app_service/service/ens/service.nim | 29 ------ src/app_service/service/ens/utils.nim | 69 ++++++++++++++ src/app_service/service/provider/service.nim | 13 +-- ui/app/AppLayouts/stores/RootStore.qml | 1 + ui/app/AppMain.qml | 12 ++- 18 files changed, 303 insertions(+), 105 deletions(-) create mode 100644 src/app_service/service/about/async_tasks.nim delete mode 100644 src/app_service/service/about/dto.nim delete mode 100644 src/app_service/service/about/service_interface.nim create mode 100644 src/app_service/service/about/update.nim delete mode 100644 src/app_service/service/ens/service.nim create mode 100644 src/app_service/service/ens/utils.nim diff --git a/src/app/boot/app_controller.nim b/src/app/boot/app_controller.nim index 502d964672..352a8ebeed 100644 --- a/src/app/boot/app_controller.nim +++ b/src/app/boot/app_controller.nim @@ -20,7 +20,6 @@ import ../../app_service/service/dapp_permissions/service as dapp_permissions_se import ../../app_service/service/mnemonic/service as mnemonic_service import ../../app_service/service/privacy/service as privacy_service import ../../app_service/service/provider/service as provider_service -import ../../app_service/service/ens/service as ens_service import ../../app_service/service/profile/service as profile_service import ../../app_service/service/settings/service as settings_service import ../../app_service/service/stickers/service as stickers_service @@ -85,7 +84,6 @@ type walletAccountService: wallet_account_service.Service bookmarkService: bookmark_service.Service dappPermissionsService: dapp_permissions_service.Service - ensService: ens_service.Service providerService: provider_service.Service profileService: profile_service.Service settingsService: settings_service.Service @@ -166,13 +164,12 @@ proc newAppController*(statusFoundation: StatusFoundation): AppController = result.networkService, result.chatService ) - result.aboutService = about_service.newService() + result.aboutService = about_service.newService(statusFoundation.status.events, statusFoundation.threadpool, result.settingsService) result.dappPermissionsService = dapp_permissions_service.newService() result.languageService = language_service.newService() result.mnemonicService = mnemonic_service.newService() result.privacyService = privacy_service.newService() - result.ensService = ens_service.newService() - result.providerService = provider_service.newService(result.dappPermissionsService, result.settingsService, result.ensService) + result.providerService = provider_service.newService(result.dappPermissionsService, result.settingsService) result.savedAddressService = saved_address_service.newService(statusFoundation.status.events) # Modules @@ -241,7 +238,6 @@ proc delete*(self: AppController) = self.activityCenterService.delete self.dappPermissionsService.delete self.providerService.delete - self.ensService.delete self.nodeConfigurationService.delete self.settingsService.delete self.stickersService.delete @@ -277,7 +273,6 @@ proc load(self: AppController) = self.bookmarkService.init() self.tokenService.init() self.dappPermissionsService.init() - self.ensService.init() self.providerService.init() self.walletAccountService.init() self.transactionService.init() @@ -286,6 +281,7 @@ proc load(self: AppController) = self.networkService.init() self.activityCenterService.init() self.savedAddressService.init() + self.aboutService.init() let pubKey = self.settingsService.getPublicKey() singletonInstance.localAccountSensitiveSettings.setFileName(pubKey) diff --git a/src/app/modules/main/module.nim b/src/app/modules/main/module.nim index 958ecbbb13..813e85bfaf 100644 --- a/src/app/modules/main/module.nim +++ b/src/app/modules/main/module.nim @@ -74,7 +74,7 @@ proc newModule*[T]( profileService: profile_service.ServiceInterface, settingsService: settings_service.ServiceInterface, contactsService: contacts_service.Service, - aboutService: about_service.ServiceInterface, + aboutService: about_service.Service, dappPermissionsService: dapp_permissions_service.ServiceInterface, languageService: language_service.ServiceInterface, mnemonicService: mnemonic_service.ServiceInterface, diff --git a/src/app/modules/main/profile_section/about/controller.nim b/src/app/modules/main/profile_section/about/controller.nim index d21e44247f..abb17695a7 100644 --- a/src/app/modules/main/profile_section/about/controller.nim +++ b/src/app/modules/main/profile_section/about/controller.nim @@ -1,29 +1,39 @@ import ./controller_interface +import eventemitter import io_interface import ../../../../../app_service/service/about/service as about_service -# import ./item as item - export controller_interface type Controller* = ref object of controller_interface.AccessInterface delegate: io_interface.AccessInterface - aboutService: about_service.ServiceInterface + events: EventEmitter + aboutService: about_service.Service -proc newController*(delegate: io_interface.AccessInterface, aboutService: about_service.ServiceInterface): Controller = +proc newController*( + delegate: io_interface.AccessInterface, + events: EventEmitter, + aboutService: about_service.Service + ): Controller = result = Controller() result.delegate = delegate + result.events = events result.aboutService = aboutService method delete*(self: Controller) = discard method init*(self: Controller) = - discard + self.events.on(SIGNAL_VERSION_FETCHED) do(e: Args): + let args = VersionArgs(e) + self.delegate.versionFetched(args.version) method getAppVersion*(self: Controller): string = return self.aboutService.getAppVersion() +method checkForUpdates*(self: Controller) = + self.aboutService.checkForUpdates() + method getNodeVersion*(self: Controller): string = return self.aboutService.getNodeVersion() diff --git a/src/app/modules/main/profile_section/about/controller_interface.nim b/src/app/modules/main/profile_section/about/controller_interface.nim index 6d901eec6c..f216a15683 100644 --- a/src/app/modules/main/profile_section/about/controller_interface.nim +++ b/src/app/modules/main/profile_section/about/controller_interface.nim @@ -16,6 +16,9 @@ method getAppVersion*(self: AccessInterface): string {.base.} = method getNodeVersion*(self: AccessInterface): string {.base.} = raise newException(ValueError, "No implementation available") +method checkForUpdates*(self: AccessInterface) {.base.} = + raise newException(ValueError, "No implementation available") + type ## Abstract class (concept) which must be implemented by object/s used in this ## module. diff --git a/src/app/modules/main/profile_section/about/io_interface.nim b/src/app/modules/main/profile_section/about/io_interface.nim index cb2185ff32..dd9a2105d9 100644 --- a/src/app/modules/main/profile_section/about/io_interface.nim +++ b/src/app/modules/main/profile_section/about/io_interface.nim @@ -17,6 +17,12 @@ method getAppVersion*(self: AccessInterface): string {.base.} = method getNodeVersion*(self: AccessInterface): string {.base.} = raise newException(ValueError, "No implementation available") +method versionFetched*(self: AccessInterface, version: string) {.base.} = + raise newException(ValueError, "No implementation available") + +method checkForUpdates*(self: AccessInterface) {.base.} = + raise newException(ValueError, "No implementation available") + # View Delegate Interface # Delegate for the view must be declared here due to use of QtObject and multi # inheritance, which is not well supported in Nim. diff --git a/src/app/modules/main/profile_section/about/module.nim b/src/app/modules/main/profile_section/about/module.nim index afdabf33d8..006e7a2f2c 100644 --- a/src/app/modules/main/profile_section/about/module.nim +++ b/src/app/modules/main/profile_section/about/module.nim @@ -1,4 +1,5 @@ -import NimQml, Tables +import NimQml +import eventemitter import ./io_interface, ./view, ./controller import ../io_interface as delegate_interface @@ -16,12 +17,16 @@ type viewVariant: QVariant moduleLoaded: bool -proc newModule*(delegate: delegate_interface.AccessInterface, aboutService: about_service.ServiceInterface): Module = +proc newModule*( + delegate: delegate_interface.AccessInterface, + events: EventEmitter, + aboutService: about_service.Service + ): Module = result = Module() result.delegate = delegate result.view = newView(result) result.viewVariant = newQVariant(result.view) - result.controller = controller.newController(result, aboutService) + result.controller = controller.newController(result, events, aboutService) result.moduleLoaded = false singletonInstance.engine.setRootContextProperty("aboutModule", result.viewVariant) @@ -31,6 +36,7 @@ method delete*(self: Module) = method load*(self: Module) = self.view.load() + self.controller.init() method isLoaded*(self: Module): bool = return self.moduleLoaded @@ -44,3 +50,9 @@ method getAppVersion*(self: Module): string = method getNodeVersion*(self: Module): string = return self.controller.getNodeVersion() + +method checkForUpdates*(self: Module) = + self.controller.checkForUpdates() + +method versionFetched*(self: Module, version: string) = + self.view.versionFetched(version) diff --git a/src/app/modules/main/profile_section/about/view.nim b/src/app/modules/main/profile_section/about/view.nim index 9278a49c45..1e1400aa5e 100644 --- a/src/app/modules/main/profile_section/about/view.nim +++ b/src/app/modules/main/profile_section/about/view.nim @@ -1,4 +1,4 @@ -import NimQml +import NimQml, json # import ./controller_interface import ./io_interface @@ -7,6 +7,7 @@ QtObject: type View* = ref object of QObject delegate: io_interface.AccessInterface + newVersion*: string proc delete*(self: View) = self.QObject.delete @@ -15,6 +16,11 @@ QtObject: new(result, delete) result.QObject.setup result.delegate = delegate + result.newVersion = $(%*{ + "available": false, + "version": "0.0.0", + "url": "about:blank" + }) proc load*(self: View) = self.delegate.viewDidLoad() @@ -24,3 +30,19 @@ QtObject: proc nodeVersion*(self: View): string {.slot.} = return self.delegate.getNodeVersion() + + proc newVersionChanged(self: View) {.signal.} + + proc versionFetched*(self: View, version: string) = + self.newVersion = version + self.newVersionChanged() + + proc checkForUpdates*(self: View) {.slot.} = + self.delegate.checkForUpdates() + + proc getNewVersion*(self: View): string {.slot.} = + return self.newVersion + + QtProperty[string] newVersion: + read = getNewVersion + notify = newVersionChanged \ No newline at end of file diff --git a/src/app/modules/main/profile_section/module.nim b/src/app/modules/main/profile_section/module.nim index 5939014c0f..30f86a3f68 100644 --- a/src/app/modules/main/profile_section/module.nim +++ b/src/app/modules/main/profile_section/module.nim @@ -46,7 +46,7 @@ proc newModule*[T](delegate: T, settingsService: settings_service.ServiceInterface, profileService: profile_service.ServiceInterface, contactsService: contacts_service.Service, - aboutService: about_service.ServiceInterface, + aboutService: about_service.Service, languageService: language_service.ServiceInterface, mnemonicService: mnemonic_service.ServiceInterface, privacyService: privacy_service.ServiceInterface, @@ -65,7 +65,7 @@ proc newModule*[T](delegate: T, result.languageModule = language_module.newModule(result, languageService) result.mnemonicModule = mnemonic_module.newModule(result, mnemonicService) result.privacyModule = privacy_module.newModule(result, privacyService, accountsService) - result.aboutModule = about_module.newModule(result, aboutService) + result.aboutModule = about_module.newModule(result, events, aboutService) result.advancedModule = advanced_module.newModule(result, settingsService, nodeConfigurationService) singletonInstance.engine.setRootContextProperty("profileSectionModule", result.viewVariant) diff --git a/src/app_service/service/about/async_tasks.nim b/src/app_service/service/about/async_tasks.nim new file mode 100644 index 0000000000..9d9866ab4b --- /dev/null +++ b/src/app_service/service/about/async_tasks.nim @@ -0,0 +1,27 @@ +include ../../common/json_utils +include ../../../app/core/tasks/common + +type CheckForNewVersionTaskArg = ref object of QObjectTaskArg + + +proc getLatestVersionJSON(): string = + var version = "" + var url = "" + + try: + debug "Getting latest version information" + let latestVersion = getLatestVersion() + version = latestVersion.version + url = latestVersion.url + except Exception as e: + error "Error while getting latest version information", msg = e.msg + + result = $(%*{ + "version": version, + "url": url + }) + +const checkForUpdatesTask: Task = proc(argEncoded: string) {.gcsafe, nimcall.} = + debug "Check for updates - async" + let arg = decode[CheckForNewVersionTaskArg](argEncoded) + arg.finish(getLatestVersionJSON()) \ No newline at end of file diff --git a/src/app_service/service/about/dto.nim b/src/app_service/service/about/dto.nim deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/src/app_service/service/about/service.nim b/src/app_service/service/about/service.nim index 479d5510d2..5a464af470 100644 --- a/src/app_service/service/about/service.nim +++ b/src/app_service/service/about/service.nim @@ -1,11 +1,15 @@ -import json, json_serialization, sequtils, chronicles -# import status/statusgo_backend_new/custom_tokens as custom_tokens +import NimQml, json, chronicles -import status/statusgo_backend/settings as status_go_settings +import eventemitter +import ../../../app/core/[main] +import ../../../app/core/tasks/[qt, threadpool] -import ./service_interface, ./dto +import ../settings/service as settings_service +import ../network/types +import status/statusgo_backend_new/about as status_about +import ./update -export service_interface +include async_tasks logScope: topics = "settings-service" @@ -13,27 +17,70 @@ logScope: # This is changed during compilation by reading the VERSION file const DESKTOP_VERSION {.strdefine.} = "0.0.0" -type - Service* = ref object of ServiceInterface - # profile: Dto +type + VersionArgs* = ref object of Args + version*: string -method delete*(self: Service) = - discard +const SIGNAL_VERSION_FETCHED* = "SIGNAL_VERSION_FETCHED" -proc newService*(): Service = - result = Service() +QtObject: + type + Service* = ref object of QObject + events: EventEmitter + threadpool: ThreadPool + settingsService: settings_service.Service -method init*(self: Service) = - try: - echo "init" + # Forward declaration + proc asyncRequestLatestVersion(self: Service) - except Exception as e: - let errDesription = e.msg - error "error: ", errDesription - return + proc delete*(self: Service) = + discard -method getAppVersion*(self: Service): string = - return DESKTOP_VERSION + proc newService*( + events: EventEmitter, + threadpool: ThreadPool, + settingsService: settings_service.Service + ): Service = + result = Service() + result.events = events + result.threadpool = threadpool + result.settingsService = settingsService -method getNodeVersion*(self: Service): string = - return status_go_settings.getWeb3ClientVersion() + proc init*(self: Service) = + # TODO uncomment this once the latest version calls is fixed + # to fix this, you need to re-upload the version and files to IPFS and pin them + # self.asyncRequestLatestVersion() + discard + + proc getAppVersion*(self: Service): string = + return DESKTOP_VERSION + + proc getNodeVersion*(self: Service): string = + try: + return status_about.getWeb3ClientVersion().result.getStr + except Exception as e: + error "Error getting Node version" + + proc asyncRequestLatestVersion(self: Service) = + let networkType = self.settingsService.getCurrentNetwork().toNetworkType() + if networkType != NetworkType.Mainnet: return + let arg = CheckForNewVersionTaskArg( + tptr: cast[ByteAddress](checkForUpdatesTask), + vptr: cast[ByteAddress](self.vptr), + slot: "latestVersionSuccess" + ) + self.threadpool.start(arg) + + proc checkForUpdates*(self: Service) = + self.asyncRequestLatestVersion() + + proc latestVersionSuccess*(self: Service, latestVersionJSON: string) {.slot.} = + let latestVersionObj = parseJSON(latestVersionJSON) + let latestVersion = latestVersionObj{"version"}.getStr() + if latestVersion == "": return + + let available = isNewer(DESKTOP_VERSION, latestVersion) + latestVersionObj["available"] = newJBool(available) + + self.events.emit(SIGNAL_VERSION_FETCHED, + VersionArgs(version: $(%*latestVersionObj))) diff --git a/src/app_service/service/about/service_interface.nim b/src/app_service/service/about/service_interface.nim deleted file mode 100644 index d63c502aaf..0000000000 --- a/src/app_service/service/about/service_interface.nim +++ /dev/null @@ -1,22 +0,0 @@ -import dto - -export dto - -type - ServiceInterface* {.pure inheritable.} = ref object of RootObj - ## Abstract class for this service access. - -method delete*(self: ServiceInterface) {.base.} = - raise newException(ValueError, "No implementation available") - -method init*(self: ServiceInterface) {.base.} = - raise newException(ValueError, "No implementation available") - -# method getPubKey*(self: ServiceInterface): string {.base.} = -# raise newException(ValueError, "No implementation available") - -method getAppVersion*(self: ServiceInterface): string {.base.} = - raise newException(ValueError, "No implementation available") - -method getNodeVersion*(self: ServiceInterface): string {.base.} = - raise newException(ValueError, "No implementation available") diff --git a/src/app_service/service/about/update.nim b/src/app_service/service/about/update.nim new file mode 100644 index 0000000000..871ee7d84a --- /dev/null +++ b/src/app_service/service/about/update.nim @@ -0,0 +1,55 @@ +from stew/base32 import nil +from stew/base58 import nil +import chronicles, httpclient, net +import strutils +import semver + +import ../provider/service as provider_service +import ../ens/utils as ens_utils +import status/statusgo_backend_new/ens as status_ens + +const APP_UPDATES_ENS* = "desktop.status.eth" +const CHECK_VERSION_TIMEOUT_MS* = 5000 + +type + VersionInfo* = object + version*: string + url*: string + +proc getLatestVersion*(): VersionInfo = + let contentHash = contenthash(APP_UPDATES_ENS) + if contentHash == "": + raise newException(ValueError, "ENS does not have a content hash") + + var url: string = "" + + let decodedHash = contentHash.decodeENSContentHash() + + case decodedHash[0]: + of ENSType.IPFS: + let + base58bytes = base58.decode(base58.BTCBase58, decodedHash[1]) + base32Hash = base32.encode(base32.Base32Lower, base58bytes) + + url = "https://" & base32Hash & IPFS_GATEWAY + + of ENSType.SWARM: + url = "https://" & SWARM_GATEWAY & "/bzz:/" & decodedHash[1] + + of ENSType.IPNS: + url = "https://" & decodedHash[1] + + else: + warn "Unknown content for", contentHash + raise newException(ValueError, "Unknown content for " & contentHash) + + # Read version from folder + let secureSSLContext = newContext() + let client = newHttpClient(sslContext = secureSSLContext, timeout = CHECK_VERSION_TIMEOUT_MS) + result.version = client.getContent(url & "/VERSION").strip() + result.url = url + +proc isNewer*(currentVersion, versionToCheck: string): bool = + let lastVersion = parseVersion(versionToCheck) + let currVersion = parseVersion(currentVersion) + result = lastVersion > currVersion diff --git a/src/app_service/service/ens/service.nim b/src/app_service/service/ens/service.nim deleted file mode 100644 index 042156d278..0000000000 --- a/src/app_service/service/ens/service.nim +++ /dev/null @@ -1,29 +0,0 @@ -import Tables, json, chronicles -import chronicles -include ../../common/json_utils -import service_interface -import status/statusgo_backend_new/ens as status_go -export service_interface - -logScope: - topics = "ens-service" - -type - Service* = ref object of ServiceInterface - -method delete*(self: Service) = - discard - -proc newService*(): Service = - result = Service() - -method init*(self: Service) = - discard - -method resourceUrl*(self: Service, username: string): (string, string, string) = - try: - let response = status_go.resourceURL(chainId=1, username=username) - return (response.result{"Scheme"}.getStr, response.result{"Host"}.getStr, response.result{"Path"}.getStr) - except Exception as e: - error "Error getting ENS resourceUrl", username=username, exception=e.msg - raise \ No newline at end of file diff --git a/src/app_service/service/ens/utils.nim b/src/app_service/service/ens/utils.nim new file mode 100644 index 0000000000..66fe59a272 --- /dev/null +++ b/src/app_service/service/ens/utils.nim @@ -0,0 +1,69 @@ +import Tables, json, chronicles, strutils +import sets +import options +import chronicles, libp2p/[multihash, multicodec, cid] +import nimcrypto +include ../../common/json_utils +import status/statusgo_backend_new/ens as status_go + +logScope: + topics = "ens-utils" + +type + ENSType* {.pure.} = enum + IPFS, + SWARM, + IPNS, + UNKNOWN + + +proc getContentHash*(ens: string): Option[string] = + try: + let contentHash = status_go.contenthash(ens) + if contentHash != "": + return some(contentHash) + except Exception as e: + let errDescription = e.msg + error "error: ", errDescription + return none(string) + +proc decodeENSContentHash*(value: string): tuple[ensType: ENSType, output: string] = + if value == "": + return (ENSType.UNKNOWN, "") + + if value[0..5] == "e40101": + return (ENSType.SWARM, value.split("1b20")[1]) + + if value[0..7] == "e3010170": + try: + let defaultCodec = parseHexInt("70") #dag-pb + var codec = defaultCodec # no codec specified + var codecStartIdx = 2 # idx of where codec would start if it was specified + # handle the case when starts with 0xe30170 instead of 0xe3010170 + if value[2..5] == "0101": + codecStartIdx = 6 + codec = parseHexInt(value[6..7]) + elif value[2..3] == "01" and value[4..5] != "12": + codecStartIdx = 4 + codec = parseHexInt(value[4..5]) + + # strip the info we no longer need + var multiHashStr = value[codecStartIdx + 2..