feat: Version Updating

Fixes #1290
This commit is contained in:
Richard Ramos 2021-06-17 14:41:11 -04:00 committed by Iuri Matias
parent 07364c16cf
commit db3b7ecfd9
14 changed files with 765 additions and 528 deletions

3
.gitmodules vendored
View File

@ -103,3 +103,6 @@
[submodule "ui/StatusQ"] [submodule "ui/StatusQ"]
path = ui/StatusQ path = ui/StatusQ
url = https://github.com/status-im/StatusQ url = https://github.com/status-im/StatusQ
[submodule "vendor/semver.nim"]
path = vendor/semver.nim
url = https://github.com/euantorano/semver.nim

View File

@ -164,6 +164,12 @@ endif
NIM_PARAMS += --outdir:./bin NIM_PARAMS += --outdir:./bin
# App version
VERSIONFILE=VERSION
DESKTOP_VERSION=`cat $(VERSIONFILE)`
NIM_PARAMS += -d:DESKTOP_VERSION="$(DESKTOP_VERSION)"
$(DOTHERSIDE): | deps $(DOTHERSIDE): | deps
echo -e $(BUILD_MSG) "DOtherSide" echo -e $(BUILD_MSG) "DOtherSide"
+ cd vendor/DOtherSide && \ + cd vendor/DOtherSide && \

View File

@ -24,4 +24,4 @@ proc delete*(self: UtilsController) =
delete self.view delete self.view
proc init*(self: UtilsController) = proc init*(self: UtilsController) =
discard self.view.asyncCheckForUpdates()

View File

@ -1,6 +1,7 @@
import NimQml, os, strformat, strutils, parseUtils, chronicles import NimQml, os, strformat, strutils, parseUtils, chronicles
import stint import stint
import ../../status/[status, wallet, settings] import ../../status/[status, wallet, settings, updates]
import ../../status/tasks/[qt, task_runner_impl]
import ../../status/stickers import ../../status/stickers
import ../../status/tokens as status_tokens import ../../status/tokens as status_tokens
import ../../status/types import ../../status/types
@ -9,13 +10,25 @@ import ../../status/ens as status_ens
import ../utils/image_utils import ../utils/image_utils
import web3/[ethtypes, conversions] import web3/[ethtypes, conversions]
import stew/byteutils import stew/byteutils
import json
const DESKTOP_VERSION {.strdefine.} = "0.0.0"
type CheckForNewVersionTaskArg = ref object of QObjectTaskArg
QtObject: QtObject:
type UtilsView* = ref object of QObject type UtilsView* = ref object of QObject
status*: Status status*: Status
newVersion*: string
proc setup(self: UtilsView) = proc setup(self: UtilsView) =
self.QObject.setup self.QObject.setup
self.newVersion = $(%*{
"available": false,
"version": "0.0.0",
"url": "about:blank"
})
proc delete*(self: UtilsView) = proc delete*(self: UtilsView) =
self.QObject.delete self.QObject.delete
@ -113,3 +126,66 @@ QtObject:
else: else:
raise newException(IOError, "cannot open: " & filename) raise newException(IOError, "cannot open: " & filename)
proc getCurrentVersion*(self: UtilsView): string {.slot.} =
return DESKTOP_VERSION
proc newVersionChanged(self: UtilsView) {.signal.}
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())
proc asyncRequestLatestVersion[T](self: T, slot: string) =
let arg = CheckForNewVersionTaskArg(
tptr: cast[ByteAddress](checkForUpdatesTask),
vptr: cast[ByteAddress](self.vptr),
slot: slot
)
self.status.tasks.threadpool.start(arg)
proc latestVersionSuccess*(self: UtilsView, 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)
debug "New version?", available, info=latestVersion
self.newVersion = $(%*latestVersionObj)
self.newVersionChanged()
proc checkForUpdates*(self: UtilsView) {.slot.} =
if self.status.settings.getCurrentNetwork() != Network.Mainnet: return
debug "Check for updates - sync"
self.latestVersionSuccess(getLatestVersionJSON())
proc asyncCheckForUpdates*(self: UtilsView) {.slot.} =
if self.status.settings.getCurrentNetwork() != Network.Mainnet: return
self.asyncRequestLatestVersion("latestVersionSuccess")
proc getNewVersion*(self: UtilsView): string {.slot.} =
return self.newVersion
QtProperty[string] newVersion:
read = getNewVersion
notify = newVersionChanged

View File

@ -5,3 +5,5 @@ export STATUSGODIR
export KEYSTOREDIR export KEYSTOREDIR
export TMPDIR export TMPDIR
export LOGDIR export LOGDIR
const APP_UPDATES_ENS* = "desktop.status.eth"

View File

@ -11,11 +11,11 @@ import nbaser
import stew/byteutils import stew/byteutils
from base32 import nil from base32 import nil
const HTTPS_SCHEME = "https" const HTTPS_SCHEME* = "https"
const IPFS_GATEWAY = ".infura.status.im" const IPFS_GATEWAY* = ".infura.status.im"
const SWARM_GATEWAY = "swarm-gateways.net" const SWARM_GATEWAY* = "swarm-gateways.net"
const base58 = "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz" const base58* = "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz"
logScope: logScope:
topics = "provider-model" topics = "provider-model"

44
src/status/updates.nim Normal file
View File

@ -0,0 +1,44 @@
import ens, provider
import stew/byteutils
from base32 import nil
import chronicles, httpclient, net
import nbaser, strutils
import semver
import constants
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 base32Hash = base32.encode(string.fromBytes(base58.decode(decodedHash[1]))).toLowerAscii().replace("=", "")
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)
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

View File

@ -23,7 +23,7 @@ Item {
//% "App version" //% "App version"
name: qsTrId("version") name: qsTrId("version")
//% "Version: %1" //% "Version: %1"
description: qsTrId("version---1").arg("0.1.0-beta.11") description: qsTrId("version---1").arg(utilsModel.getCurrentVersion())
tooltipUnder: true tooltipUnder: true
} }
@ -50,7 +50,8 @@ Item {
parent.font.underline = false parent.font.underline = false
} }
onClicked: { onClicked: {
appMain.openLink("https://github.com/status-im/nim-status-client/releases") utilsModel.checkForUpdates();
openPopup(downloadModalComponent, {newVersionAvailable: newVersionJSON.available, downloadURL: newVersionJSON.url})
} }
} }
} }

View File

@ -3,8 +3,8 @@ import QtQuick.Controls 2.13
import QtQuick.Layouts 1.13 import QtQuick.Layouts 1.13
import "../../../imports" import "../../../imports"
import "../../../shared" import "../../../shared"
import "../Profile/Sections"
import "." import "."
import StatusQ.Layout 0.1 import StatusQ.Layout 0.1
Item { Item {
@ -22,16 +22,8 @@ Item {
id: signPhrasePopup id: signPhrasePopup
} }
SeedPhraseBackupWarning {
id: seedPhraseWarning
width: parent.width
anchors.top: parent.top
}
StatusAppTwoPanelLayout { StatusAppTwoPanelLayout {
anchors.top: seedPhraseWarning.bottom anchors.fill: parent
height: walletView.height - seedPhraseWarning.height
width: walletView.width
Component.onCompleted: { Component.onCompleted: {
if(onboardingModel.firstTimeLogin){ if(onboardingModel.firstTimeLogin){

View File

@ -12,6 +12,8 @@ import "./AppLayouts/Wallet"
import "./AppLayouts/WalletV2" import "./AppLayouts/WalletV2"
import "./AppLayouts/Chat/components" import "./AppLayouts/Chat/components"
import "./AppLayouts/Chat/CommunityComponents" import "./AppLayouts/Chat/CommunityComponents"
import "./AppLayouts/Profile/Sections"
import Qt.labs.platform 1.1 import Qt.labs.platform 1.1
import Qt.labs.settings 1.0 import Qt.labs.settings 1.0
@ -20,12 +22,13 @@ import StatusQ.Controls 0.1
import StatusQ.Layout 0.1 import StatusQ.Layout 0.1
import StatusQ.Popups 0.1 import StatusQ.Popups 0.1
StatusAppLayout {
Item {
id: appMain id: appMain
anchors.fill: parent anchors.fill: parent
property alias appSettings: appSettings property alias appSettings: appSettings
property var newVersionJSON: JSON.parse(utilsModel.newVersion)
property bool profilePopupOpened: false property bool profilePopupOpened: false
property bool networkGuarded: profileModel.network.current === Constants.networkMainnet || (profileModel.network.current === Constants.networkRopsten && appSettings.stickersEnsRopsten) property bool networkGuarded: profileModel.network.current === Constants.networkMainnet || (profileModel.network.current === Constants.networkRopsten && appSettings.stickersEnsRopsten)
@ -136,6 +139,125 @@ StatusAppLayout {
} }
} }
Component {
id: downloadModalComponent
DownloadModal {
onClosed: {
destroy();
}
}
}
Settings {
id: appSettings
fileName: profileModel.settings.settingsFile
property var chatSplitView
property var walletSplitView
property var profileSplitView
property bool communitiesEnabled: false
property bool isWalletEnabled: false
property bool isWalletV2Enabled: false
property bool nodeManagementEnabled: false
property bool isBrowserEnabled: false
property bool isActivityCenterEnabled: false
property bool showOnlineUsers: false
property bool isGifWidgetEnabled: false
property bool isTenorWarningAccepted: false
property bool displayChatImages: false
property bool useCompactMode: true
property bool timelineEnabled: true
property var recentEmojis: []
property var hiddenCommunityWelcomeBanners: []
property var hiddenCommunityBackUpBanners: []
property real volume: 0.2
property int notificationSetting: Constants.notifyAllMessages
property bool notificationSoundsEnabled: true
property bool useOSNotifications: true
property int notificationMessagePreviewSetting: Constants.notificationPreviewNameAndMessage
property bool notifyOnNewRequests: true
property var whitelistedUnfurlingSites: ({})
property bool neverAskAboutUnfurlingAgain: false
property bool hideChannelSuggestions: false
property int fontSize: Constants.fontSizeM
property bool hideSignPhraseModal: false
property bool onlyShowContactsProfilePics: true
property bool quitOnClose: false
property string skinColor: ""
property bool showDeleteMessageWarning: true
// Browser settings
property bool showBrowserSelector: true
property bool openLinksInStatus: true
property bool shouldShowFavoritesBar: true
property string browserHomepage: ""
property int shouldShowBrowserSearchEngine: Constants.browserSearchEngineDuckDuckGo
property int useBrowserEthereumExplorer: Constants.browserEthereumExplorerEtherscan
property bool autoLoadImages: true
property bool javaScriptEnabled: true
property bool errorPageEnabled: true
property bool pluginsEnabled: true
property bool autoLoadIconsForPage: true
property bool touchIconsEnabled: true
property bool webRTCPublicInterfacesOnly: false
property bool devToolsEnabled: false
property bool pdfViewerEnabled: true
property bool compatibilityMode: true
// Ropsten settings
property bool stickersEnsRopsten: false
}
ErrorSound {
id: errorSound
}
Audio {
id: sendMessageSound
audioRole: Audio.NotificationRole
source: "../../../../sounds/send_message.wav"
volume: appSettings.volume
muted: !appSettings.notificationSoundsEnabled
}
Audio {
id: notificationSound
audioRole: Audio.NotificationRole
source: "../../../../sounds/notification.wav"
volume: appSettings.volume
muted: !appSettings.notificationSoundsEnabled
}
ModuleWarning {
id: versionWarning
width: parent.width
visible: newVersionJSON.available
color: Style.current.green
btnWidth: 100
text: qsTr("A new version of Status (%1) is available").arg(newVersionJSON.version)
btnText: qsTr("Download")
onClick: function(){
openPopup(downloadModalComponent, {newVersionAvailable: newVersionJSON.available, downloadURL: newVersionJSON.url})
}
}
ModuleWarning {
id: mnemonicBackupWarning
width: parent.width
visible: appSettings.isWalletEnabled && !profileModel.mnemonic.isBackedUp && appView.currentIndex == Utils.getAppSectionIndex(Constants.wallet)
color: Style.current.red
//% "Back up your seed phrase"
text: qsTrId("back-up-your-seed-phrase")
btnText: qsTr("Back up")
onClick: function() {
openPopup(backupSeedModalComponent);
}
}
StatusAppLayout {
width: parent.width
anchors.top: parent.top
anchors.topMargin: (versionWarning.visible || mnemonicBackupWarning.visible) ? 32 : 0
anchors.bottom: parent.bottom
appNavBar: StatusAppNavBar { appNavBar: StatusAppNavBar {
height: appMain.height height: appMain.height
@ -428,84 +550,6 @@ StatusAppLayout {
} }
} }
Settings {
id: appSettings
fileName: profileModel.settings.settingsFile
property var chatSplitView
property var walletSplitView
property var profileSplitView
property bool communitiesEnabled: false
property bool isWalletEnabled: false
property bool isWalletV2Enabled: false
property bool nodeManagementEnabled: false
property bool isBrowserEnabled: false
property bool isActivityCenterEnabled: false
property bool showOnlineUsers: false
property bool isGifWidgetEnabled: false
property bool isTenorWarningAccepted: false
property bool displayChatImages: false
property bool useCompactMode: true
property bool timelineEnabled: true
property var recentEmojis: []
property var hiddenCommunityWelcomeBanners: []
property var hiddenCommunityBackUpBanners: []
property real volume: 0.2
property int notificationSetting: Constants.notifyAllMessages
property bool notificationSoundsEnabled: true
property bool useOSNotifications: true
property int notificationMessagePreviewSetting: Constants.notificationPreviewNameAndMessage
property bool notifyOnNewRequests: true
property var whitelistedUnfurlingSites: ({})
property bool neverAskAboutUnfurlingAgain: false
property bool hideChannelSuggestions: false
property int fontSize: Constants.fontSizeM
property bool hideSignPhraseModal: false
property bool onlyShowContactsProfilePics: true
property bool quitOnClose: false
property string skinColor: ""
property bool showDeleteMessageWarning: true
// Browser settings
property bool showBrowserSelector: true
property bool openLinksInStatus: true
property bool shouldShowFavoritesBar: true
property string browserHomepage: ""
property int shouldShowBrowserSearchEngine: Constants.browserSearchEngineDuckDuckGo
property int useBrowserEthereumExplorer: Constants.browserEthereumExplorerEtherscan
property bool autoLoadImages: true
property bool javaScriptEnabled: true
property bool errorPageEnabled: true
property bool pluginsEnabled: true
property bool autoLoadIconsForPage: true
property bool touchIconsEnabled: true
property bool webRTCPublicInterfacesOnly: false
property bool devToolsEnabled: false
property bool pdfViewerEnabled: true
property bool compatibilityMode: true
// Ropsten settings
property bool stickersEnsRopsten: false
}
ErrorSound {
id: errorSound
}
Audio {
id: sendMessageSound
audioRole: Audio.NotificationRole
source: "../../../../sounds/send_message.wav"
volume: appSettings.volume
muted: !appSettings.notificationSoundsEnabled
}
Audio {
id: notificationSound
audioRole: Audio.NotificationRole
source: "../../../../sounds/notification.wav"
volume: appSettings.volume
muted: !appSettings.notificationSoundsEnabled
}
Connections { Connections {
target: profileModel.settings target: profileModel.settings
@ -750,7 +794,7 @@ StatusAppLayout {
} }
} }
} }
}
/*##^## /*##^##
Designer { Designer {
D{i:0;formeditorZoom:1.75;height:770;width:1232} D{i:0;formeditorZoom:1.75;height:770;width:1232}

View File

@ -0,0 +1,68 @@
import QtQuick 2.13
import QtQuick.Controls 2.13
import QtQuick.Layouts 1.13
import "../imports"
import "../shared/status"
import "./"
ModalPopup {
id: popup
property bool newVersionAvailable: true
property string downloadURL: "https://github.com/status-im/status-desktop/releases/latest"
height: 240
width: 400
title: newVersionAvailable ?
qsTr("New version available!") :
qsTr("No new version available")
SVGImage {
visible: newVersionAvailable
id: imgExclamation
width: 13.33
height: 13.33
sourceSize.height: height * 2
sourceSize.width: width * 2
anchors.horizontalCenter: parent.horizontalCenter
fillMode: Image.PreserveAspectFit
source: "../app/img/exclamation_outline.svg"
}
StyledText {
visible: newVersionAvailable
id: innerText
text: qsTr("Make sure you have your account password and seed phrase stored. Without them you can lock yourself out of your account and lose funds.")
font.pixelSize: 13
color: Style.current.red
anchors.top: imgExclamation.bottom
anchors.topMargin: Style.current.halfPadding
anchors.left: parent.left
anchors.right: parent.right
wrapMode: Text.WordWrap
horizontalAlignment: Text.AlignHCenter
}
StyledText {
visible: !newVersionAvailable
id: innerText2
text: qsTr("You are up to date!")
font.pixelSize: 15
anchors.left: parent.left
anchors.right: parent.right
wrapMode: Text.WordWrap
}
footer: StatusButton {
id: confirmButton
text: newVersionAvailable ?
qsTr("Download") :
qsTr("Ok")
anchors.right: parent.right
onClicked: newVersionAvailable ? appMain.openLink(downloadURL) : close()
}
}

View File

@ -2,37 +2,39 @@ import QtQuick 2.13
import QtQuick.Controls 2.13 import QtQuick.Controls 2.13
import QtQuick.Layouts 1.13 import QtQuick.Layouts 1.13
import QtGraphicalEffects 1.13 import QtGraphicalEffects 1.13
import "../../../imports" import "../imports"
import "../../../shared"
import "../Profile/Sections"
import "." import "."
Rectangle { Rectangle {
id: root id: root
visible: !profileModel.mnemonic.isBackedUp
height: visible ? 32 : 0 height: visible ? 32 : 0
color: Style.current.red color: Style.current.red
property string text: ""
property string btnText: ""
property int btnWidth: 58
property var onClick: function() {}
Row { Row {
spacing: Style.current.halfPadding spacing: Style.current.halfPadding
anchors.horizontalCenter: parent.horizontalCenter anchors.horizontalCenter: parent.horizontalCenter
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
StyledText { StyledText {
//% "Back up your seed phrase" text: root.text
text: qsTrId("back-up-your-seed-phrase")
font.pixelSize: 13 font.pixelSize: 13
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
color: Style.current.white color: Style.current.white
} }
Button { Button {
width: 58 width: btnWidth
height: 24 height: 24
contentItem: Item { contentItem: Item {
anchors.fill: parent anchors.fill: parent
Text { Text {
text: "Back up" text: btnText
font.pixelSize: 13 font.pixelSize: 13
font.weight: Font.Medium font.weight: Font.Medium
font.family: Style.current.fontRegular.name font.family: Style.current.fontRegular.name
@ -54,7 +56,7 @@ Rectangle {
MouseArea { MouseArea {
cursorShape: Qt.PointingHandCursor cursorShape: Qt.PointingHandCursor
anchors.fill: parent anchors.fill: parent
onClicked: backupSeedModal.open() onClicked: root.onClick()
} }
} }
} }
@ -65,7 +67,7 @@ Rectangle {
anchors.topMargin: 6 anchors.topMargin: 6
anchors.right: parent.right anchors.right: parent.right
anchors.rightMargin: 18 anchors.rightMargin: 18
source: "../../img/close-white.svg" source: "img/close-white.svg"
height: 20 height: 20
width: 20 width: 20
} }
@ -83,10 +85,4 @@ Rectangle {
PropertyAnimation { target: root; property: "y"; to: -1 * root.height } PropertyAnimation { target: root; property: "y"; to: -1 * root.height }
} }
} }
BackupSeedModal {
id: backupSeedModal
}
} }

View File

@ -0,0 +1,4 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M10.6866 5.31319C10.8818 5.50846 10.8818 5.82504 10.6866 6.0203L8.94244 7.76442C8.81227 7.89459 8.81227 8.10565 8.94244 8.23582L10.6866 9.97994C10.8818 10.1752 10.8818 10.4918 10.6866 10.687C10.4913 10.8823 10.1747 10.8823 9.97945 10.687L8.23534 8.94293C8.10516 8.81276 7.89411 8.81276 7.76393 8.94293L6.01989 10.687C5.82463 10.8822 5.50805 10.8822 5.31279 10.687C5.11753 10.4917 5.11753 10.1751 5.31279 9.97986L7.05682 8.23582C7.187 8.10565 7.187 7.89459 7.05682 7.76442L5.31279 6.02038C5.11753 5.82512 5.11753 5.50854 5.31279 5.31328C5.50805 5.11801 5.82463 5.11801 6.01989 5.31328L7.76393 7.05731C7.89411 7.18749 8.10516 7.18749 8.23534 7.05731L9.97945 5.31319C10.1747 5.11793 10.4913 5.11793 10.6866 5.31319Z" fill="#FFFFFF"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M7.99967 14.6668C11.6816 14.6668 14.6663 11.6821 14.6663 8.00016C14.6663 4.31826 11.6816 1.3335 7.99967 1.3335C4.31778 1.3335 1.33301 4.31826 1.33301 8.00016C1.33301 11.6821 4.31778 14.6668 7.99967 14.6668ZM7.99967 13.6668C11.1293 13.6668 13.6663 11.1298 13.6663 8.00016C13.6663 4.87055 11.1293 2.3335 7.99967 2.3335C4.87006 2.3335 2.33301 4.87055 2.33301 8.00016C2.33301 11.1298 4.87006 13.6668 7.99967 13.6668Z" fill="#FFFFFF"/>
</svg>

After

Width:  |  Height:  |  Size: 1.3 KiB

1
vendor/semver.nim vendored Submodule

@ -0,0 +1 @@
Subproject commit 3b7ace48704236329363290c62ce43f4d35f684d