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()
}