GODT-1244: Refactor switching stable-early and factory reset

This commit is contained in:
Alexander Bilyak
2021-11-03 16:55:25 +00:00
committed by Jakub
parent b5b477a3ce
commit 41e15db442
12 changed files with 163 additions and 106 deletions

View File

@ -23,6 +23,7 @@ import (
"strconv" "strconv"
"time" "time"
"github.com/Masterminds/semver/v3"
"github.com/ProtonMail/proton-bridge/internal/config/settings" "github.com/ProtonMail/proton-bridge/internal/config/settings"
"github.com/ProtonMail/proton-bridge/internal/constants" "github.com/ProtonMail/proton-bridge/internal/constants"
"github.com/ProtonMail/proton-bridge/internal/metrics" "github.com/ProtonMail/proton-bridge/internal/metrics"
@ -131,54 +132,54 @@ func (b *Bridge) GetUpdateChannel() updater.UpdateChannel {
} }
// SetUpdateChannel switches update channel. // SetUpdateChannel switches update channel.
// Downgrading to previous version (by switching from early to stable, for example) func (b *Bridge) SetUpdateChannel(channel updater.UpdateChannel) {
// requires clearing all data including update files due to possibility of
// inconsistency between versions and absence of backwards migration scripts.
func (b *Bridge) SetUpdateChannel(channel updater.UpdateChannel) (needRestart bool, err error) {
b.settings.Set(settings.UpdateChannelKey, string(channel)) b.settings.Set(settings.UpdateChannelKey, string(channel))
}
func (b *Bridge) resetToLatestStable() error {
version, err := b.updater.Check() version, err := b.updater.Check()
if err != nil { if err != nil {
return false, err // If we can not check for updates - just remove all local updates and reset to base installer version.
} // Not using `b.locations.ClearUpdates()` because `versioner.RemoveOtherVersions` can also handle
// case when it is needed to remove currently running verion.
// We have to deal right away only with downgrade - that action needs to if err := b.versioner.RemoveOtherVersions(semver.MustParse("0.0.0")); err != nil {
// clear data and updates, and install bridge right away. But regular
// upgrade can be left out for periodic check.
if !b.updater.IsDowngrade(version) {
return false, nil
}
if err := b.Users.ClearData(); err != nil {
log.WithError(err).Error("Failed to clear data while downgrading channel")
}
if err := b.locations.ClearUpdates(); err != nil {
log.WithError(err).Error("Failed to clear updates while downgrading channel") log.WithError(err).Error("Failed to clear updates while downgrading channel")
} }
return nil
}
// If current version is same as upstream stable version - do nothing.
if version.Version.Equal(semver.MustParse(constants.Version)) {
return nil
}
if err := b.updater.InstallUpdate(version); err != nil { if err := b.updater.InstallUpdate(version); err != nil {
return false, err return err
} }
return true, b.versioner.RemoveOtherVersions(version.Version) return b.versioner.RemoveOtherVersions(version.Version)
} }
// FactoryReset will remove all local cache and settings. // FactoryReset will remove all local cache and settings.
// We want to downgrade to latest stable version if user is early higher than stable. // It will also downgrade to latest stable version if user is on early version.
// Setting the channel back to stable will do this for us.
func (b *Bridge) FactoryReset() { func (b *Bridge) FactoryReset() {
if _, err := b.SetUpdateChannel(updater.StableChannel); err != nil { wasEarly := b.GetUpdateChannel() == updater.EarlyChannel
log.WithError(err).Error("Failed to revert to stable update channel")
}
if err := b.Users.ClearUsers(); err != nil { b.settings.Set(settings.UpdateChannelKey, string(updater.StableChannel))
log.WithError(err).Error("Failed to remove bridge users")
if wasEarly {
if err := b.resetToLatestStable(); err != nil {
log.WithError(err).Error("Failed to reset to latest stable version")
}
} }
if err := b.Users.ClearData(); err != nil { if err := b.Users.ClearData(); err != nil {
log.WithError(err).Error("Failed to remove bridge data") log.WithError(err).Error("Failed to remove bridge data")
} }
if err := b.Users.ClearUsers(); err != nil {
log.WithError(err).Error("Failed to remove bridge users")
}
} }
// GetKeychainApp returns current keychain helper. // GetKeychainApp returns current keychain helper.

View File

@ -81,13 +81,7 @@ func (f *frontendCLI) selectEarlyChannel(c *ishell.Context) {
f.Println("Bridge is currently on the stable update channel.") f.Println("Bridge is currently on the stable update channel.")
if f.yesNoQuestion("Are you sure you want to switch to the early-access update channel") { if f.yesNoQuestion("Are you sure you want to switch to the early-access update channel") {
needRestart, err := f.bridge.SetUpdateChannel(updater.EarlyChannel) f.bridge.SetUpdateChannel(updater.EarlyChannel)
if err != nil {
f.Println("There was a problem switching update channel.")
}
if needRestart {
f.restarter.SetToRestart()
}
} }
} }
@ -101,12 +95,6 @@ func (f *frontendCLI) selectStableChannel(c *ishell.Context) {
f.Println("Switching to the stable channel may reset all data!") f.Println("Switching to the stable channel may reset all data!")
if f.yesNoQuestion("Are you sure you want to switch to the stable update channel") { if f.yesNoQuestion("Are you sure you want to switch to the stable update channel") {
needRestart, err := f.bridge.SetUpdateChannel(updater.StableChannel) f.bridge.SetUpdateChannel(updater.StableChannel)
if err != nil {
f.Println("There was a problem switching update channel.")
}
if needRestart {
f.restarter.SetToRestart()
}
} }
} }

View File

@ -79,7 +79,7 @@ SettingsView {
if (!beta.checked) { if (!beta.checked) {
root.notifications.askEnableBeta() root.notifications.askEnableBeta()
} else { } else {
root.notifications.askDisableBeta() root.backend.toggleBeta(false)
} }
} }

View File

@ -66,11 +66,6 @@ Item {
notification: root.notifications.updateForceError notification: root.notifications.updateForceError
} }
NotificationDialog {
colorScheme: root.colorScheme
notification: root.notifications.disableBeta
}
NotificationDialog { NotificationDialog {
colorScheme: root.colorScheme colorScheme: root.colorScheme
notification: root.notifications.enableBeta notification: root.notifications.enableBeta

View File

@ -29,7 +29,6 @@ QtObject {
property StatusWindow frontendStatus property StatusWindow frontendStatus
property SystemTrayIcon frontendTray property SystemTrayIcon frontendTray
signal askDisableBeta()
signal askEnableBeta() signal askEnableBeta()
signal askEnableSplitMode(var user) signal askEnableSplitMode(var user)
signal askDisableLocalCache() signal askDisableLocalCache()
@ -58,7 +57,6 @@ QtObject {
root.updateIsLatestVersion, root.updateIsLatestVersion,
root.loginConnectionError, root.loginConnectionError,
root.onlyPaidUsers, root.onlyPaidUsers,
root.disableBeta,
root.enableBeta, root.enableBeta,
root.bugReportSendSuccess, root.bugReportSendSuccess,
root.bugReportSendError, root.bugReportSendError,
@ -337,41 +335,6 @@ QtObject {
} }
} }
property Notification disableBeta: Notification {
text: qsTr("Disable beta access?")
description: qsTr("This resets Bridge to the current release and will restart the app. Your preferences, cached data, and email client configurations will be cleared. ")
icon: "./icons/ic-exclamation-circle-filled.svg"
type: Notification.NotificationType.Warning
group: Notifications.Group.Update | Notifications.Group.Dialogs
Connections {
target: root
onAskDisableBeta: {
root.disableBeta.active = true
}
}
action: [
Action {
id: disableBeta_remindLater
text: qsTr("Remind me later")
onTriggered: {
root.disableBeta.active = false
}
},
Action {
id: disableBeta_disable
text: qsTr("Disable and restart")
onTriggered: {
root.backend.toggleBeta(false)
disableBeta_disable.loading = true
disableBeta_remindLater.enabled = false
}
}
]
}
property Notification enableBeta: Notification { property Notification enableBeta: Notification {
text: qsTr("Enable Beta access") text: qsTr("Enable Beta access")
description: qsTr("Be the first to get new updates and use new features. Bridge will update to the latest beta version.") description: qsTr("Be the first to get new updates and use new features. Bridge will update to the latest beta version.")

View File

@ -15,6 +15,7 @@
// You should have received a copy of the GNU General Public License // You should have received a copy of the GNU General Public License
// along with ProtonMail Bridge. If not, see <https://www.gnu.org/licenses/>. // along with ProtonMail Bridge. If not, see <https://www.gnu.org/licenses/>.
//go:build build_qt
// +build build_qt // +build build_qt
package qt package qt
@ -100,31 +101,15 @@ func (f *FrontendQt) setIsBetaEnabled() {
} }
func (f *FrontendQt) toggleBeta(makeItEnabled bool) { func (f *FrontendQt) toggleBeta(makeItEnabled bool) {
channel := f.bridge.GetUpdateChannel() channel := updater.StableChannel
if makeItEnabled == (channel == updater.EarlyChannel) {
f.qml.SetIsBetaEnabled(makeItEnabled)
return
}
channel = updater.StableChannel
if makeItEnabled { if makeItEnabled {
channel = updater.EarlyChannel channel = updater.EarlyChannel
} }
needRestart, err := f.bridge.SetUpdateChannel(channel) f.bridge.SetUpdateChannel(channel)
f.setIsBetaEnabled() f.setIsBetaEnabled()
if err != nil { // Immediately check the updates to set the correct landing page link.
f.log.WithError(err).Warn("Switching udpate channel failed.") f.checkUpdates()
f.qml.UpdateManualError()
return
}
if needRestart {
f.restart()
return
}
f.checkUpdatesAndNotify(false)
} }

View File

@ -81,7 +81,7 @@ type Bridger interface {
DisableCache() error DisableCache() error
MigrateCache(from, to string) error MigrateCache(from, to string) error
GetUpdateChannel() updater.UpdateChannel GetUpdateChannel() updater.UpdateChannel
SetUpdateChannel(updater.UpdateChannel) (needRestart bool, err error) SetUpdateChannel(updater.UpdateChannel)
GetKeychainApp() string GetKeychainApp() string
SetKeychainApp(keychain string) SetKeychainApp(keychain string)
} }

View File

@ -15,6 +15,7 @@
// You should have received a copy of the GNU General Public License // You should have received a copy of the GNU General Public License
// along with ProtonMail Bridge. If not, see <https://www.gnu.org/licenses/>. // along with ProtonMail Bridge. If not, see <https://www.gnu.org/licenses/>.
//go:build !darwin
// +build !darwin // +build !darwin
package versioner package versioner
@ -23,6 +24,7 @@ import (
"os" "os"
"github.com/Masterminds/semver/v3" "github.com/Masterminds/semver/v3"
"github.com/ProtonMail/proton-bridge/internal/constants"
"github.com/sirupsen/logrus" "github.com/sirupsen/logrus"
) )
@ -58,6 +60,12 @@ func (v *Versioner) RemoveOtherVersions(versionToKeep *semver.Version) error {
if version.Equal(versionToKeep) { if version.Equal(versionToKeep) {
continue continue
} }
if version.Equal(semver.MustParse(constants.Version)) {
if err := v.RemoveCurrentVersion(); err != nil {
logrus.WithError(err).Error("Failed to remove current app version")
}
continue
}
if err := os.RemoveAll(version.path); err != nil { if err := os.RemoveAll(version.path); err != nil {
logrus.WithError(err).Error("Failed to remove old app version") logrus.WithError(err).Error("Failed to remove old app version")
} }

View File

@ -15,6 +15,9 @@
// You should have received a copy of the GNU General Public License // You should have received a copy of the GNU General Public License
// along with ProtonMail Bridge. If not, see <https://www.gnu.org/licenses/>. // along with ProtonMail Bridge. If not, see <https://www.gnu.org/licenses/>.
//go:build darwin
// +build darwin
package versioner package versioner
import "github.com/Masterminds/semver/v3" import "github.com/Masterminds/semver/v3"
@ -30,3 +33,9 @@ func (v *Versioner) RemoveOtherVersions(versionToKeep *semver.Version) error {
// darwin does not use the versioner; removal is a noop. // darwin does not use the versioner; removal is a noop.
return nil return nil
} }
// RemoveOtherVersions removes current app version unless it is base installed version.
func (v *Versioner) RemoveCurrentVersion() error {
// darwin does not use the versioner; removal is a noop.
return nil
}

View File

@ -0,0 +1,52 @@
// Copyright (c) 2021 Proton Technologies AG
//
// This file is part of ProtonMail Bridge.
//
// ProtonMail Bridge is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// ProtonMail Bridge is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with ProtonMail Bridge. If not, see <https://www.gnu.org/licenses/>.
//go:build linux
// +build linux
package versioner
import (
"os"
"path/filepath"
"strings"
)
func (v *Versioner) RemoveCurrentVersion() error {
// get current executable
exec, err := os.Executable()
if err != nil {
return err
}
// Check that current executtable is update package so we won't
// delete base version (that is controlled by package manager).
// Get absolute paths to ensure there is no crazy stuff there.
absExec, err := filepath.Abs(exec)
if err != nil {
return err
}
absRoot, err := filepath.Abs(v.root)
if err != nil {
return err
}
if !strings.HasPrefix(absExec, absRoot) {
return ErrNoRemoveBase
}
return os.RemoveAll(filepath.Dir(absExec))
}

View File

@ -0,0 +1,55 @@
// Copyright (c) 2021 Proton Technologies AG
//
// This file is part of ProtonMail Bridge.
//
// ProtonMail Bridge is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// ProtonMail Bridge is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with ProtonMail Bridge. If not, see <https://www.gnu.org/licenses/>.
//go:build windows
// +build windows
package versioner
import (
"os"
"path/filepath"
"strings"
)
func (v *Versioner) RemoveCurrentVersion() error {
// get current executable
exec, err := os.Executable()
if err != nil {
return err
}
// Check that current executtable is update package so we won't
// delete base version (that is controlled by package manager).
// Get absolute paths to ensure there is no crazy stuff there.
absExec, err := filepath.Abs(exec)
if err != nil {
return err
}
absRoot, err := filepath.Abs(v.root)
if err != nil {
return err
}
if !strings.HasPrefix(absExec, absRoot) {
return ErrNoRemoveBase
}
// It is impossible delete running executable on Windows, so instead
// we rename it. Next time launcher will start it will remove this version
// as checksum won't match.
return os.Rename(absExec, filepath.Join(filepath.Dir(absExec), "_"+filepath.Base(absExec)))
}

View File

@ -29,6 +29,7 @@ import (
var ( var (
ErrNoVersions = errors.New("no available versions") ErrNoVersions = errors.New("no available versions")
ErrNoExecutable = errors.New("no executable found") ErrNoExecutable = errors.New("no executable found")
ErrNoRemoveBase = errors.New("can't remove base version")
) )
// Versioner manages a directory of versioned app directories. // Versioner manages a directory of versioned app directories.