diff --git a/src/app/core/signals/remote_signals/signal_type.nim b/src/app/core/signals/remote_signals/signal_type.nim index 16ff444107..e819360766 100644 --- a/src/app/core/signals/remote_signals/signal_type.nim +++ b/src/app/core/signals/remote_signals/signal_type.nim @@ -38,6 +38,7 @@ type SignalType* {.pure.} = enum HistoryArchivesSeeding = "community.historyArchivesSeeding" HistoryArchivesUnseeded = "community.historyArchivesUnseeded" HistoryArchiveDownloaded = "community.historyArchiveDownloaded" + UpdateAvailable = "update.available" Unknown proc event*(self:SignalType):string = diff --git a/src/app/core/signals/remote_signals/update_available.nim b/src/app/core/signals/remote_signals/update_available.nim new file mode 100644 index 0000000000..3061918a48 --- /dev/null +++ b/src/app/core/signals/remote_signals/update_available.nim @@ -0,0 +1,17 @@ +import json +import base +import signal_type + +type UpdateAvailableSignal* = ref object of Signal + available*: bool + version*: string + url*: string + +proc fromEvent*(T: type UpdateAvailableSignal, jsonSignal: JsonNode): UpdateAvailableSignal = + result = UpdateAvailableSignal() + result.signalType = SignalType.UpdateAvailable + if jsonSignal["event"].kind != JNull: + result.available = jsonSignal["event"]["available"].getBool() + result.version = jsonSignal["event"]["version"].getStr() + result.url = jsonSignal["event"]["url"].getStr() + diff --git a/src/app/core/signals/signals_manager.nim b/src/app/core/signals/signals_manager.nim index 99c8090540..48e1cf0987 100644 --- a/src/app/core/signals/signals_manager.nim +++ b/src/app/core/signals/signals_manager.nim @@ -92,6 +92,7 @@ QtObject: of SignalType.HistoryArchivesSeeding: HistoryArchivesSignal.historyArchivesSeedingFromEvent(jsonSignal) of SignalType.HistoryArchivesUnseeded: HistoryArchivesSignal.historyArchivesUnseededFromEvent(jsonSignal) of SignalType.HistoryArchiveDownloaded: HistoryArchivesSignal.historyArchiveDownloadedFromEvent(jsonSignal) + of SignalType.UpdateAvailable: UpdateAvailableSignal.fromEvent(jsonSignal) else: Signal() result.signalType = signalType diff --git a/src/app/core/signals/types.nim b/src/app/core/signals/types.nim index fc394c629f..7894b643b4 100644 --- a/src/app/core/signals/types.nim +++ b/src/app/core/signals/types.nim @@ -1,7 +1,7 @@ {.used.} import ./remote_signals/[base, chronicles_logs, community, discovery_summary, envelope, expired, mailserver, messages, -peerstats, signal_type, stats, wallet, whisper_filter, keycard] +peerstats, signal_type, stats, wallet, whisper_filter, keycard, update_available] export base, chronicles_logs, community, discovery_summary, envelope, expired, mailserver, messages, peerstats, - signal_type, stats, wallet, whisper_filter, keycard + signal_type, stats, wallet, whisper_filter, keycard, update_available diff --git a/src/app/modules/main/profile_section/about/controller.nim b/src/app/modules/main/profile_section/about/controller.nim index 0536ad70ac..d49ce6aee3 100644 --- a/src/app/modules/main/profile_section/about/controller.nim +++ b/src/app/modules/main/profile_section/about/controller.nim @@ -24,7 +24,7 @@ proc delete*(self: Controller) = proc init*(self: Controller) = self.events.on(SIGNAL_VERSION_FETCHED) do(e: Args): let args = VersionArgs(e) - self.delegate.versionFetched(args.version) + self.delegate.versionFetched(args.available, args.version, args.url) proc getAppVersion*(self: Controller): string = return self.aboutService.getAppVersion() 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 9ed0b3b4e2..9fa10db4c4 100644 --- a/src/app/modules/main/profile_section/about/io_interface.nim +++ b/src/app/modules/main/profile_section/about/io_interface.nim @@ -17,7 +17,7 @@ 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.} = +method versionFetched*(self: AccessInterface, available: bool, version: string, url: string) {.base.} = raise newException(ValueError, "No implementation available") method checkForUpdates*(self: AccessInterface) {.base.} = diff --git a/src/app/modules/main/profile_section/about/module.nim b/src/app/modules/main/profile_section/about/module.nim index 6a412f1f6f..243aeb3b95 100644 --- a/src/app/modules/main/profile_section/about/module.nim +++ b/src/app/modules/main/profile_section/about/module.nim @@ -55,5 +55,5 @@ method getNodeVersion*(self: Module): string = method checkForUpdates*(self: Module) = self.controller.checkForUpdates() -method versionFetched*(self: Module, version: string) = - self.view.versionFetched(version) +method versionFetched*(self: Module, available: bool, version: string, url: string) = + self.view.versionFetched(available, version, url) diff --git a/src/app/modules/main/profile_section/about/view.nim b/src/app/modules/main/profile_section/about/view.nim index caba81f12e..835b7adacf 100644 --- a/src/app/modules/main/profile_section/about/view.nim +++ b/src/app/modules/main/profile_section/about/view.nim @@ -7,7 +7,6 @@ QtObject: type View* = ref object of QObject delegate: io_interface.AccessInterface - newVersion*: string fetching*: bool proc delete*(self: View) = @@ -18,14 +17,6 @@ QtObject: result.QObject.setup result.delegate = delegate result.fetching = false - result.newVersion = $(%*{ - "available": false, - "version": "", - "url": "" - }) - - proc load*(self: View) = - self.delegate.viewDidLoad() proc getCurrentVersion*(self: View): string {.slot.} = return self.delegate.getAppVersion() @@ -33,31 +24,26 @@ QtObject: proc nodeVersion*(self: View): string {.slot.} = return self.delegate.getNodeVersion() - proc appVersionFetched(self: View) {.signal.} + proc appVersionFetched*(self: View, available: bool, version: string, url: string) {.signal.} + proc fetchingChanged(self: View) {.signal.} - proc versionFetched*(self: View, version: string) = - self.newVersion = version + proc versionFetched*(self: View, available: bool, version: string, url: string) = self.fetching = false self.fetchingChanged() - self.appVersionFetched() + self.appVersionFetched(available, version, url) proc checkForUpdates*(self: View) {.slot.} = self.fetching = true self.fetchingChanged() self.delegate.checkForUpdates() - proc getNewVersion*(self: View): string {.slot.} = - return self.newVersion - - QtProperty[string] newVersion: - read = getNewVersion - notify = appVersionFetched - - proc getFetching*(self: View): bool {.slot.} = return self.fetching QtProperty[bool] fetching: read = getFetching notify = fetchingChanged + + proc load*(self: View) = + self.delegate.viewDidLoad() diff --git a/src/app_service/service/about/async_tasks.nim b/src/app_service/service/about/async_tasks.nim deleted file mode 100644 index 247d4a90a9..0000000000 --- a/src/app_service/service/about/async_tasks.nim +++ /dev/null @@ -1,26 +0,0 @@ -include ../../common/json_utils -include ../../../app/core/tasks/common - -proc getLatestVersionJSON(): string = - var jsonObj = %*{ - "version": "", - "url": "" - } - try: - debug "Getting latest version information" - - let latestVersion = getLatestVersion() - - jsonObj["version"] = %*latestVersion.version - jsonObj["url"] = %*latestVersion.url - - except Exception as e: - error "Error while getting latest version information", msg = e.msg - - return $jsonObj - -const checkForUpdatesTask: Task = proc(argEncoded: string) {.gcsafe, nimcall.} = - debug "Check for updates - async" - let arg = decode[QObjectTaskArg](argEncoded) - let response = getLatestVersionJSON() - arg.finish(response) diff --git a/src/app_service/service/about/service.nim b/src/app_service/service/about/service.nim index 88c40fd609..fff58222dc 100644 --- a/src/app_service/service/about/service.nim +++ b/src/app_service/service/about/service.nim @@ -1,22 +1,29 @@ import NimQml, json, chronicles -import ../../../app/core/eventemitter -import ../../../app/core/tasks/[qt, threadpool] -import ../../../constants - import ../settings/service as settings_service import ../network/types +import ../../../app/core/eventemitter +import ../../../app/core/tasks/[qt, threadpool] +import ../../../app/core/signals/types as signal_types import ../../../backend/backend -import ./update - -include async_tasks +import ../../../backend/about as status_about +import ../../../constants logScope: topics = "about-service" +# This is changed during compilation by reading the VERSION file +const DESKTOP_VERSION {.strdefine.} = "0.0.0" + +const APP_UPDATES_ENS* = "desktop.status.eth" + type VersionArgs* = ref object of Args + available*: bool version*: string + url*: string + + # Signals which may be emitted by this service: const SIGNAL_VERSION_FETCHED* = "versionFetched" @@ -28,9 +35,6 @@ QtObject: threadpool: ThreadPool settingsService: settings_service.Service - # Forward declaration - proc asyncRequestLatestVersion(self: Service) - proc delete*(self: Service) = self.QObject.delete @@ -43,12 +47,6 @@ QtObject: result.events = events result.threadpool = threadpool - 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 @@ -58,29 +56,19 @@ QtObject: except Exception as e: error "Error getting Node version" - proc emitSignal(self: Service, versionJsonObj: JsonNode) = - self.events.emit(SIGNAL_VERSION_FETCHED, VersionArgs(version: $versionJsonObj)) - - proc asyncRequestLatestVersion(self: Service) = - let arg = QObjectTaskArg( - tptr: cast[ByteAddress](checkForUpdatesTask), - vptr: cast[ByteAddress](self.vptr), - slot: "latestVersionSuccess" - ) - self.threadpool.start(arg) - proc checkForUpdates*(self: Service) = - self.asyncRequestLatestVersion() + try: + discard status_about.checkForUpdates(types.Mainnet, APP_UPDATES_ENS, DESKTOP_VERSION) + except Exception as e: + error "Error checking for updates", msg=e.msg - proc latestVersionSuccess*(self: Service, latestVersionJSON: string) {.slot.} = - var latestVersionObj = parseJSON(latestVersionJSON) - - var newVersionAvailable = false - let latestVersion = latestVersionObj{"version"}.getStr() - if(latestVersion.len > 0): - newVersionAvailable = isNewer(DESKTOP_VERSION, latestVersion) - - latestVersionObj["available"] = newJBool(newVersionAvailable) - - self.emitSignal(latestVersionObj) + proc init*(self: Service) = + self.events.on(SignalType.UpdateAvailable.event) do(e: Args): + var updateSignal = UpdateAvailableSignal(e) + self.events.emit(SIGNAL_VERSION_FETCHED, VersionArgs( + available: updateSignal.available, + version: updateSignal.version, + url: updateSignal.url)) + self.checkForUpdates() + diff --git a/src/app_service/service/about/update.nim b/src/app_service/service/about/update.nim deleted file mode 100644 index 1f3a42f02c..0000000000 --- a/src/app_service/service/about/update.nim +++ /dev/null @@ -1,32 +0,0 @@ -import json, chronicles, httpclient, net, options -import strutils -import semver - -import ../../../backend/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 response = status_ens.resourceUrl(chainId=1, username=APP_UPDATES_ENS) - let host = response.result{"Host"}.getStr - if host == "": - raise newException(ValueError, "ENS does not have a content hash") - - let url = "https://" & host & response.result{"Path"}.getStr - - # 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 \ No newline at end of file diff --git a/src/backend/about.nim b/src/backend/about.nim new file mode 100644 index 0000000000..8e2f09fd77 --- /dev/null +++ b/src/backend/about.nim @@ -0,0 +1,9 @@ +import json +import ./core +import response_type + +export response_type + +proc checkForUpdates*(chainId: int, ensAddress: string, currVersion: string): RpcResponse[JsonNode] {.raises: [Exception].} = + let payload = %* [chainId, ensAddress, currVersion] + result = callPrivateRPC("updates_check", payload) diff --git a/ui/app/AppLayouts/stores/RootStore.qml b/ui/app/AppLayouts/stores/RootStore.qml index 7b4b505484..430094c663 100644 --- a/ui/app/AppLayouts/stores/RootStore.qml +++ b/ui/app/AppLayouts/stores/RootStore.qml @@ -9,6 +9,20 @@ QtObject { property var communitiesModuleInst: communitiesModule property var observedCommunity: communitiesModuleInst.observedCommunity + property bool newVersionAvailable: false + property string latestVersion + property string downloadURL + + function setLatestVersionInfo(newVersionAvailable, latestVersion, downloadURL) { + root.newVersionAvailable = newVersionAvailable; + root.latestVersion = latestVersion; + root.downloadURL = downloadURL; + } + + function resetLastVersion(){ + root.newVersionAvailable = false + } + property AppSearchStore appSearchStore: AppSearchStore { appSearchModule: root.mainModuleInst.appSearchModule } diff --git a/ui/app/mainui/AppMain.qml b/ui/app/mainui/AppMain.qml index 2efe9b1893..232a3de86e 100644 --- a/ui/app/mainui/AppMain.qml +++ b/ui/app/mainui/AppMain.qml @@ -43,21 +43,14 @@ Item { property RootStore rootStore: RootStore { } // set from main.qml property var sysPalette - property var newVersionJSON: { - try { - return JSON.parse(rootStore.aboutModuleInst.newVersion) - } catch (e) { - console.error("Error parsing version data", e) - return {} - } - } signal openContactsPopup() Connections { target: rootStore.aboutModuleInst onAppVersionFetched: { - if (!newVersionJSON.available) { + rootStore.setLatestVersionInfo(available, version, url); + if (!available) { versionUpToDate.show() } else { versionWarning.show() @@ -81,10 +74,10 @@ Item { onOpenDownloadModalRequested: { const downloadPage = downloadPageComponent.createObject(appMain, { - newVersionAvailable: newVersionJSON.available, - downloadURL: newVersionJSON.url, + newVersionAvailable: available, + downloadURL: url, currentVersion: rootStore.profileSectionStore.getCurrentVersion(), - newVersion: newVersionJSON.version + newVersion: version }) return downloadPage } @@ -414,13 +407,16 @@ Item { id: versionWarning width: parent.width height: 32 - visible: !!newVersionJSON.available + visible: appMain.rootStore.newVersionAvailable color: Style.current.green btnWidth: 100 - text: qsTr("A new version of Status (%1) is available").arg(newVersionJSON.version) + text: qsTr("A new version of Status (%1) is available").arg(appMain.rootStore.latestVersion) btnText: qsTr("Download") onClick: function(){ - Global.openDownloadModal() + Global.openDownloadModal(appMain.rootStore.newVersionAvailable, appMain.rootStore.latestVersion, appMain.rootStore.downloadURL) + } + onClosed: { + appMain.rootStore.resetLastVersion(); } function show() { diff --git a/ui/imports/shared/panels/ModuleWarning.qml b/ui/imports/shared/panels/ModuleWarning.qml index 51dba0fbd7..d8519683e9 100644 --- a/ui/imports/shared/panels/ModuleWarning.qml +++ b/ui/imports/shared/panels/ModuleWarning.qml @@ -10,20 +10,23 @@ import "./" Rectangle { id: root height: visible ? 32 : 0 + implicitHeight: height color: Style.current.red property string text: "" property string btnText: "" property int btnWidth: 58 + property bool closing: false property var onClick: function() {} + signal closed() + function close() { closeBtn.clicked(null) + closed(); } - signal closed - Row { spacing: Style.current.halfPadding anchors.horizontalCenter: parent.horizontalCenter @@ -89,10 +92,21 @@ Rectangle { id: closeBtn anchors.fill: closeImg cursorShape: Qt.PointingHandCursor - onClicked: ParallelAnimation { - PropertyAnimation { target: root; property: "visible"; to: false; } - PropertyAnimation { target: root; property: "y"; to: -1 * root.height } - onFinished: root.closed() + onClicked: { + closing = true + } + } + + ParallelAnimation { + running: closing + PropertyAnimation { target: root; property: "visible"; to: false; } + PropertyAnimation { target: root; property: "y"; to: -1 * root.height } + onRunningChanged: { + if(!running){ + closing = false; + root.y = 0; + root.closed(); + } } } } diff --git a/ui/imports/shared/status/StatusSectionDescItem.qml b/ui/imports/shared/status/StatusSectionDescItem.qml index c59458c1f6..61ea8ea117 100644 --- a/ui/imports/shared/status/StatusSectionDescItem.qml +++ b/ui/imports/shared/status/StatusSectionDescItem.qml @@ -15,6 +15,9 @@ Item { property alias tooltipUnder: copyToClipboardBtn.tooltipUnder property var store + property alias textFont: name.font + property alias textColor: name.color + id: root width: parent.width height: name.height diff --git a/ui/imports/utils/Global.qml b/ui/imports/utils/Global.qml index ff32b5a76e..36dc4753d2 100644 --- a/ui/imports/utils/Global.qml +++ b/ui/imports/utils/Global.qml @@ -19,7 +19,8 @@ QtObject { signal openImagePopup(var image, var contextMenu) signal openLinkInBrowser(string link) signal openChooseBrowserPopup(string link) - signal openDownloadModalRequested() + signal openPopupRequested(var popupComponent, var params) + signal openDownloadModalRequested(bool available, string version, string url) signal settingsLoaded() signal openBackUpSeedPopup() signal openCreateChatView() @@ -40,8 +41,8 @@ QtObject { return popup; } - function openDownloadModal(){ - openDownloadModalRequested(); + function openDownloadModal(available, version, url){ + openDownloadModalRequested(available, version, url); } function changeAppSectionBySectionType(sectionType, subsection = 0) {