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)
|
||||
}
|
||||
|
||||
// fir to the right
|
||||
// fit to the right
|
||||
_x = iconRect.right
|
||||
if (isInInterval(_x, screenRect.left, screenRect.right - width)) {
|
||||
// position preferebly in the vertical center but bound to the screen rect
|
||||
|
|
|
@ -729,6 +729,9 @@ Window {
|
|||
console.log("check updates")
|
||||
}
|
||||
signal checkUpdatesFinished()
|
||||
function installUpdate() {
|
||||
console.log("manuall install update triggered")
|
||||
}
|
||||
|
||||
|
||||
property bool isDiskCacheEnabled: true
|
||||
|
@ -748,7 +751,19 @@ Window {
|
|||
property bool isAutomaticUpdateOn : true
|
||||
function toggleAutomaticUpdate(makeItActive) {
|
||||
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
|
||||
|
|
|
@ -182,6 +182,7 @@ ApplicationWindow {
|
|||
colorScheme: root.colorScheme
|
||||
notifications: root.notifications
|
||||
mainWindow: root
|
||||
backend: root.backend
|
||||
}
|
||||
|
||||
function showLocalCacheSettings() { contentWrapper.showLocalCacheSettings() }
|
||||
|
|
|
@ -25,6 +25,7 @@ import Notifications 1.0
|
|||
|
||||
Item {
|
||||
id: root
|
||||
property var backend
|
||||
|
||||
property ColorScheme colorScheme
|
||||
property var notifications
|
||||
|
@ -51,8 +52,11 @@ Item {
|
|||
notification: root.notifications.updateManualReady
|
||||
|
||||
Switch {
|
||||
id:autoUpdate
|
||||
colorScheme: root.colorScheme
|
||||
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 {
|
||||
description: qsTr("Bridge couldn’t update")
|
||||
brief: description
|
||||
title: qsTr("Bridge couldn’t update")
|
||||
brief: title
|
||||
description: qsTr("Please follow manual installation in order to update Bridge.")
|
||||
icon: "./icons/ic-exclamation-circle-filled.svg"
|
||||
type: Notification.NotificationType.Warning
|
||||
group: Notifications.Group.Update
|
||||
|
@ -192,7 +193,7 @@ QtObject {
|
|||
text: qsTr("Remind me later")
|
||||
|
||||
onTriggered: {
|
||||
root.updateManualReady.active = false
|
||||
root.updateManualError.active = false
|
||||
}
|
||||
}
|
||||
]
|
||||
|
@ -273,7 +274,7 @@ QtObject {
|
|||
|
||||
onTriggered: {
|
||||
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) {
|
||||
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()
|
||||
}
|
||||
|
||||
|
|
|
@ -25,6 +25,7 @@ import (
|
|||
|
||||
"github.com/ProtonMail/proton-bridge/internal/config/settings"
|
||||
"github.com/ProtonMail/proton-bridge/internal/updater"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
var checkingUpdates = sync.Mutex{}
|
||||
|
@ -62,9 +63,18 @@ func (f *FrontendQt) checkUpdatesAndNotify(isRequestFromUser bool) {
|
|||
|
||||
if !f.updater.CanInstall(f.newVersionInfo) {
|
||||
f.log.Debug("A manual update is required")
|
||||
f.qml.UpdateManualReady(f.newVersionInfo.Version.String())
|
||||
f.qml.UpdateManualError()
|
||||
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() {
|
||||
|
@ -113,3 +123,26 @@ func (f *FrontendQt) toggleBeta(makeItEnabled bool) {
|
|||
// Immediately check the updates to set the correct landing page link.
|
||||
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() `slot:"checkUpdates"`
|
||||
_ func() `signal:"checkUpdatesFinished"`
|
||||
_ func() `slot:"installUpdate"`
|
||||
|
||||
_ bool `property:"isDiskCacheEnabled"`
|
||||
_ 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.setDiskCachePath()
|
||||
q.ConnectChangeLocalCache(func(e bool, d *core.QUrl) {
|
||||
|
|
Loading…
Reference in New Issue