From d2066173f0684eed383fbf4463e3cf32f7336083 Mon Sep 17 00:00:00 2001 From: James Houlahan Date: Thu, 3 Dec 2020 15:36:02 +0100 Subject: [PATCH] feat: early access --- Makefile | 4 +- go.sum | 3 + internal/app/base/base.go | 2 +- internal/config/settings/kvs.go | 5 ++ internal/config/settings/settings.go | 2 + .../frontend/qml/BridgeUI/DialogYesNo.qml | 39 ++++++--- .../frontend/qml/BridgeUI/SettingsView.qml | 25 +++++- internal/frontend/qml/tst_Gui.qml | 8 ++ internal/frontend/qt/frontend.go | 18 ++++ internal/frontend/qt/ui.go | 3 + internal/updater/channel_default.go | 24 ------ .../updater/{channel_beta.go => channels.go} | 13 ++- internal/updater/host_default.go | 2 +- internal/updater/host_qa.go | 2 +- internal/updater/key_default.go | 2 +- internal/updater/key_qa.go | 2 +- internal/updater/updater.go | 32 +++++-- internal/updater/updater_test.go | 85 +++++++++++++++++-- pkg/pmapi/config_default.go | 2 +- pkg/pmapi/config_qa.go | 2 +- pkg/sum/sum.go | 17 ++-- utils/hasher/main.go | 15 +++- 22 files changed, 235 insertions(+), 72 deletions(-) delete mode 100644 internal/updater/channel_default.go rename internal/updater/{channel_beta.go => channels.go} (69%) diff --git a/Makefile b/Makefile index 133128fd..d57c39c5 100644 --- a/Makefile +++ b/Makefile @@ -81,10 +81,10 @@ build-ie-nogui: TARGET_CMD=Import-Export $(MAKE) build-nogui build-launcher: - go build -ldflags="-X 'main.ConfigName=bridge' -X 'main.ExeName=proton-bridge'" -o launcher-bridge cmd/launcher/main.go + go build -tags='${BUILD_TAGS}' -ldflags="-X 'main.ConfigName=bridge' -X 'main.ExeName=proton-bridge'" -o launcher-bridge cmd/launcher/main.go build-launcher-ie: - go build -ldflags="-X 'main.ConfigName=importExport' -X 'main.ExeName=Import-Export'" -o launcher-ie cmd/launcher/main.go + go build -tags='${BUILD_TAGS}' -ldflags="-X 'main.ConfigName=importExport' -X 'main.ExeName=Import-Export'" -o launcher-ie cmd/launcher/main.go versioner: go build ${BUILD_FLAGS} ${GO_LDFLAGS} -o versioner utils/versioner/main.go diff --git a/go.sum b/go.sum index df53cf03..1dfd90e6 100644 --- a/go.sum +++ b/go.sum @@ -348,6 +348,9 @@ golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3 golang.org/x/tools v0.0.0-20190420181800-aa740d480789/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= golang.org/x/tools v0.0.0-20191216052735-49a3e744a425/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200117012304-6edc0a871e69 h1:yBHHx+XZqXJBm6Exke3N7V9gnlsyXxoCPEb1yVenjfk= +golang.org/x/tools v0.0.0-20200117012304-6edc0a871e69/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= diff --git a/internal/app/base/base.go b/internal/app/base/base.go index 5f400efe..ce15c153 100644 --- a/internal/app/base/base.go +++ b/internal/app/base/base.go @@ -180,11 +180,11 @@ func New( // nolint[funlen] updater := updater.New( cm, installer, + settingsObj, kr, semver.MustParse(constants.Version), updateURLName, runtime.GOOS, - settingsObj.GetFloat64(settings.RolloutKey), ) return &Base{ diff --git a/internal/config/settings/kvs.go b/internal/config/settings/kvs.go index bd2ecb9b..2be45b42 100644 --- a/internal/config/settings/kvs.go +++ b/internal/config/settings/kvs.go @@ -20,6 +20,7 @@ package settings import ( "encoding/json" "errors" + "fmt" "os" "strconv" "sync" @@ -135,3 +136,7 @@ func (p *keyValueStore) SetBool(key string, value bool) { func (p *keyValueStore) SetInt(key string, value int) { p.Set(key, strconv.Itoa(value)) } + +func (p *keyValueStore) SetFloat64(key string, value float64) { + p.Set(key, fmt.Sprintf("%v", value)) +} diff --git a/internal/config/settings/settings.go b/internal/config/settings/settings.go index f09258e2..864a5859 100644 --- a/internal/config/settings/settings.go +++ b/internal/config/settings/settings.go @@ -40,6 +40,7 @@ const ( CookiesKey = "cookies" ReportOutgoingNoEncKey = "report_outgoing_email_without_encryption" LastVersionKey = "last_used_version" + UpdateChannelKey = "update_channel" RolloutKey = "rollout" ) @@ -75,6 +76,7 @@ func (s *Settings) setDefaultValues() { s.setDefault(AutoUpdateKey, "true") s.setDefault(ReportOutgoingNoEncKey, "false") s.setDefault(LastVersionKey, "") + s.setDefault(UpdateChannelKey, "") s.setDefault(RolloutKey, fmt.Sprintf("%v", rand.Float64())) s.setDefault(APIPortKey, DefaultAPIPort) diff --git a/internal/frontend/qml/BridgeUI/DialogYesNo.qml b/internal/frontend/qml/BridgeUI/DialogYesNo.qml index 82e38491..6086ed90 100644 --- a/internal/frontend/qml/BridgeUI/DialogYesNo.qml +++ b/internal/frontend/qml/BridgeUI/DialogYesNo.qml @@ -137,6 +137,7 @@ Dialog { spacing: Style.dialog.spacing ButtonRounded { id:buttonNo + visible: root.state != "toggleEarlyAccess" color_main: Style.dialog.text fa_icon: Style.fa.times text: qsTr("No") @@ -148,7 +149,7 @@ Dialog { color_minor: Style.main.textBlue isOpaque: true fa_icon: Style.fa.check - text: qsTr("Yes") + text: root.state == "toggleEarlyAccess" ? qsTr("Ok") : qsTr("Yes") onClicked : { currentIndex=1 root.confirmed() @@ -292,6 +293,17 @@ Dialog { } } }, + State { + name: "toggleEarlyAccess" + PropertyChanges { + target: root + currentIndex : 0 + question : qsTr("Do you want to be the first to get the latest updates? Please keep in mind that early versions may be less stable.") + note : "" + title : qsTr("Enable early access") + answer : qsTr("Enabling early access...") + } + }, State { name: "noKeychain" PropertyChanges { @@ -343,8 +355,6 @@ Dialog { root.visible = true } - - onConfirmed : { if (state == "quit" || state == "instance exists" ) { timer.interval = 1000 @@ -358,17 +368,18 @@ Dialog { Connections { target: timer onTriggered: { - if ( state == "addressmode" ) { go.switchAddressMode (input) } - if ( state == "clearChain" ) { go.clearKeychain () } - if ( state == "clearCache" ) { go.clearCache () } - if ( state == "deleteUser" ) { go.deleteAccount (input, checkBoxWrapper.isChecked) } - if ( state == "logout" ) { go.logoutAccount (input) } - if ( state == "toggleAutoStart" ) { go.toggleAutoStart () } - if ( state == "toggleAllowProxy" ) { go.toggleAllowProxy () } - if ( state == "quit" ) { Qt.quit () } - if ( state == "instance exists" ) { Qt.quit () } - if ( state == "noKeychain" ) { Qt.quit () } - if ( state == "checkUpdates" ) { } + if ( state == "addressmode" ) { go.switchAddressMode (input) } + if ( state == "clearChain" ) { go.clearKeychain () } + if ( state == "clearCache" ) { go.clearCache () } + if ( state == "deleteUser" ) { go.deleteAccount (input, checkBoxWrapper.isChecked) } + if ( state == "logout" ) { go.logoutAccount (input) } + if ( state == "toggleAutoStart" ) { go.toggleAutoStart () } + if ( state == "toggleAllowProxy" ) { go.toggleAllowProxy () } + if ( state == "toggleEarlyAccess" ) { go.toggleEarlyAccess () } + if ( state == "quit" ) { Qt.quit () } + if ( state == "instance exists" ) { Qt.quit () } + if ( state == "noKeychain" ) { Qt.quit () } + if ( state == "checkUpdates" ) { } } } diff --git a/internal/frontend/qml/BridgeUI/SettingsView.qml b/internal/frontend/qml/BridgeUI/SettingsView.qml index d5980644..869cf5da 100644 --- a/internal/frontend/qml/BridgeUI/SettingsView.qml +++ b/internal/frontend/qml/BridgeUI/SettingsView.qml @@ -116,6 +116,30 @@ Item { } } + ButtonIconText { + id: earlyAccess + text: qsTr("Early access", "label for toggle that enables and disables early access") + leftIcon.text : Style.fa.star + rightIcon { + font.pointSize : Style.settings.toggleSize * Style.pt + text : go.isEarlyAccess!=false ? Style.fa.toggle_on : Style.fa.toggle_off + color : go.isEarlyAccess!=false ? Style.main.textBlue : Style.main.textDisabled + } + Accessible.description: ( + go.isEarlyAccess == false ? + qsTr("Enable" , "Click to enable early access") : + qsTr("Disable" , "Click to disable early access") + ) + " " + text + onClicked: { + if (go.isEarlyAccess == true) { + go.toggleEarlyAccess() + } else { + dialogGlobal.state="toggleEarlyAccess" + dialogGlobal.show() + } + } + } + ButtonIconText { id: advancedSettings property bool isAdvanced : !go.isDefaultPort @@ -196,7 +220,6 @@ Item { dialogGlobal.show() } } - } } } diff --git a/internal/frontend/qml/tst_Gui.qml b/internal/frontend/qml/tst_Gui.qml index 8790c96a..3da0773c 100644 --- a/internal/frontend/qml/tst_Gui.qml +++ b/internal/frontend/qml/tst_Gui.qml @@ -267,6 +267,7 @@ Window { property bool isAutoStart : true property bool isAutoUpdate : false + property bool isEarlyAccess : false property bool isProxyAllowed : false property bool isFirstStart : false property bool isFreshVersion : false @@ -336,6 +337,7 @@ Window { signal processFinished() signal toggleAutoStart() + signal toggleEarlyAccess() signal toggleAutoUpdate() signal notifyBubble(int tabIndex, string message) signal silentBubble(int tabIndex, string message) @@ -627,6 +629,12 @@ Window { isAutoUpdate = (isAutoUpdate!=false) ? false : true console.log (" Test: onToggleAutoUpdate "+isAutoUpdate) } + + onToggleEarlyAccess: { + workAndClose() + isEarlyAccess = (isEarlyAccess!=false) ? false : true + console.log (" Test: onToggleEarlyAccess "+isEarlyAccess) + } } } diff --git a/internal/frontend/qt/frontend.go b/internal/frontend/qt/frontend.go index 948afe76..e3f3bf44 100644 --- a/internal/frontend/qt/frontend.go +++ b/internal/frontend/qt/frontend.go @@ -370,6 +370,12 @@ func (s *FrontendQt) qtExecute(Procedure func(*FrontendQt) error) error { s.Qml.SetIsProxyAllowed(false) } + if updater.UpdateChannel(s.settings.Get(settings.UpdateChannelKey)) == updater.BetaChannel { + s.Qml.SetIsEarlyAccess(true) + } else { + s.Qml.SetIsEarlyAccess(false) + } + s.eventListener.RetryEmit(events.TLSCertIssue) s.eventListener.RetryEmit(events.ErrorEvent) @@ -548,6 +554,18 @@ func (s *FrontendQt) toggleAutoUpdate() { } } +func (s *FrontendQt) toggleEarlyAccess() { + defer s.Qml.ProcessFinished() + + if updater.UpdateChannel(s.settings.Get(settings.UpdateChannelKey)) == updater.BetaChannel { + s.settings.Set(settings.UpdateChannelKey, string(updater.LiveChannel)) + s.Qml.SetIsEarlyAccess(false) + } else { + s.settings.Set(settings.UpdateChannelKey, string(updater.BetaChannel)) + s.Qml.SetIsEarlyAccess(true) + } +} + func (s *FrontendQt) toggleAllowProxy() { defer s.Qml.ProcessFinished() diff --git a/internal/frontend/qt/ui.go b/internal/frontend/qt/ui.go index c1115078..c53006d0 100644 --- a/internal/frontend/qt/ui.go +++ b/internal/frontend/qt/ui.go @@ -35,6 +35,7 @@ type GoQMLInterface struct { _ bool `property:"isAutoStart"` _ bool `property:"isAutoUpdate"` + _ bool `property:"isEarlyAccess"` _ bool `property:"isProxyAllowed"` _ string `property:"currentAddress"` _ string `property:"goos"` @@ -94,6 +95,7 @@ type GoQMLInterface struct { _ func() `slot:"toggleAutoStart"` _ func() `slot:"toggleAutoUpdate"` + _ func() `slot:"toggleEarlyAccess"` _ func() `slot:"toggleAllowProxy"` _ func() `slot:"loadAccounts"` _ func() `slot:"openLogs"` @@ -157,6 +159,7 @@ func (s *GoQMLInterface) init() {} // SetFrontend connects all slots and signals from Go to QML. func (s *GoQMLInterface) SetFrontend(f *FrontendQt) { s.ConnectToggleAutoStart(f.toggleAutoStart) + s.ConnectToggleEarlyAccess(f.toggleEarlyAccess) s.ConnectToggleAutoUpdate(f.toggleAutoUpdate) s.ConnectToggleAllowProxy(f.toggleAllowProxy) s.ConnectLoadAccounts(f.loadAccounts) diff --git a/internal/updater/channel_default.go b/internal/updater/channel_default.go deleted file mode 100644 index b9878af6..00000000 --- a/internal/updater/channel_default.go +++ /dev/null @@ -1,24 +0,0 @@ -// 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 . - -// +build !beta - -package updater - -// Channel is the channel of updates users are subscribed to. -// For now it is hardcoded in the build. In future, it might be selectable in settings. -const Channel = "live" diff --git a/internal/updater/channel_beta.go b/internal/updater/channels.go similarity index 69% rename from internal/updater/channel_beta.go rename to internal/updater/channels.go index 6463ee4b..b47d22fb 100644 --- a/internal/updater/channel_beta.go +++ b/internal/updater/channels.go @@ -15,8 +15,15 @@ // You should have received a copy of the GNU General Public License // along with ProtonMail Bridge. If not, see . -// +build beta - package updater -const Channel = "beta" +// UpdateChannel represents an update channel users can be subscribed to. +type UpdateChannel string + +const ( + // LiveChannel is the channel all users are subscribed to by default. + LiveChannel UpdateChannel = "live" + + // BetaChannel is the channel users subscribe to when they enable "Early Access". + BetaChannel UpdateChannel = "beta" +) diff --git a/internal/updater/host_default.go b/internal/updater/host_default.go index 7d3edee3..efd846f5 100644 --- a/internal/updater/host_default.go +++ b/internal/updater/host_default.go @@ -15,7 +15,7 @@ // You should have received a copy of the GNU General Public License // along with ProtonMail Bridge. If not, see . -// +build !pmapi_qa +// +build !build_qa package updater diff --git a/internal/updater/host_qa.go b/internal/updater/host_qa.go index e8fb8b5b..e131f5a6 100644 --- a/internal/updater/host_qa.go +++ b/internal/updater/host_qa.go @@ -15,7 +15,7 @@ // You should have received a copy of the GNU General Public License // along with ProtonMail Bridge. If not, see . -// +build pmapi_qa +// +build build_qa package updater diff --git a/internal/updater/key_default.go b/internal/updater/key_default.go index 4ae95c7b..73fe0e28 100644 --- a/internal/updater/key_default.go +++ b/internal/updater/key_default.go @@ -15,7 +15,7 @@ // You should have received a copy of the GNU General Public License // along with ProtonMail Bridge. If not, see . -// +build !pmapi_qa +// +build !build_qa package updater diff --git a/internal/updater/key_qa.go b/internal/updater/key_qa.go index b5246843..601926ce 100644 --- a/internal/updater/key_qa.go +++ b/internal/updater/key_qa.go @@ -15,7 +15,7 @@ // You should have received a copy of the GNU General Public License // along with ProtonMail Bridge. If not, see . -// +build pmapi_qa +// +build build_qa package updater diff --git a/internal/updater/updater.go b/internal/updater/updater.go index aa85de3c..67ba0477 100644 --- a/internal/updater/updater.go +++ b/internal/updater/updater.go @@ -23,6 +23,7 @@ import ( "github.com/Masterminds/semver/v3" "github.com/ProtonMail/gopenpgp/v2/crypto" + "github.com/ProtonMail/proton-bridge/internal/config/settings" "github.com/ProtonMail/proton-bridge/pkg/pmapi" "github.com/pkg/errors" "github.com/sirupsen/logrus" @@ -38,15 +39,21 @@ type Installer interface { InstallUpdate(*semver.Version, io.Reader) error } +type Settings interface { + Get(string) string + Set(string, string) + GetFloat64(string) float64 +} + type Updater struct { cm ClientProvider installer Installer + settings Settings kr *crypto.KeyRing curVer *semver.Version updateURLName string platform string - rollout float64 locker *locker } @@ -54,19 +61,25 @@ type Updater struct { func New( cm ClientProvider, installer Installer, + s Settings, kr *crypto.KeyRing, curVer *semver.Version, updateURLName, platform string, - rollout float64, ) *Updater { + // If there's some unexpected value in the preferences, we force it back onto the live channel. + // This prevents users from screwing up silent updates by modifying their prefs.json file. + if channel := UpdateChannel(s.Get(settings.UpdateChannelKey)); !(channel == LiveChannel || channel == BetaChannel) { + s.Set(settings.UpdateChannelKey, string(LiveChannel)) + } + return &Updater{ cm: cm, installer: installer, + settings: s, kr: kr, curVer: curVer, updateURLName: updateURLName, platform: platform, - rollout: rollout, locker: newLocker(), } } @@ -92,7 +105,12 @@ func (u *Updater) Check() (VersionInfo, error) { return VersionInfo{}, err } - return versionMap[Channel], nil + version, ok := versionMap[u.settings.Get(settings.UpdateChannelKey)] + if !ok { + return VersionInfo{}, errors.New("no updates available for this channel") + } + + return version, nil } func (u *Updater) IsUpdateApplicable(version VersionInfo) bool { @@ -100,7 +118,7 @@ func (u *Updater) IsUpdateApplicable(version VersionInfo) bool { return false } - if u.rollout > version.Rollout { + if u.settings.GetFloat64(settings.RolloutKey) > version.Rollout { return false } @@ -108,6 +126,10 @@ func (u *Updater) IsUpdateApplicable(version VersionInfo) bool { } func (u *Updater) CanInstall(version VersionInfo) bool { + if version.MinAuto == nil { + return true + } + return !u.curVer.LessThan(version.MinAuto) } diff --git a/internal/updater/updater_test.go b/internal/updater/updater_test.go index e0dc329c..b1fe68bf 100644 --- a/internal/updater/updater_test.go +++ b/internal/updater/updater_test.go @@ -22,11 +22,13 @@ import ( "encoding/json" "errors" "io" + "io/ioutil" "sync" "testing" "time" "github.com/Masterminds/semver/v3" + "github.com/ProtonMail/proton-bridge/internal/config/settings" "github.com/ProtonMail/proton-bridge/pkg/pmapi" "github.com/ProtonMail/proton-bridge/pkg/pmapi/mocks" "github.com/golang/mock/gomock" @@ -40,7 +42,7 @@ func TestCheck(t *testing.T) { client := mocks.NewMockClient(c) - updater := newTestUpdater(client, "1.1.0") + updater := newTestUpdater(client, "1.1.0", false) versionMap := VersionMap{ "live": VersionInfo{ @@ -65,13 +67,50 @@ func TestCheck(t *testing.T) { assert.NoError(t, err) } +func TestCheckEarlyAccess(t *testing.T) { + c := gomock.NewController(t) + defer c.Finish() + + client := mocks.NewMockClient(c) + + updater := newTestUpdater(client, "1.1.0", true) + + versionMap := VersionMap{ + "live": VersionInfo{ + Version: semver.MustParse("1.5.0"), + MinAuto: semver.MustParse("1.0.0"), + Package: "https://protonmail.com/download/bridge/update_1.5.0_linux.tgz", + Rollout: 1.0, + }, + "beta": VersionInfo{ + Version: semver.MustParse("1.6.0"), + MinAuto: semver.MustParse("1.0.0"), + Package: "https://protonmail.com/download/bridge/update_1.6.0_linux.tgz", + Rollout: 1.0, + }, + } + + client.EXPECT().DownloadAndVerify( + updater.getVersionFileURL(), + updater.getVersionFileURL()+".sig", + gomock.Any(), + ).Return(bytes.NewReader(mustMarshal(t, versionMap)), nil) + + client.EXPECT().Logout() + + version, err := updater.Check() + + assert.Equal(t, semver.MustParse("1.6.0"), version.Version) + assert.NoError(t, err) +} + func TestCheckBadSignature(t *testing.T) { c := gomock.NewController(t) defer c.Finish() client := mocks.NewMockClient(c) - updater := newTestUpdater(client, "1.2.0") + updater := newTestUpdater(client, "1.2.0", false) client.EXPECT().DownloadAndVerify( updater.getVersionFileURL(), @@ -92,7 +131,7 @@ func TestIsUpdateApplicable(t *testing.T) { client := mocks.NewMockClient(c) - updater := newTestUpdater(client, "1.4.0") + updater := newTestUpdater(client, "1.4.0", false) versionOld := VersionInfo{ Version: semver.MustParse("1.3.0"), @@ -128,7 +167,7 @@ func TestCanInstall(t *testing.T) { client := mocks.NewMockClient(c) - updater := newTestUpdater(client, "1.4.0") + updater := newTestUpdater(client, "1.4.0", false) versionManual := VersionInfo{ Version: semver.MustParse("1.5.0"), @@ -155,7 +194,7 @@ func TestInstallUpdate(t *testing.T) { client := mocks.NewMockClient(c) - updater := newTestUpdater(client, "1.4.0") + updater := newTestUpdater(client, "1.4.0", false) latestVersion := VersionInfo{ Version: semver.MustParse("1.5.0"), @@ -183,7 +222,7 @@ func TestInstallUpdateBadSignature(t *testing.T) { client := mocks.NewMockClient(c) - updater := newTestUpdater(client, "1.4.0") + updater := newTestUpdater(client, "1.4.0", false) latestVersion := VersionInfo{ Version: semver.MustParse("1.5.0"), @@ -211,7 +250,7 @@ func TestInstallUpdateAlreadyOngoing(t *testing.T) { client := mocks.NewMockClient(c) - updater := newTestUpdater(client, "1.4.0") + updater := newTestUpdater(client, "1.4.0", false) updater.installer = &fakeInstaller{delay: 2 * time.Second} @@ -249,14 +288,14 @@ func TestInstallUpdateAlreadyOngoing(t *testing.T) { wg.Wait() } -func newTestUpdater(client *mocks.MockClient, curVer string) *Updater { +func newTestUpdater(client *mocks.MockClient, curVer string, earlyAccess bool) *Updater { return New( &fakeClientProvider{client: client}, &fakeInstaller{}, + newFakeSettings(0.5, earlyAccess), nil, semver.MustParse(curVer), "bridge", "linux", - 0.5, ) } @@ -289,3 +328,31 @@ func mustMarshal(t *testing.T, v interface{}) []byte { return b } + +type fakeSettings struct { + *settings.Settings + dir string +} + +// newFakeSettings creates a temporary folder for files. +func newFakeSettings(rollout float64, earlyAccess bool) *fakeSettings { + dir, err := ioutil.TempDir("", "test-settings") + if err != nil { + panic(err) + } + + s := &fakeSettings{ + Settings: settings.New(dir), + dir: dir, + } + + s.SetFloat64(settings.RolloutKey, rollout) + + if earlyAccess { + s.Set(settings.UpdateChannelKey, string(BetaChannel)) + } else { + s.Set(settings.UpdateChannelKey, string(LiveChannel)) + } + + return s +} diff --git a/pkg/pmapi/config_default.go b/pkg/pmapi/config_default.go index 44044b99..b69460b3 100644 --- a/pkg/pmapi/config_default.go +++ b/pkg/pmapi/config_default.go @@ -15,7 +15,7 @@ // You should have received a copy of the GNU General Public License // along with ProtonMail Bridge. If not, see . -// +build !pmapi_qa +// +build !build_qa package pmapi diff --git a/pkg/pmapi/config_qa.go b/pkg/pmapi/config_qa.go index 4dd3c598..a4caff1c 100644 --- a/pkg/pmapi/config_qa.go +++ b/pkg/pmapi/config_qa.go @@ -15,7 +15,7 @@ // You should have received a copy of the GNU General Public License // along with ProtonMail Bridge. If not, see . -// +build pmapi_qa +// +build build_qa package pmapi diff --git a/pkg/sum/sum.go b/pkg/sum/sum.go index d3d3d9e7..8248b282 100644 --- a/pkg/sum/sum.go +++ b/pkg/sum/sum.go @@ -22,14 +22,16 @@ import ( "io" "os" "path/filepath" - "strings" ) // RecursiveSum computes the sha512 sum of all files in the root directory and descendents. // If a skipFile is provided (e.g. the path of a checksum file relative to rootDir), it (and its signature) is ignored. -func RecursiveSum(rootDir, skipFile string) ([]byte, error) { +func RecursiveSum(rootDir, skipFileName string) ([]byte, error) { hash := sha512.New() + skipFile := filepath.Join(rootDir, skipFileName) + skipFileSig := skipFile + ".sig" + if err := filepath.Walk(rootDir, func(path string, info os.FileInfo, err error) error { if err != nil { return err @@ -40,11 +42,16 @@ func RecursiveSum(rootDir, skipFile string) ([]byte, error) { } // The hashfile itself isn't included in the hash. - if path == filepath.Join(rootDir, skipFile) || path == filepath.Join(rootDir, skipFile+".sig") { + if path == skipFile || path == skipFileSig { return nil } - if _, err := hash.Write([]byte(strings.TrimPrefix(path, rootDir))); err != nil { + rel, err := filepath.Rel(rootDir, path) + if err != nil { + return err + } + + if _, err := hash.Write([]byte(rel)); err != nil { return err } @@ -57,7 +64,7 @@ func RecursiveSum(rootDir, skipFile string) ([]byte, error) { return err } - return nil + return f.Close() }); err != nil { return nil, err } diff --git a/utils/hasher/main.go b/utils/hasher/main.go index 96b590e9..46856f49 100644 --- a/utils/hasher/main.go +++ b/utils/hasher/main.go @@ -43,6 +43,12 @@ func createApp() *cli.App { // nolint[funlen] Usage: "The root directory from which to begin recursive hashing", Required: true, }, + &cli.StringFlag{ + Name: "output", + Aliases: []string{"o"}, + Usage: "The file to save the sum in", + Required: true, + }, } return app @@ -54,9 +60,14 @@ func computeSum(c *cli.Context) error { return err } - if _, err := c.App.Writer.Write(b); err != nil { + f, err := os.Create(c.String("output")) + if err != nil { return err } - return nil + if _, err := f.Write(b); err != nil { + return err + } + + return f.Close() }