GODT-976 Exclude updates from clearing cache and clear cache, including updates, while switching early access off

This commit is contained in:
Michal Horejsek
2021-01-27 09:16:04 +01:00
parent c6107dbd4b
commit 7468ed7dc0
18 changed files with 241 additions and 13 deletions

View File

@ -65,7 +65,7 @@ func run(b *base.Base, c *cli.Context) error { // nolint[funlen]
logrus.WithError(err).Fatal("Failed to load TLS config") logrus.WithError(err).Fatal("Failed to load TLS config")
} }
bridge := bridge.New(b.Locations, b.Cache, b.Settings, b.CrashHandler, b.Listener, b.CM, b.Creds) bridge := bridge.New(b.Locations, b.Cache, b.Settings, b.CrashHandler, b.Listener, b.CM, b.Creds, b.Updater, b.Versioner)
imapBackend := imap.NewIMAPBackend(b.CrashHandler, b.Listener, b.Cache, bridge) imapBackend := imap.NewIMAPBackend(b.CrashHandler, b.Listener, b.Cache, bridge)
smtpBackend := smtp.NewSMTPBackend(b.CrashHandler, b.Listener, b.Settings, bridge) smtpBackend := smtp.NewSMTPBackend(b.CrashHandler, b.Listener, b.Settings, bridge)

View File

@ -26,6 +26,7 @@ import (
"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"
"github.com/ProtonMail/proton-bridge/internal/updater"
"github.com/ProtonMail/proton-bridge/internal/users" "github.com/ProtonMail/proton-bridge/internal/users"
"github.com/ProtonMail/proton-bridge/pkg/pmapi" "github.com/ProtonMail/proton-bridge/pkg/pmapi"
@ -40,8 +41,11 @@ var (
type Bridge struct { type Bridge struct {
*users.Users *users.Users
locations Locator
settings SettingsProvider settings SettingsProvider
clientManager users.ClientManager clientManager users.ClientManager
updater Updater
versioner Versioner
userAgentClientName string userAgentClientName string
userAgentClientVersion string userAgentClientVersion string
@ -56,6 +60,8 @@ func New(
eventListener listener.Listener, eventListener listener.Listener,
clientManager users.ClientManager, clientManager users.ClientManager,
credStorer users.CredentialsStorer, credStorer users.CredentialsStorer,
updater Updater,
versioner Versioner,
) *Bridge { ) *Bridge {
// Allow DoH before starting the app if the user has previously set this setting. // Allow DoH before starting the app if the user has previously set this setting.
// This allows us to start even if protonmail is blocked. // This allows us to start even if protonmail is blocked.
@ -68,8 +74,11 @@ func New(
b := &Bridge{ b := &Bridge{
Users: u, Users: u,
locations: locations,
settings: s, settings: s,
clientManager: clientManager, clientManager: clientManager,
updater: updater,
versioner: versioner,
} }
if s.GetBool(settings.FirstStartKey) { if s.GetBool(settings.FirstStartKey) {
@ -168,3 +177,36 @@ func (b *Bridge) ReportBug(osType, osVersion, description, accountName, address,
return nil return nil
} }
// GetUpdateChannel returns currently set update channel.
func (b *Bridge) GetUpdateChannel() updater.UpdateChannel {
return updater.UpdateChannel(b.settings.Get(settings.UpdateChannelKey))
}
// 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) error {
b.settings.Set(settings.UpdateChannelKey, string(channel))
version, err := b.updater.Check()
if err != nil {
return err
}
if b.updater.IsDowngrade(version) {
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")
}
}
if err := b.updater.InstallUpdate(version); err != nil {
return err
}
return b.versioner.RemoveOtherVersions(version.Version)
}

View File

@ -17,8 +17,15 @@
package bridge package bridge
import (
"github.com/Masterminds/semver/v3"
"github.com/ProtonMail/proton-bridge/internal/updater"
)
type Locator interface { type Locator interface {
Clear() error Clear() error
ClearUpdates() error
} }
type Cacher interface { type Cacher interface {
@ -32,3 +39,13 @@ type SettingsProvider interface {
GetBool(key string) bool GetBool(key string) bool
SetBool(key string, val bool) SetBool(key string, val bool)
} }
type Updater interface {
Check() (updater.VersionInfo, error)
IsDowngrade(updater.VersionInfo) bool
InstallUpdate(updater.VersionInfo) error
}
type Versioner interface {
RemoveOtherVersions(*semver.Version) error
}

View File

@ -294,7 +294,7 @@ Dialog {
} }
}, },
State { State {
name: "toggleEarlyAccess" name: "toggleEarlyAccessOn"
PropertyChanges { PropertyChanges {
target: root target: root
currentIndex : 0 currentIndex : 0
@ -304,6 +304,17 @@ Dialog {
answer : qsTr("Enabling early access...") answer : qsTr("Enabling early access...")
} }
}, },
State {
name: "toggleEarlyAccessOff"
PropertyChanges {
target: root
currentIndex : 0
question : qsTr("Are you sure you want to leave early access? Please keep in mind this operation clears the cache and restarts Bridge.")
note : qsTr("This will delete all of your stored preferences as well as cached email data for all accounts, temporarily slowing down the email download process significantly.")
title : qsTr("Disable early access")
answer : qsTr("Disabling early access...")
}
},
State { State {
name: "noKeychain" name: "noKeychain"
PropertyChanges { PropertyChanges {
@ -375,7 +386,8 @@ Dialog {
if ( state == "logout" ) { go.logoutAccount (input) } if ( state == "logout" ) { go.logoutAccount (input) }
if ( state == "toggleAutoStart" ) { go.toggleAutoStart () } if ( state == "toggleAutoStart" ) { go.toggleAutoStart () }
if ( state == "toggleAllowProxy" ) { go.toggleAllowProxy () } if ( state == "toggleAllowProxy" ) { go.toggleAllowProxy () }
if ( state == "toggleEarlyAccess" ) { go.toggleEarlyAccess () } if ( state == "toggleEarlyAccessOn" ) { go.toggleEarlyAccess () }
if ( state == "toggleEarlyAccessOff" ) { go.toggleEarlyAccess () }
if ( state == "quit" ) { Qt.quit () } if ( state == "quit" ) { Qt.quit () }
if ( state == "instance exists" ) { Qt.quit () } if ( state == "instance exists" ) { Qt.quit () }
if ( state == "noKeychain" ) { Qt.quit () } if ( state == "noKeychain" ) { Qt.quit () }

View File

@ -150,9 +150,10 @@ Item {
) + " " + text ) + " " + text
onClicked: { onClicked: {
if (go.isEarlyAccess == true) { if (go.isEarlyAccess == true) {
go.toggleEarlyAccess() dialogGlobal.state="toggleEarlyAccessOff"
dialogGlobal.show()
} else { } else {
dialogGlobal.state="toggleEarlyAccess" dialogGlobal.state="toggleEarlyAccessOn"
dialogGlobal.show() dialogGlobal.show()
} }
} }

View File

@ -26,6 +26,7 @@ import (
"github.com/ProtonMail/proton-bridge/internal/bridge" "github.com/ProtonMail/proton-bridge/internal/bridge"
"github.com/ProtonMail/proton-bridge/internal/config/settings" "github.com/ProtonMail/proton-bridge/internal/config/settings"
"github.com/ProtonMail/proton-bridge/internal/events" "github.com/ProtonMail/proton-bridge/internal/events"
"github.com/ProtonMail/proton-bridge/internal/updater"
"github.com/ProtonMail/proton-bridge/pkg/keychain" "github.com/ProtonMail/proton-bridge/pkg/keychain"
pmapi "github.com/ProtonMail/proton-bridge/pkg/pmapi" pmapi "github.com/ProtonMail/proton-bridge/pkg/pmapi"
) )
@ -80,6 +81,15 @@ func (s *FrontendQt) loadAccounts() {
func (s *FrontendQt) clearCache() { func (s *FrontendQt) clearCache() {
defer s.Qml.ProcessFinished() defer s.Qml.ProcessFinished()
channel := s.bridge.GetUpdateChannel()
if channel == updater.EarlyChannel {
if err := s.bridge.SetUpdateChannel(updater.StableChannel); err != nil {
s.Qml.NotifyManualUpdateError()
return
}
}
if err := s.bridge.ClearData(); err != nil { if err := s.bridge.ClearData(); err != nil {
log.Error("While clearing cache: ", err) log.Error("While clearing cache: ", err)
} }

View File

@ -578,13 +578,21 @@ func (s *FrontendQt) toggleAutoUpdate() {
func (s *FrontendQt) toggleEarlyAccess() { func (s *FrontendQt) toggleEarlyAccess() {
defer s.Qml.ProcessFinished() defer s.Qml.ProcessFinished()
if updater.UpdateChannel(s.settings.Get(settings.UpdateChannelKey)) == updater.EarlyChannel { channel := s.bridge.GetUpdateChannel()
s.settings.Set(settings.UpdateChannelKey, string(updater.StableChannel)) if channel == updater.EarlyChannel {
s.Qml.SetIsEarlyAccess(false) channel = updater.StableChannel
} else { } else {
s.settings.Set(settings.UpdateChannelKey, string(updater.EarlyChannel)) channel = updater.EarlyChannel
s.Qml.SetIsEarlyAccess(true)
} }
err := s.bridge.SetUpdateChannel(channel)
s.Qml.SetIsEarlyAccess(channel == updater.EarlyChannel)
if err != nil {
s.Qml.NotifyManualUpdateError()
return
}
s.restarter.SetToRestart()
s.App.Quit()
} }
func (s *FrontendQt) toggleAllowProxy() { func (s *FrontendQt) toggleAllowProxy() {

View File

@ -80,6 +80,8 @@ type Bridger interface {
ReportBug(osType, osVersion, description, accountName, address, emailClient string) error ReportBug(osType, osVersion, description, accountName, address, emailClient string) error
AllowProxy() AllowProxy()
DisallowProxy() DisallowProxy()
GetUpdateChannel() updater.UpdateChannel
SetUpdateChannel(updater.UpdateChannel) error
} }
type bridgeWrap struct { type bridgeWrap struct {

View File

@ -165,12 +165,20 @@ func (l *Locations) getUpdatesPath() string {
return filepath.Join(l.userCache, "updates") return filepath.Join(l.userCache, "updates")
} }
// Clear removes everything except the lock file. // Clear removes everything except the lock and update files.
func (l *Locations) Clear() error { func (l *Locations) Clear() error {
return files.Remove( return files.Remove(
l.getSettingsPath(), l.getSettingsPath(),
l.getLogsPath(), l.getLogsPath(),
l.getCachePath(), l.getCachePath(),
).Except(
l.getUpdatesPath(),
).Do()
}
// ClearUpdates removes update files.
func (l *Locations) ClearUpdates() error {
return files.Remove(
l.getUpdatesPath(), l.getUpdatesPath(),
).Do() ).Do()
} }

View File

@ -39,7 +39,7 @@ func (dirs *fakeAppDirs) UserCache() string {
return dirs.cacheDir return dirs.cacheDir
} }
func TestClearRemovesEverythingExceptLockFile(t *testing.T) { func TestClearRemovesEverythingExceptLockAndUpdateFiles(t *testing.T) {
l := newTestLocations(t) l := newTestLocations(t)
assert.NoError(t, l.Clear()) assert.NoError(t, l.Clear())
@ -48,6 +48,18 @@ func TestClearRemovesEverythingExceptLockFile(t *testing.T) {
assert.NoDirExists(t, l.getSettingsPath()) assert.NoDirExists(t, l.getSettingsPath())
assert.NoDirExists(t, l.getLogsPath()) assert.NoDirExists(t, l.getLogsPath())
assert.NoDirExists(t, l.getCachePath()) assert.NoDirExists(t, l.getCachePath())
assert.DirExists(t, l.getUpdatesPath())
}
func TestClearUpdateFiles(t *testing.T) {
l := newTestLocations(t)
assert.NoError(t, l.ClearUpdates())
assert.FileExists(t, l.GetLockFile())
assert.DirExists(t, l.getSettingsPath())
assert.DirExists(t, l.getLogsPath())
assert.DirExists(t, l.getCachePath())
assert.NoDirExists(t, l.getUpdatesPath()) assert.NoDirExists(t, l.getUpdatesPath())
} }

View File

@ -125,6 +125,10 @@ func (u *Updater) IsUpdateApplicable(version VersionInfo) bool {
return true return true
} }
func (u *Updater) IsDowngrade(version VersionInfo) bool {
return version.Version.LessThan(u.curVer)
}
func (u *Updater) CanInstall(version VersionInfo) bool { func (u *Updater) CanInstall(version VersionInfo) bool {
if version.MinAuto == nil { if version.MinAuto == nil {
return true return true

View File

@ -17,8 +17,16 @@
package versioner package versioner
import "github.com/Masterminds/semver/v3"
// RemoveOldVersions removes all but the latest app version. // RemoveOldVersions removes all but the latest app version.
func (v *Versioner) RemoveOldVersions() error { func (v *Versioner) RemoveOldVersions() 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 all but the specific provided app version.
func (v *Versioner) RemoveOtherVersions(versionToKeep *semver.Version) error {
// darwin does not use the versioner; removal is a noop.
return nil
}

View File

@ -22,6 +22,7 @@ package versioner
import ( import (
"os" "os"
"github.com/Masterminds/semver/v3"
"github.com/sirupsen/logrus" "github.com/sirupsen/logrus"
) )
@ -45,3 +46,22 @@ func (v *Versioner) RemoveOldVersions() error {
return nil return nil
} }
// RemoveOtherVersions removes all but the specific provided app version.
func (v *Versioner) RemoveOtherVersions(versionToKeep *semver.Version) error {
versions, err := v.ListVersions()
if err != nil {
return err
}
for _, version := range versions {
if version.Equal(versionToKeep) {
continue
}
if err := os.RemoveAll(version.path); err != nil {
logrus.WithError(err).Error("Failed to remove old app version")
}
}
return nil
}

View File

@ -55,6 +55,10 @@ func (v *Version) String() string {
return fmt.Sprintf("%v", v.version) return fmt.Sprintf("%v", v.version)
} }
func (v *Version) Equal(version *semver.Version) bool {
return v.version.Equal(version)
}
// VerifyFiles verifies all files in the version directory. // VerifyFiles verifies all files in the version directory.
func (v *Version) VerifyFiles(kr *crypto.KeyRing) error { func (v *Version) VerifyFiles(kr *crypto.KeyRing) error {
fileBytes, err := ioutil.ReadFile(filepath.Join(v.path, sumFile)) // nolint[gosec] fileBytes, err := ioutil.ReadFile(filepath.Join(v.path, sumFile)) // nolint[gosec]

View File

@ -68,5 +68,7 @@ func newBridgeInstance(
clientManager users.ClientManager, clientManager users.ClientManager,
) *bridge.Bridge { ) *bridge.Bridge {
panicHandler := &panicHandler{t: t} panicHandler := &panicHandler{t: t}
return bridge.New(locations, cache, settings, panicHandler, eventListener, clientManager, credStore) updater := newFakeUpdater()
versioner := newFakeVersioner()
return bridge.New(locations, cache, settings, panicHandler, eventListener, clientManager, credStore, updater, versioner)
} }

View File

@ -48,3 +48,7 @@ func (l *fakeLocations) ProvideSettingsPath() (string, error) {
func (l *fakeLocations) Clear() error { func (l *fakeLocations) Clear() error {
return os.RemoveAll(l.dir) return os.RemoveAll(l.dir)
} }
func (l *fakeLocations) ClearUpdates() error {
return nil
}

41
test/context/updater.go Normal file
View File

@ -0,0 +1,41 @@
// Copyright (c) 2021 Proton Technologies AG
//
// This file is part of ProtonMail Bridge.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/>.
package context
import (
"github.com/ProtonMail/proton-bridge/internal/updater"
)
type fakeUpdater struct{}
// newFakeUpdater creates an empty updater just to fulfill Bridge dependencies.
func newFakeUpdater() *fakeUpdater {
return &fakeUpdater{}
}
func (c *fakeUpdater) Check() (updater.VersionInfo, error) {
return updater.VersionInfo{}, nil
}
func (c *fakeUpdater) IsDowngrade(_ updater.VersionInfo) bool {
return false
}
func (c *fakeUpdater) InstallUpdate(_ updater.VersionInfo) error {
return nil
}

33
test/context/versioner.go Normal file
View File

@ -0,0 +1,33 @@
// Copyright (c) 2021 Proton Technologies AG
//
// This file is part of ProtonMail Bridge.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/>.
package context
import (
"github.com/Masterminds/semver/v3"
)
type fakeVersioner struct{}
// newFakeVersioner creates an empty versioner just to fulfill Bridge dependencies.
func newFakeVersioner() *fakeVersioner {
return &fakeVersioner{}
}
func (c *fakeVersioner) RemoveOtherVersions(_ *semver.Version) error {
return nil
}