feat: check for updates (using status-go)

This commit is contained in:
Richard Ramos 2022-03-03 17:00:52 -04:00
parent 04037d1d64
commit 75a3ff858c
17 changed files with 120 additions and 148 deletions

View File

@ -38,6 +38,7 @@ type SignalType* {.pure.} = enum
HistoryArchivesSeeding = "community.historyArchivesSeeding" HistoryArchivesSeeding = "community.historyArchivesSeeding"
HistoryArchivesUnseeded = "community.historyArchivesUnseeded" HistoryArchivesUnseeded = "community.historyArchivesUnseeded"
HistoryArchiveDownloaded = "community.historyArchiveDownloaded" HistoryArchiveDownloaded = "community.historyArchiveDownloaded"
UpdateAvailable = "update.available"
Unknown Unknown
proc event*(self:SignalType):string = proc event*(self:SignalType):string =

View File

@ -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()

View File

@ -92,6 +92,7 @@ QtObject:
of SignalType.HistoryArchivesSeeding: HistoryArchivesSignal.historyArchivesSeedingFromEvent(jsonSignal) of SignalType.HistoryArchivesSeeding: HistoryArchivesSignal.historyArchivesSeedingFromEvent(jsonSignal)
of SignalType.HistoryArchivesUnseeded: HistoryArchivesSignal.historyArchivesUnseededFromEvent(jsonSignal) of SignalType.HistoryArchivesUnseeded: HistoryArchivesSignal.historyArchivesUnseededFromEvent(jsonSignal)
of SignalType.HistoryArchiveDownloaded: HistoryArchivesSignal.historyArchiveDownloadedFromEvent(jsonSignal) of SignalType.HistoryArchiveDownloaded: HistoryArchivesSignal.historyArchiveDownloadedFromEvent(jsonSignal)
of SignalType.UpdateAvailable: UpdateAvailableSignal.fromEvent(jsonSignal)
else: Signal() else: Signal()
result.signalType = signalType result.signalType = signalType

View File

@ -1,7 +1,7 @@
{.used.} {.used.}
import ./remote_signals/[base, chronicles_logs, community, discovery_summary, envelope, expired, mailserver, messages, 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, 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

View File

@ -24,7 +24,7 @@ proc delete*(self: Controller) =
proc init*(self: Controller) = proc init*(self: Controller) =
self.events.on(SIGNAL_VERSION_FETCHED) do(e: Args): self.events.on(SIGNAL_VERSION_FETCHED) do(e: Args):
let args = VersionArgs(e) let args = VersionArgs(e)
self.delegate.versionFetched(args.version) self.delegate.versionFetched(args.available, args.version, args.url)
proc getAppVersion*(self: Controller): string = proc getAppVersion*(self: Controller): string =
return self.aboutService.getAppVersion() return self.aboutService.getAppVersion()

View File

@ -17,7 +17,7 @@ method getAppVersion*(self: AccessInterface): string {.base.} =
method getNodeVersion*(self: AccessInterface): string {.base.} = method getNodeVersion*(self: AccessInterface): string {.base.} =
raise newException(ValueError, "No implementation available") 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") raise newException(ValueError, "No implementation available")
method checkForUpdates*(self: AccessInterface) {.base.} = method checkForUpdates*(self: AccessInterface) {.base.} =

View File

@ -55,5 +55,5 @@ method getNodeVersion*(self: Module): string =
method checkForUpdates*(self: Module) = method checkForUpdates*(self: Module) =
self.controller.checkForUpdates() self.controller.checkForUpdates()
method versionFetched*(self: Module, version: string) = method versionFetched*(self: Module, available: bool, version: string, url: string) =
self.view.versionFetched(version) self.view.versionFetched(available, version, url)

View File

@ -7,7 +7,6 @@ QtObject:
type type
View* = ref object of QObject View* = ref object of QObject
delegate: io_interface.AccessInterface delegate: io_interface.AccessInterface
newVersion*: string
fetching*: bool fetching*: bool
proc delete*(self: View) = proc delete*(self: View) =
@ -18,14 +17,6 @@ QtObject:
result.QObject.setup result.QObject.setup
result.delegate = delegate result.delegate = delegate
result.fetching = false result.fetching = false
result.newVersion = $(%*{
"available": false,
"version": "",
"url": ""
})
proc load*(self: View) =
self.delegate.viewDidLoad()
proc getCurrentVersion*(self: View): string {.slot.} = proc getCurrentVersion*(self: View): string {.slot.} =
return self.delegate.getAppVersion() return self.delegate.getAppVersion()
@ -33,31 +24,26 @@ QtObject:
proc nodeVersion*(self: View): string {.slot.} = proc nodeVersion*(self: View): string {.slot.} =
return self.delegate.getNodeVersion() 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 fetchingChanged(self: View) {.signal.}
proc versionFetched*(self: View, version: string) = proc versionFetched*(self: View, available: bool, version: string, url: string) =
self.newVersion = version
self.fetching = false self.fetching = false
self.fetchingChanged() self.fetchingChanged()
self.appVersionFetched() self.appVersionFetched(available, version, url)
proc checkForUpdates*(self: View) {.slot.} = proc checkForUpdates*(self: View) {.slot.} =
self.fetching = true self.fetching = true
self.fetchingChanged() self.fetchingChanged()
self.delegate.checkForUpdates() 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.} = proc getFetching*(self: View): bool {.slot.} =
return self.fetching return self.fetching
QtProperty[bool] fetching: QtProperty[bool] fetching:
read = getFetching read = getFetching
notify = fetchingChanged notify = fetchingChanged
proc load*(self: View) =
self.delegate.viewDidLoad()

View File

@ -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)

View File

@ -1,22 +1,29 @@
import NimQml, json, chronicles import NimQml, json, chronicles
import ../../../app/core/eventemitter
import ../../../app/core/tasks/[qt, threadpool]
import ../../../constants
import ../settings/service as settings_service import ../settings/service as settings_service
import ../network/types 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 ../../../backend/backend
import ./update import ../../../backend/about as status_about
import ../../../constants
include async_tasks
logScope: logScope:
topics = "about-service" 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 type
VersionArgs* = ref object of Args VersionArgs* = ref object of Args
available*: bool
version*: string version*: string
url*: string
# Signals which may be emitted by this service: # Signals which may be emitted by this service:
const SIGNAL_VERSION_FETCHED* = "versionFetched" const SIGNAL_VERSION_FETCHED* = "versionFetched"
@ -28,9 +35,6 @@ QtObject:
threadpool: ThreadPool threadpool: ThreadPool
settingsService: settings_service.Service settingsService: settings_service.Service
# Forward declaration
proc asyncRequestLatestVersion(self: Service)
proc delete*(self: Service) = proc delete*(self: Service) =
self.QObject.delete self.QObject.delete
@ -43,12 +47,6 @@ QtObject:
result.events = events result.events = events
result.threadpool = threadpool 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 = proc getAppVersion*(self: Service): string =
return DESKTOP_VERSION return DESKTOP_VERSION
@ -58,29 +56,19 @@ QtObject:
except Exception as e: except Exception as e:
error "Error getting Node version" 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) = 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.} = proc init*(self: Service) =
var latestVersionObj = parseJSON(latestVersionJSON) self.events.on(SignalType.UpdateAvailable.event) do(e: Args):
var updateSignal = UpdateAvailableSignal(e)
var newVersionAvailable = false self.events.emit(SIGNAL_VERSION_FETCHED, VersionArgs(
let latestVersion = latestVersionObj{"version"}.getStr() available: updateSignal.available,
if(latestVersion.len > 0): version: updateSignal.version,
newVersionAvailable = isNewer(DESKTOP_VERSION, latestVersion) url: updateSignal.url))
latestVersionObj["available"] = newJBool(newVersionAvailable)
self.emitSignal(latestVersionObj)
self.checkForUpdates()

View File

@ -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

9
src/backend/about.nim Normal file
View File

@ -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)

View File

@ -9,6 +9,20 @@ QtObject {
property var communitiesModuleInst: communitiesModule property var communitiesModuleInst: communitiesModule
property var observedCommunity: communitiesModuleInst.observedCommunity 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 { property AppSearchStore appSearchStore: AppSearchStore {
appSearchModule: root.mainModuleInst.appSearchModule appSearchModule: root.mainModuleInst.appSearchModule
} }

View File

@ -43,21 +43,14 @@ Item {
property RootStore rootStore: RootStore { } property RootStore rootStore: RootStore { }
// set from main.qml // set from main.qml
property var sysPalette 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() signal openContactsPopup()
Connections { Connections {
target: rootStore.aboutModuleInst target: rootStore.aboutModuleInst
onAppVersionFetched: { onAppVersionFetched: {
if (!newVersionJSON.available) { rootStore.setLatestVersionInfo(available, version, url);
if (!available) {
versionUpToDate.show() versionUpToDate.show()
} else { } else {
versionWarning.show() versionWarning.show()
@ -81,10 +74,10 @@ Item {
onOpenDownloadModalRequested: { onOpenDownloadModalRequested: {
const downloadPage = downloadPageComponent.createObject(appMain, const downloadPage = downloadPageComponent.createObject(appMain,
{ {
newVersionAvailable: newVersionJSON.available, newVersionAvailable: available,
downloadURL: newVersionJSON.url, downloadURL: url,
currentVersion: rootStore.profileSectionStore.getCurrentVersion(), currentVersion: rootStore.profileSectionStore.getCurrentVersion(),
newVersion: newVersionJSON.version newVersion: version
}) })
return downloadPage return downloadPage
} }
@ -414,13 +407,16 @@ Item {
id: versionWarning id: versionWarning
width: parent.width width: parent.width
height: 32 height: 32
visible: !!newVersionJSON.available visible: appMain.rootStore.newVersionAvailable
color: Style.current.green color: Style.current.green
btnWidth: 100 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") btnText: qsTr("Download")
onClick: function(){ onClick: function(){
Global.openDownloadModal() Global.openDownloadModal(appMain.rootStore.newVersionAvailable, appMain.rootStore.latestVersion, appMain.rootStore.downloadURL)
}
onClosed: {
appMain.rootStore.resetLastVersion();
} }
function show() { function show() {

View File

@ -10,20 +10,23 @@ import "./"
Rectangle { Rectangle {
id: root id: root
height: visible ? 32 : 0 height: visible ? 32 : 0
implicitHeight: height
color: Style.current.red color: Style.current.red
property string text: "" property string text: ""
property string btnText: "" property string btnText: ""
property int btnWidth: 58 property int btnWidth: 58
property bool closing: false
property var onClick: function() {} property var onClick: function() {}
signal closed()
function close() { function close() {
closeBtn.clicked(null) closeBtn.clicked(null)
closed();
} }
signal closed
Row { Row {
spacing: Style.current.halfPadding spacing: Style.current.halfPadding
anchors.horizontalCenter: parent.horizontalCenter anchors.horizontalCenter: parent.horizontalCenter
@ -89,10 +92,21 @@ Rectangle {
id: closeBtn id: closeBtn
anchors.fill: closeImg anchors.fill: closeImg
cursorShape: Qt.PointingHandCursor cursorShape: Qt.PointingHandCursor
onClicked: ParallelAnimation { onClicked: {
PropertyAnimation { target: root; property: "visible"; to: false; } closing = true
PropertyAnimation { target: root; property: "y"; to: -1 * root.height } }
onFinished: root.closed() }
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();
}
} }
} }
} }

View File

@ -15,6 +15,9 @@ Item {
property alias tooltipUnder: copyToClipboardBtn.tooltipUnder property alias tooltipUnder: copyToClipboardBtn.tooltipUnder
property var store property var store
property alias textFont: name.font
property alias textColor: name.color
id: root id: root
width: parent.width width: parent.width
height: name.height height: name.height

View File

@ -19,7 +19,8 @@ QtObject {
signal openImagePopup(var image, var contextMenu) signal openImagePopup(var image, var contextMenu)
signal openLinkInBrowser(string link) signal openLinkInBrowser(string link)
signal openChooseBrowserPopup(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 settingsLoaded()
signal openBackUpSeedPopup() signal openBackUpSeedPopup()
signal openCreateChatView() signal openCreateChatView()
@ -40,8 +41,8 @@ QtObject {
return popup; return popup;
} }
function openDownloadModal(){ function openDownloadModal(available, version, url){
openDownloadModalRequested(); openDownloadModalRequested(available, version, url);
} }
function changeAppSectionBySectionType(sectionType, subsection = 0) { function changeAppSectionBySectionType(sectionType, subsection = 0) {