From 41e15db44247dbe6785969cb245f9f5a210cc6dc Mon Sep 17 00:00:00 2001 From: Alexander Bilyak Date: Wed, 3 Nov 2021 16:55:25 +0000 Subject: [PATCH] GODT-1244: Refactor switching stable-early and factory reset --- internal/bridge/bridge.go | 55 ++++++++++--------- internal/frontend/cli/updates.go | 16 +----- internal/frontend/qml/GeneralSettings.qml | 2 +- internal/frontend/qml/NotificationPopups.qml | 5 -- .../qml/Notifications/Notifications.qml | 37 ------------- internal/frontend/qt/frontend_updates.go | 27 ++------- internal/frontend/types/types.go | 2 +- .../{remove_default.go => remove.go} | 8 +++ internal/versioner/remove_darwin.go | 9 +++ internal/versioner/remove_linux.go | 52 ++++++++++++++++++ internal/versioner/remove_windows.go | 55 +++++++++++++++++++ internal/versioner/versioner.go | 1 + 12 files changed, 163 insertions(+), 106 deletions(-) rename internal/versioner/{remove_default.go => remove.go} (86%) create mode 100644 internal/versioner/remove_linux.go create mode 100644 internal/versioner/remove_windows.go diff --git a/internal/bridge/bridge.go b/internal/bridge/bridge.go index 6143d59..94b5708 100644 --- a/internal/bridge/bridge.go +++ b/internal/bridge/bridge.go @@ -23,6 +23,7 @@ import ( "strconv" "time" + "github.com/Masterminds/semver/v3" "github.com/ProtonMail/proton-bridge/internal/config/settings" "github.com/ProtonMail/proton-bridge/internal/constants" "github.com/ProtonMail/proton-bridge/internal/metrics" @@ -131,54 +132,54 @@ func (b *Bridge) GetUpdateChannel() updater.UpdateChannel { } // SetUpdateChannel switches update channel. -// Downgrading to previous version (by switching from early to stable, for example) -// 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) { +func (b *Bridge) SetUpdateChannel(channel updater.UpdateChannel) { b.settings.Set(settings.UpdateChannelKey, string(channel)) +} +func (b *Bridge) resetToLatestStable() error { version, err := b.updater.Check() if err != nil { - return false, err - } - - // We have to deal right away only with downgrade - that action needs to - // 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 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. + if err := b.versioner.RemoveOtherVersions(semver.MustParse("0.0.0")); err != nil { + log.WithError(err).Error("Failed to clear updates while downgrading channel") + } + return nil } - if err := b.locations.ClearUpdates(); err != nil { - log.WithError(err).Error("Failed to clear updates while downgrading channel") + // 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 { - 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. -// We want to downgrade to latest stable version if user is early higher than stable. -// Setting the channel back to stable will do this for us. +// It will also downgrade to latest stable version if user is on early version. func (b *Bridge) FactoryReset() { - if _, err := b.SetUpdateChannel(updater.StableChannel); err != nil { - log.WithError(err).Error("Failed to revert to stable update channel") - } + wasEarly := b.GetUpdateChannel() == updater.EarlyChannel - if err := b.Users.ClearUsers(); err != nil { - log.WithError(err).Error("Failed to remove bridge users") + b.settings.Set(settings.UpdateChannelKey, string(updater.StableChannel)) + + 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 { 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. diff --git a/internal/frontend/cli/updates.go b/internal/frontend/cli/updates.go index 5050023..8d0e9e8 100644 --- a/internal/frontend/cli/updates.go +++ b/internal/frontend/cli/updates.go @@ -81,13 +81,7 @@ func (f *frontendCLI) selectEarlyChannel(c *ishell.Context) { 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") { - needRestart, err := f.bridge.SetUpdateChannel(updater.EarlyChannel) - if err != nil { - f.Println("There was a problem switching update channel.") - } - if needRestart { - f.restarter.SetToRestart() - } + f.bridge.SetUpdateChannel(updater.EarlyChannel) } } @@ -101,12 +95,6 @@ func (f *frontendCLI) selectStableChannel(c *ishell.Context) { 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") { - needRestart, err := f.bridge.SetUpdateChannel(updater.StableChannel) - if err != nil { - f.Println("There was a problem switching update channel.") - } - if needRestart { - f.restarter.SetToRestart() - } + f.bridge.SetUpdateChannel(updater.StableChannel) } } diff --git a/internal/frontend/qml/GeneralSettings.qml b/internal/frontend/qml/GeneralSettings.qml index c18b08e..64d4b03 100644 --- a/internal/frontend/qml/GeneralSettings.qml +++ b/internal/frontend/qml/GeneralSettings.qml @@ -79,7 +79,7 @@ SettingsView { if (!beta.checked) { root.notifications.askEnableBeta() } else { - root.notifications.askDisableBeta() + root.backend.toggleBeta(false) } } diff --git a/internal/frontend/qml/NotificationPopups.qml b/internal/frontend/qml/NotificationPopups.qml index a98a409..0c68642 100644 --- a/internal/frontend/qml/NotificationPopups.qml +++ b/internal/frontend/qml/NotificationPopups.qml @@ -66,11 +66,6 @@ Item { notification: root.notifications.updateForceError } - NotificationDialog { - colorScheme: root.colorScheme - notification: root.notifications.disableBeta - } - NotificationDialog { colorScheme: root.colorScheme notification: root.notifications.enableBeta diff --git a/internal/frontend/qml/Notifications/Notifications.qml b/internal/frontend/qml/Notifications/Notifications.qml index c078869..ef26eb2 100644 --- a/internal/frontend/qml/Notifications/Notifications.qml +++ b/internal/frontend/qml/Notifications/Notifications.qml @@ -29,7 +29,6 @@ QtObject { property StatusWindow frontendStatus property SystemTrayIcon frontendTray - signal askDisableBeta() signal askEnableBeta() signal askEnableSplitMode(var user) signal askDisableLocalCache() @@ -58,7 +57,6 @@ QtObject { root.updateIsLatestVersion, root.loginConnectionError, root.onlyPaidUsers, - root.disableBeta, root.enableBeta, root.bugReportSendSuccess, 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 { 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.") diff --git a/internal/frontend/qt/frontend_updates.go b/internal/frontend/qt/frontend_updates.go index 453e89e..00036d4 100644 --- a/internal/frontend/qt/frontend_updates.go +++ b/internal/frontend/qt/frontend_updates.go @@ -15,6 +15,7 @@ // You should have received a copy of the GNU General Public License // along with ProtonMail Bridge. If not, see . +//go:build build_qt // +build build_qt package qt @@ -100,31 +101,15 @@ func (f *FrontendQt) setIsBetaEnabled() { } func (f *FrontendQt) toggleBeta(makeItEnabled bool) { - channel := f.bridge.GetUpdateChannel() - - if makeItEnabled == (channel == updater.EarlyChannel) { - f.qml.SetIsBetaEnabled(makeItEnabled) - return - } - - channel = updater.StableChannel + channel := updater.StableChannel if makeItEnabled { channel = updater.EarlyChannel } - needRestart, err := f.bridge.SetUpdateChannel(channel) - f.setIsBetaEnabled() - - if err != nil { - f.log.WithError(err).Warn("Switching udpate channel failed.") - f.qml.UpdateManualError() - return - } + f.bridge.SetUpdateChannel(channel) - if needRestart { - f.restart() - return - } + f.setIsBetaEnabled() - f.checkUpdatesAndNotify(false) + // Immediately check the updates to set the correct landing page link. + f.checkUpdates() } diff --git a/internal/frontend/types/types.go b/internal/frontend/types/types.go index 926f12f..84758eb 100644 --- a/internal/frontend/types/types.go +++ b/internal/frontend/types/types.go @@ -81,7 +81,7 @@ type Bridger interface { DisableCache() error MigrateCache(from, to string) error GetUpdateChannel() updater.UpdateChannel - SetUpdateChannel(updater.UpdateChannel) (needRestart bool, err error) + SetUpdateChannel(updater.UpdateChannel) GetKeychainApp() string SetKeychainApp(keychain string) } diff --git a/internal/versioner/remove_default.go b/internal/versioner/remove.go similarity index 86% rename from internal/versioner/remove_default.go rename to internal/versioner/remove.go index 3e7ae2a..e0edfcb 100644 --- a/internal/versioner/remove_default.go +++ b/internal/versioner/remove.go @@ -15,6 +15,7 @@ // You should have received a copy of the GNU General Public License // along with ProtonMail Bridge. If not, see . +//go:build !darwin // +build !darwin package versioner @@ -23,6 +24,7 @@ import ( "os" "github.com/Masterminds/semver/v3" + "github.com/ProtonMail/proton-bridge/internal/constants" "github.com/sirupsen/logrus" ) @@ -58,6 +60,12 @@ func (v *Versioner) RemoveOtherVersions(versionToKeep *semver.Version) error { if version.Equal(versionToKeep) { 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 { logrus.WithError(err).Error("Failed to remove old app version") } diff --git a/internal/versioner/remove_darwin.go b/internal/versioner/remove_darwin.go index f9a1b05..ba756ff 100644 --- a/internal/versioner/remove_darwin.go +++ b/internal/versioner/remove_darwin.go @@ -15,6 +15,9 @@ // You should have received a copy of the GNU General Public License // along with ProtonMail Bridge. If not, see . +//go:build darwin +// +build darwin + package versioner 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. 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 +} diff --git a/internal/versioner/remove_linux.go b/internal/versioner/remove_linux.go new file mode 100644 index 0000000..9ca40c7 --- /dev/null +++ b/internal/versioner/remove_linux.go @@ -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 . + +//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)) +} diff --git a/internal/versioner/remove_windows.go b/internal/versioner/remove_windows.go new file mode 100644 index 0000000..d38c9f2 --- /dev/null +++ b/internal/versioner/remove_windows.go @@ -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 . + +//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))) +} diff --git a/internal/versioner/versioner.go b/internal/versioner/versioner.go index b7eadd1..0a40612 100644 --- a/internal/versioner/versioner.go +++ b/internal/versioner/versioner.go @@ -29,6 +29,7 @@ import ( var ( ErrNoVersions = errors.New("no available versions") ErrNoExecutable = errors.New("no executable found") + ErrNoRemoveBase = errors.New("can't remove base version") ) // Versioner manages a directory of versioned app directories.