GODT-1537: Manual in-app update mechanism.
This commit is contained in:
parent
3b07121f08
commit
5b20b6a3d0
|
@ -0,0 +1,103 @@
|
||||||
|
# Update mechanism of Bridge
|
||||||
|
|
||||||
|
There are mulitple options how to change version of application:
|
||||||
|
* Automatic in-app update
|
||||||
|
* Manual in-app update
|
||||||
|
* Manual install
|
||||||
|
|
||||||
|
In-app update ends with restarting bridge into new version. Automatic in-app
|
||||||
|
update is downloading, verifying and installing the new version immediatelly
|
||||||
|
without user confirmation. For manual in-app update user needs to confirm first.
|
||||||
|
Update is done from special update file published on website.
|
||||||
|
|
||||||
|
The manual installation requires user to download, verify and install manually
|
||||||
|
using installer for given OS.
|
||||||
|
|
||||||
|
The bridge is installed and executed differently for given OS:
|
||||||
|
|
||||||
|
* Windows and Linux apps are using launcher mechanism:
|
||||||
|
* There is system protected installation path which is created on first
|
||||||
|
install. It contains bridge exe and launcher exe. When users starts
|
||||||
|
bridge the launcher is executed first. It will check update path compare
|
||||||
|
version with installed one. The newer version then is then executed.
|
||||||
|
* Update mechanism means to replace files in update folder which is located
|
||||||
|
in user space.
|
||||||
|
|
||||||
|
* macOS app does not use launcher
|
||||||
|
* No launcher, only one executable
|
||||||
|
* In-App udpate replaces the bridge files in installation path directly
|
||||||
|
|
||||||
|
|
||||||
|
```mermaid
|
||||||
|
flowchart LR
|
||||||
|
subgraph Frontend
|
||||||
|
U[User requests<br>version check]
|
||||||
|
ManIns((Notify user about<br>manual install<br>is needed))
|
||||||
|
R((Notify user<br>about restart))
|
||||||
|
ManUp((Notify user about<br>manual update))
|
||||||
|
NF((Notify user about<br>force update))
|
||||||
|
|
||||||
|
ManUp -->|Install| InstFront[Install]
|
||||||
|
InstFront -->|Ok| R
|
||||||
|
InstFront -->|Error| ManIns
|
||||||
|
|
||||||
|
U --> CheckFront[Check online]
|
||||||
|
CheckFront -->|Ok| IAFront{Is new version<br>and applicable?}
|
||||||
|
CheckFront -->|Error| ManIns
|
||||||
|
|
||||||
|
IAFront -->|No| Latest((Notify user<br>has latest version))
|
||||||
|
IAFront -->|Yes| CanInstall{Can update?}
|
||||||
|
CanInstall -->|No| ManIns
|
||||||
|
CanInstall -->|Yes| NotifOrInstall{Is automatic<br>update enabled?}
|
||||||
|
NotifOrInstall -->|Manual| ManUp
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
subgraph Backend
|
||||||
|
W[Wait for next check]
|
||||||
|
|
||||||
|
W --> Check[Check online]
|
||||||
|
|
||||||
|
Check --> NV{Has new<br>version?}
|
||||||
|
Check -->|Error| W
|
||||||
|
NV -->|No new version| W
|
||||||
|
IA{Is install<br>applicable?}
|
||||||
|
NV -->|New version<br>available| IA
|
||||||
|
IA -->|Local rollout<br>not enough| W
|
||||||
|
IA -->|Yes| AU{Is automatic\nupdate enabled?}
|
||||||
|
|
||||||
|
AU -->|Yes| CanUp{Can update?}
|
||||||
|
CanUp -->|No| ManIns
|
||||||
|
|
||||||
|
CanUp -->|Yes| Ins[Install]
|
||||||
|
Ins -->|Error| ManIns
|
||||||
|
Ins -->|Ok| R
|
||||||
|
|
||||||
|
AU -->|No| ManUp
|
||||||
|
ManUp -->|Ignore| W
|
||||||
|
|
||||||
|
|
||||||
|
F[Force update]
|
||||||
|
F --> NF
|
||||||
|
end
|
||||||
|
|
||||||
|
ManIns --> Web[Open web page]
|
||||||
|
NF --> Web
|
||||||
|
ManUp --> Web
|
||||||
|
R --> Re[Restart]
|
||||||
|
NF --> Q[Quit bridge]
|
||||||
|
NotifOrInstall -->|Automatic| W
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
The non-trivial is to combine the update with setting change:
|
||||||
|
* turn off/on automatic in-app updates
|
||||||
|
* change from stable to beta or back
|
||||||
|
|
||||||
|
_TODO fill flow chart details_
|
||||||
|
|
||||||
|
|
||||||
|
We are not support downgrade functionality. Only some circumstances can lead to
|
||||||
|
downgrading the app version.
|
||||||
|
|
||||||
|
_TODO fill flow chart details_
|
|
@ -133,7 +133,7 @@ QtObject {
|
||||||
return Qt.point(_x, _y)
|
return Qt.point(_x, _y)
|
||||||
}
|
}
|
||||||
|
|
||||||
// fir to the right
|
// fit to the right
|
||||||
_x = iconRect.right
|
_x = iconRect.right
|
||||||
if (isInInterval(_x, screenRect.left, screenRect.right - width)) {
|
if (isInInterval(_x, screenRect.left, screenRect.right - width)) {
|
||||||
// position preferebly in the vertical center but bound to the screen rect
|
// position preferebly in the vertical center but bound to the screen rect
|
||||||
|
|
|
@ -729,6 +729,9 @@ Window {
|
||||||
console.log("check updates")
|
console.log("check updates")
|
||||||
}
|
}
|
||||||
signal checkUpdatesFinished()
|
signal checkUpdatesFinished()
|
||||||
|
function installUpdate() {
|
||||||
|
console.log("manuall install update triggered")
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
property bool isDiskCacheEnabled: true
|
property bool isDiskCacheEnabled: true
|
||||||
|
@ -748,7 +751,19 @@ Window {
|
||||||
property bool isAutomaticUpdateOn : true
|
property bool isAutomaticUpdateOn : true
|
||||||
function toggleAutomaticUpdate(makeItActive) {
|
function toggleAutomaticUpdate(makeItActive) {
|
||||||
console.debug("-> silent updates", makeItActive, root.isAutomaticUpdateOn)
|
console.debug("-> silent updates", makeItActive, root.isAutomaticUpdateOn)
|
||||||
root.isAutomaticUpdateOn = makeItActive
|
var callback = function () {
|
||||||
|
root.isAutomaticUpdateOn = makeItActive;
|
||||||
|
console.debug("-> CHANGED silent updates", makeItActive, root.isAutomaticUpdateOn)
|
||||||
|
}
|
||||||
|
atimer.onTriggered.connect(callback)
|
||||||
|
atimer.restart()
|
||||||
|
}
|
||||||
|
|
||||||
|
Timer {
|
||||||
|
id: atimer
|
||||||
|
interval: 2000
|
||||||
|
running: false
|
||||||
|
repeat: false
|
||||||
}
|
}
|
||||||
|
|
||||||
property bool isAutostartOn : true // Example of settings with loading state
|
property bool isAutostartOn : true // Example of settings with loading state
|
||||||
|
|
|
@ -182,6 +182,7 @@ ApplicationWindow {
|
||||||
colorScheme: root.colorScheme
|
colorScheme: root.colorScheme
|
||||||
notifications: root.notifications
|
notifications: root.notifications
|
||||||
mainWindow: root
|
mainWindow: root
|
||||||
|
backend: root.backend
|
||||||
}
|
}
|
||||||
|
|
||||||
function showLocalCacheSettings() { contentWrapper.showLocalCacheSettings() }
|
function showLocalCacheSettings() { contentWrapper.showLocalCacheSettings() }
|
||||||
|
|
|
@ -25,6 +25,7 @@ import Notifications 1.0
|
||||||
|
|
||||||
Item {
|
Item {
|
||||||
id: root
|
id: root
|
||||||
|
property var backend
|
||||||
|
|
||||||
property ColorScheme colorScheme
|
property ColorScheme colorScheme
|
||||||
property var notifications
|
property var notifications
|
||||||
|
@ -51,8 +52,11 @@ Item {
|
||||||
notification: root.notifications.updateManualReady
|
notification: root.notifications.updateManualReady
|
||||||
|
|
||||||
Switch {
|
Switch {
|
||||||
|
id:autoUpdate
|
||||||
colorScheme: root.colorScheme
|
colorScheme: root.colorScheme
|
||||||
text: qsTr("Update automatically in the future")
|
text: qsTr("Update automatically in the future")
|
||||||
|
checked: root.backend.isAutomaticUpdateOn
|
||||||
|
onClicked: root.backend.toggleAutomaticUpdate(autoUpdate.checked)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -166,8 +166,9 @@ QtObject {
|
||||||
}
|
}
|
||||||
|
|
||||||
property Notification updateManualError: Notification {
|
property Notification updateManualError: Notification {
|
||||||
description: qsTr("Bridge couldn’t update")
|
title: qsTr("Bridge couldn’t update")
|
||||||
brief: description
|
brief: title
|
||||||
|
description: qsTr("Please follow manual installation in order to update Bridge.")
|
||||||
icon: "./icons/ic-exclamation-circle-filled.svg"
|
icon: "./icons/ic-exclamation-circle-filled.svg"
|
||||||
type: Notification.NotificationType.Warning
|
type: Notification.NotificationType.Warning
|
||||||
group: Notifications.Group.Update
|
group: Notifications.Group.Update
|
||||||
|
@ -192,7 +193,7 @@ QtObject {
|
||||||
text: qsTr("Remind me later")
|
text: qsTr("Remind me later")
|
||||||
|
|
||||||
onTriggered: {
|
onTriggered: {
|
||||||
root.updateManualReady.active = false
|
root.updateManualError.active = false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
@ -273,7 +274,7 @@ QtObject {
|
||||||
|
|
||||||
onTriggered: {
|
onTriggered: {
|
||||||
root.backend.quit()
|
root.backend.quit()
|
||||||
root.updateForce.active = false
|
root.updateForceError.active = false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|
|
@ -153,7 +153,7 @@ func (f *FrontendQt) NotifySilentUpdateInstalled() {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *FrontendQt) NotifySilentUpdateError(err error) {
|
func (f *FrontendQt) NotifySilentUpdateError(err error) {
|
||||||
f.log.WithError(err).Warn("Update failed, asking for manual.")
|
f.log.WithError(err).Warn("In-app update failed, asking for manual.")
|
||||||
f.qml.UpdateManualError()
|
f.qml.UpdateManualError()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -25,6 +25,7 @@ import (
|
||||||
|
|
||||||
"github.com/ProtonMail/proton-bridge/internal/config/settings"
|
"github.com/ProtonMail/proton-bridge/internal/config/settings"
|
||||||
"github.com/ProtonMail/proton-bridge/internal/updater"
|
"github.com/ProtonMail/proton-bridge/internal/updater"
|
||||||
|
"github.com/pkg/errors"
|
||||||
)
|
)
|
||||||
|
|
||||||
var checkingUpdates = sync.Mutex{}
|
var checkingUpdates = sync.Mutex{}
|
||||||
|
@ -62,9 +63,18 @@ func (f *FrontendQt) checkUpdatesAndNotify(isRequestFromUser bool) {
|
||||||
|
|
||||||
if !f.updater.CanInstall(f.newVersionInfo) {
|
if !f.updater.CanInstall(f.newVersionInfo) {
|
||||||
f.log.Debug("A manual update is required")
|
f.log.Debug("A manual update is required")
|
||||||
f.qml.UpdateManualReady(f.newVersionInfo.Version.String())
|
f.qml.UpdateManualError()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if f.settings.GetBool(settings.AutoUpdateKey) {
|
||||||
|
// NOOP will update eventually
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if isRequestFromUser {
|
||||||
|
f.qml.UpdateManualReady(f.newVersionInfo.Version.String())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *FrontendQt) updateForce() {
|
func (f *FrontendQt) updateForce() {
|
||||||
|
@ -113,3 +123,26 @@ func (f *FrontendQt) toggleBeta(makeItEnabled bool) {
|
||||||
// Immediately check the updates to set the correct landing page link.
|
// Immediately check the updates to set the correct landing page link.
|
||||||
f.checkUpdates()
|
f.checkUpdates()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (f *FrontendQt) installUpdate() {
|
||||||
|
checkingUpdates.Lock()
|
||||||
|
defer checkingUpdates.Unlock()
|
||||||
|
|
||||||
|
if !f.updater.CanInstall(f.newVersionInfo) {
|
||||||
|
f.log.Warning("Skipping update installation, current version too old")
|
||||||
|
f.qml.UpdateManualError()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := f.updater.InstallUpdate(f.newVersionInfo); err != nil {
|
||||||
|
if errors.Cause(err) == updater.ErrDownloadVerify {
|
||||||
|
f.log.WithError(err).Warning("Skipping update installation due to temporary error")
|
||||||
|
} else {
|
||||||
|
f.log.WithError(err).Error("The update couldn't be installed")
|
||||||
|
f.qml.UpdateManualError()
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
f.qml.UpdateSilentRestartNeeded()
|
||||||
|
}
|
||||||
|
|
|
@ -81,6 +81,7 @@ type QMLBackend struct {
|
||||||
_ func() `signal:"updateIsLatestVersion"`
|
_ func() `signal:"updateIsLatestVersion"`
|
||||||
_ func() `slot:"checkUpdates"`
|
_ func() `slot:"checkUpdates"`
|
||||||
_ func() `signal:"checkUpdatesFinished"`
|
_ func() `signal:"checkUpdatesFinished"`
|
||||||
|
_ func() `slot:"installUpdate"`
|
||||||
|
|
||||||
_ bool `property:"isDiskCacheEnabled"`
|
_ bool `property:"isDiskCacheEnabled"`
|
||||||
_ core.QUrl `property:"diskCachePath"`
|
_ core.QUrl `property:"diskCachePath"`
|
||||||
|
@ -213,6 +214,13 @@ func (q *QMLBackend) setup(f *FrontendQt) {
|
||||||
}()
|
}()
|
||||||
})
|
})
|
||||||
|
|
||||||
|
q.ConnectInstallUpdate(func() {
|
||||||
|
go func() {
|
||||||
|
defer f.panicHandler.HandlePanic()
|
||||||
|
f.installUpdate()
|
||||||
|
}()
|
||||||
|
})
|
||||||
|
|
||||||
f.setIsDiskCacheEnabled()
|
f.setIsDiskCacheEnabled()
|
||||||
f.setDiskCachePath()
|
f.setDiskCachePath()
|
||||||
q.ConnectChangeLocalCache(func(e bool, d *core.QUrl) {
|
q.ConnectChangeLocalCache(func(e bool, d *core.QUrl) {
|
||||||
|
|
Loading…
Reference in New Issue