feat: early access

This commit is contained in:
James Houlahan
2020-12-03 15:36:02 +01:00
parent eccad4bbfd
commit d2066173f0
22 changed files with 235 additions and 72 deletions

View File

@ -81,10 +81,10 @@ build-ie-nogui:
TARGET_CMD=Import-Export $(MAKE) build-nogui TARGET_CMD=Import-Export $(MAKE) build-nogui
build-launcher: 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: 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: versioner:
go build ${BUILD_FLAGS} ${GO_LDFLAGS} -o versioner utils/versioner/main.go go build ${BUILD_FLAGS} ${GO_LDFLAGS} -o versioner utils/versioner/main.go

3
go.sum
View File

@ -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-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-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-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-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 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=

View File

@ -180,11 +180,11 @@ func New( // nolint[funlen]
updater := updater.New( updater := updater.New(
cm, cm,
installer, installer,
settingsObj,
kr, kr,
semver.MustParse(constants.Version), semver.MustParse(constants.Version),
updateURLName, updateURLName,
runtime.GOOS, runtime.GOOS,
settingsObj.GetFloat64(settings.RolloutKey),
) )
return &Base{ return &Base{

View File

@ -20,6 +20,7 @@ package settings
import ( import (
"encoding/json" "encoding/json"
"errors" "errors"
"fmt"
"os" "os"
"strconv" "strconv"
"sync" "sync"
@ -135,3 +136,7 @@ func (p *keyValueStore) SetBool(key string, value bool) {
func (p *keyValueStore) SetInt(key string, value int) { func (p *keyValueStore) SetInt(key string, value int) {
p.Set(key, strconv.Itoa(value)) p.Set(key, strconv.Itoa(value))
} }
func (p *keyValueStore) SetFloat64(key string, value float64) {
p.Set(key, fmt.Sprintf("%v", value))
}

View File

@ -40,6 +40,7 @@ const (
CookiesKey = "cookies" CookiesKey = "cookies"
ReportOutgoingNoEncKey = "report_outgoing_email_without_encryption" ReportOutgoingNoEncKey = "report_outgoing_email_without_encryption"
LastVersionKey = "last_used_version" LastVersionKey = "last_used_version"
UpdateChannelKey = "update_channel"
RolloutKey = "rollout" RolloutKey = "rollout"
) )
@ -75,6 +76,7 @@ func (s *Settings) setDefaultValues() {
s.setDefault(AutoUpdateKey, "true") s.setDefault(AutoUpdateKey, "true")
s.setDefault(ReportOutgoingNoEncKey, "false") s.setDefault(ReportOutgoingNoEncKey, "false")
s.setDefault(LastVersionKey, "") s.setDefault(LastVersionKey, "")
s.setDefault(UpdateChannelKey, "")
s.setDefault(RolloutKey, fmt.Sprintf("%v", rand.Float64())) s.setDefault(RolloutKey, fmt.Sprintf("%v", rand.Float64()))
s.setDefault(APIPortKey, DefaultAPIPort) s.setDefault(APIPortKey, DefaultAPIPort)

View File

@ -137,6 +137,7 @@ Dialog {
spacing: Style.dialog.spacing spacing: Style.dialog.spacing
ButtonRounded { ButtonRounded {
id:buttonNo id:buttonNo
visible: root.state != "toggleEarlyAccess"
color_main: Style.dialog.text color_main: Style.dialog.text
fa_icon: Style.fa.times fa_icon: Style.fa.times
text: qsTr("No") text: qsTr("No")
@ -148,7 +149,7 @@ Dialog {
color_minor: Style.main.textBlue color_minor: Style.main.textBlue
isOpaque: true isOpaque: true
fa_icon: Style.fa.check fa_icon: Style.fa.check
text: qsTr("Yes") text: root.state == "toggleEarlyAccess" ? qsTr("Ok") : qsTr("Yes")
onClicked : { onClicked : {
currentIndex=1 currentIndex=1
root.confirmed() 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 { State {
name: "noKeychain" name: "noKeychain"
PropertyChanges { PropertyChanges {
@ -343,8 +355,6 @@ Dialog {
root.visible = true root.visible = true
} }
onConfirmed : { onConfirmed : {
if (state == "quit" || state == "instance exists" ) { if (state == "quit" || state == "instance exists" ) {
timer.interval = 1000 timer.interval = 1000
@ -358,17 +368,18 @@ Dialog {
Connections { Connections {
target: timer target: timer
onTriggered: { onTriggered: {
if ( state == "addressmode" ) { go.switchAddressMode (input) } if ( state == "addressmode" ) { go.switchAddressMode (input) }
if ( state == "clearChain" ) { go.clearKeychain () } if ( state == "clearChain" ) { go.clearKeychain () }
if ( state == "clearCache" ) { go.clearCache () } if ( state == "clearCache" ) { go.clearCache () }
if ( state == "deleteUser" ) { go.deleteAccount (input, checkBoxWrapper.isChecked) } if ( state == "deleteUser" ) { go.deleteAccount (input, checkBoxWrapper.isChecked) }
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 == "quit" ) { Qt.quit () } if ( state == "toggleEarlyAccess" ) { go.toggleEarlyAccess () }
if ( state == "instance exists" ) { Qt.quit () } if ( state == "quit" ) { Qt.quit () }
if ( state == "noKeychain" ) { Qt.quit () } if ( state == "instance exists" ) { Qt.quit () }
if ( state == "checkUpdates" ) { } if ( state == "noKeychain" ) { Qt.quit () }
if ( state == "checkUpdates" ) { }
} }
} }

View File

@ -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 { ButtonIconText {
id: advancedSettings id: advancedSettings
property bool isAdvanced : !go.isDefaultPort property bool isAdvanced : !go.isDefaultPort
@ -196,7 +220,6 @@ Item {
dialogGlobal.show() dialogGlobal.show()
} }
} }
} }
} }
} }

View File

@ -267,6 +267,7 @@ Window {
property bool isAutoStart : true property bool isAutoStart : true
property bool isAutoUpdate : false property bool isAutoUpdate : false
property bool isEarlyAccess : false
property bool isProxyAllowed : false property bool isProxyAllowed : false
property bool isFirstStart : false property bool isFirstStart : false
property bool isFreshVersion : false property bool isFreshVersion : false
@ -336,6 +337,7 @@ Window {
signal processFinished() signal processFinished()
signal toggleAutoStart() signal toggleAutoStart()
signal toggleEarlyAccess()
signal toggleAutoUpdate() signal toggleAutoUpdate()
signal notifyBubble(int tabIndex, string message) signal notifyBubble(int tabIndex, string message)
signal silentBubble(int tabIndex, string message) signal silentBubble(int tabIndex, string message)
@ -627,6 +629,12 @@ Window {
isAutoUpdate = (isAutoUpdate!=false) ? false : true isAutoUpdate = (isAutoUpdate!=false) ? false : true
console.log (" Test: onToggleAutoUpdate "+isAutoUpdate) console.log (" Test: onToggleAutoUpdate "+isAutoUpdate)
} }
onToggleEarlyAccess: {
workAndClose()
isEarlyAccess = (isEarlyAccess!=false) ? false : true
console.log (" Test: onToggleEarlyAccess "+isEarlyAccess)
}
} }
} }

View File

@ -370,6 +370,12 @@ func (s *FrontendQt) qtExecute(Procedure func(*FrontendQt) error) error {
s.Qml.SetIsProxyAllowed(false) 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.TLSCertIssue)
s.eventListener.RetryEmit(events.ErrorEvent) 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() { func (s *FrontendQt) toggleAllowProxy() {
defer s.Qml.ProcessFinished() defer s.Qml.ProcessFinished()

View File

@ -35,6 +35,7 @@ type GoQMLInterface struct {
_ bool `property:"isAutoStart"` _ bool `property:"isAutoStart"`
_ bool `property:"isAutoUpdate"` _ bool `property:"isAutoUpdate"`
_ bool `property:"isEarlyAccess"`
_ bool `property:"isProxyAllowed"` _ bool `property:"isProxyAllowed"`
_ string `property:"currentAddress"` _ string `property:"currentAddress"`
_ string `property:"goos"` _ string `property:"goos"`
@ -94,6 +95,7 @@ type GoQMLInterface struct {
_ func() `slot:"toggleAutoStart"` _ func() `slot:"toggleAutoStart"`
_ func() `slot:"toggleAutoUpdate"` _ func() `slot:"toggleAutoUpdate"`
_ func() `slot:"toggleEarlyAccess"`
_ func() `slot:"toggleAllowProxy"` _ func() `slot:"toggleAllowProxy"`
_ func() `slot:"loadAccounts"` _ func() `slot:"loadAccounts"`
_ func() `slot:"openLogs"` _ func() `slot:"openLogs"`
@ -157,6 +159,7 @@ func (s *GoQMLInterface) init() {}
// SetFrontend connects all slots and signals from Go to QML. // SetFrontend connects all slots and signals from Go to QML.
func (s *GoQMLInterface) SetFrontend(f *FrontendQt) { func (s *GoQMLInterface) SetFrontend(f *FrontendQt) {
s.ConnectToggleAutoStart(f.toggleAutoStart) s.ConnectToggleAutoStart(f.toggleAutoStart)
s.ConnectToggleEarlyAccess(f.toggleEarlyAccess)
s.ConnectToggleAutoUpdate(f.toggleAutoUpdate) s.ConnectToggleAutoUpdate(f.toggleAutoUpdate)
s.ConnectToggleAllowProxy(f.toggleAllowProxy) s.ConnectToggleAllowProxy(f.toggleAllowProxy)
s.ConnectLoadAccounts(f.loadAccounts) s.ConnectLoadAccounts(f.loadAccounts)

View File

@ -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 <https://www.gnu.org/licenses/>.
// +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"

View File

@ -15,8 +15,15 @@
// 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/>.
// +build beta
package updater 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"
)

View File

@ -15,7 +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/>.
// +build !pmapi_qa // +build !build_qa
package updater package updater

View File

@ -15,7 +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/>.
// +build pmapi_qa // +build build_qa
package updater package updater

View File

@ -15,7 +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/>.
// +build !pmapi_qa // +build !build_qa
package updater package updater

View File

@ -15,7 +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/>.
// +build pmapi_qa // +build build_qa
package updater package updater

View File

@ -23,6 +23,7 @@ import (
"github.com/Masterminds/semver/v3" "github.com/Masterminds/semver/v3"
"github.com/ProtonMail/gopenpgp/v2/crypto" "github.com/ProtonMail/gopenpgp/v2/crypto"
"github.com/ProtonMail/proton-bridge/internal/config/settings"
"github.com/ProtonMail/proton-bridge/pkg/pmapi" "github.com/ProtonMail/proton-bridge/pkg/pmapi"
"github.com/pkg/errors" "github.com/pkg/errors"
"github.com/sirupsen/logrus" "github.com/sirupsen/logrus"
@ -38,15 +39,21 @@ type Installer interface {
InstallUpdate(*semver.Version, io.Reader) error InstallUpdate(*semver.Version, io.Reader) error
} }
type Settings interface {
Get(string) string
Set(string, string)
GetFloat64(string) float64
}
type Updater struct { type Updater struct {
cm ClientProvider cm ClientProvider
installer Installer installer Installer
settings Settings
kr *crypto.KeyRing kr *crypto.KeyRing
curVer *semver.Version curVer *semver.Version
updateURLName string updateURLName string
platform string platform string
rollout float64
locker *locker locker *locker
} }
@ -54,19 +61,25 @@ type Updater struct {
func New( func New(
cm ClientProvider, cm ClientProvider,
installer Installer, installer Installer,
s Settings,
kr *crypto.KeyRing, kr *crypto.KeyRing,
curVer *semver.Version, curVer *semver.Version,
updateURLName, platform string, updateURLName, platform string,
rollout float64,
) *Updater { ) *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{ return &Updater{
cm: cm, cm: cm,
installer: installer, installer: installer,
settings: s,
kr: kr, kr: kr,
curVer: curVer, curVer: curVer,
updateURLName: updateURLName, updateURLName: updateURLName,
platform: platform, platform: platform,
rollout: rollout,
locker: newLocker(), locker: newLocker(),
} }
} }
@ -92,7 +105,12 @@ func (u *Updater) Check() (VersionInfo, error) {
return VersionInfo{}, err 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 { func (u *Updater) IsUpdateApplicable(version VersionInfo) bool {
@ -100,7 +118,7 @@ func (u *Updater) IsUpdateApplicable(version VersionInfo) bool {
return false return false
} }
if u.rollout > version.Rollout { if u.settings.GetFloat64(settings.RolloutKey) > version.Rollout {
return false return false
} }
@ -108,6 +126,10 @@ func (u *Updater) IsUpdateApplicable(version VersionInfo) bool {
} }
func (u *Updater) CanInstall(version VersionInfo) bool { func (u *Updater) CanInstall(version VersionInfo) bool {
if version.MinAuto == nil {
return true
}
return !u.curVer.LessThan(version.MinAuto) return !u.curVer.LessThan(version.MinAuto)
} }

View File

@ -22,11 +22,13 @@ import (
"encoding/json" "encoding/json"
"errors" "errors"
"io" "io"
"io/ioutil"
"sync" "sync"
"testing" "testing"
"time" "time"
"github.com/Masterminds/semver/v3" "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"
"github.com/ProtonMail/proton-bridge/pkg/pmapi/mocks" "github.com/ProtonMail/proton-bridge/pkg/pmapi/mocks"
"github.com/golang/mock/gomock" "github.com/golang/mock/gomock"
@ -40,7 +42,7 @@ func TestCheck(t *testing.T) {
client := mocks.NewMockClient(c) client := mocks.NewMockClient(c)
updater := newTestUpdater(client, "1.1.0") updater := newTestUpdater(client, "1.1.0", false)
versionMap := VersionMap{ versionMap := VersionMap{
"live": VersionInfo{ "live": VersionInfo{
@ -65,13 +67,50 @@ func TestCheck(t *testing.T) {
assert.NoError(t, err) 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) { func TestCheckBadSignature(t *testing.T) {
c := gomock.NewController(t) c := gomock.NewController(t)
defer c.Finish() defer c.Finish()
client := mocks.NewMockClient(c) client := mocks.NewMockClient(c)
updater := newTestUpdater(client, "1.2.0") updater := newTestUpdater(client, "1.2.0", false)
client.EXPECT().DownloadAndVerify( client.EXPECT().DownloadAndVerify(
updater.getVersionFileURL(), updater.getVersionFileURL(),
@ -92,7 +131,7 @@ func TestIsUpdateApplicable(t *testing.T) {
client := mocks.NewMockClient(c) client := mocks.NewMockClient(c)
updater := newTestUpdater(client, "1.4.0") updater := newTestUpdater(client, "1.4.0", false)
versionOld := VersionInfo{ versionOld := VersionInfo{
Version: semver.MustParse("1.3.0"), Version: semver.MustParse("1.3.0"),
@ -128,7 +167,7 @@ func TestCanInstall(t *testing.T) {
client := mocks.NewMockClient(c) client := mocks.NewMockClient(c)
updater := newTestUpdater(client, "1.4.0") updater := newTestUpdater(client, "1.4.0", false)
versionManual := VersionInfo{ versionManual := VersionInfo{
Version: semver.MustParse("1.5.0"), Version: semver.MustParse("1.5.0"),
@ -155,7 +194,7 @@ func TestInstallUpdate(t *testing.T) {
client := mocks.NewMockClient(c) client := mocks.NewMockClient(c)
updater := newTestUpdater(client, "1.4.0") updater := newTestUpdater(client, "1.4.0", false)
latestVersion := VersionInfo{ latestVersion := VersionInfo{
Version: semver.MustParse("1.5.0"), Version: semver.MustParse("1.5.0"),
@ -183,7 +222,7 @@ func TestInstallUpdateBadSignature(t *testing.T) {
client := mocks.NewMockClient(c) client := mocks.NewMockClient(c)
updater := newTestUpdater(client, "1.4.0") updater := newTestUpdater(client, "1.4.0", false)
latestVersion := VersionInfo{ latestVersion := VersionInfo{
Version: semver.MustParse("1.5.0"), Version: semver.MustParse("1.5.0"),
@ -211,7 +250,7 @@ func TestInstallUpdateAlreadyOngoing(t *testing.T) {
client := mocks.NewMockClient(c) client := mocks.NewMockClient(c)
updater := newTestUpdater(client, "1.4.0") updater := newTestUpdater(client, "1.4.0", false)
updater.installer = &fakeInstaller{delay: 2 * time.Second} updater.installer = &fakeInstaller{delay: 2 * time.Second}
@ -249,14 +288,14 @@ func TestInstallUpdateAlreadyOngoing(t *testing.T) {
wg.Wait() wg.Wait()
} }
func newTestUpdater(client *mocks.MockClient, curVer string) *Updater { func newTestUpdater(client *mocks.MockClient, curVer string, earlyAccess bool) *Updater {
return New( return New(
&fakeClientProvider{client: client}, &fakeClientProvider{client: client},
&fakeInstaller{}, &fakeInstaller{},
newFakeSettings(0.5, earlyAccess),
nil, nil,
semver.MustParse(curVer), semver.MustParse(curVer),
"bridge", "linux", "bridge", "linux",
0.5,
) )
} }
@ -289,3 +328,31 @@ func mustMarshal(t *testing.T, v interface{}) []byte {
return b 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
}

View File

@ -15,7 +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/>.
// +build !pmapi_qa // +build !build_qa
package pmapi package pmapi

View File

@ -15,7 +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/>.
// +build pmapi_qa // +build build_qa
package pmapi package pmapi

View File

@ -22,14 +22,16 @@ import (
"io" "io"
"os" "os"
"path/filepath" "path/filepath"
"strings"
) )
// RecursiveSum computes the sha512 sum of all files in the root directory and descendents. // 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. // 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() 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 := filepath.Walk(rootDir, func(path string, info os.FileInfo, err error) error {
if err != nil { if err != nil {
return err return err
@ -40,11 +42,16 @@ func RecursiveSum(rootDir, skipFile string) ([]byte, error) {
} }
// The hashfile itself isn't included in the hash. // 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 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 return err
} }
@ -57,7 +64,7 @@ func RecursiveSum(rootDir, skipFile string) ([]byte, error) {
return err return err
} }
return nil return f.Close()
}); err != nil { }); err != nil {
return nil, err return nil, err
} }

View File

@ -43,6 +43,12 @@ func createApp() *cli.App { // nolint[funlen]
Usage: "The root directory from which to begin recursive hashing", Usage: "The root directory from which to begin recursive hashing",
Required: true, Required: true,
}, },
&cli.StringFlag{
Name: "output",
Aliases: []string{"o"},
Usage: "The file to save the sum in",
Required: true,
},
} }
return app return app
@ -54,9 +60,14 @@ func computeSum(c *cli.Context) error {
return err return err
} }
if _, err := c.App.Writer.Write(b); err != nil { f, err := os.Create(c.String("output"))
if err != nil {
return err return err
} }
return nil if _, err := f.Write(b); err != nil {
return err
}
return f.Close()
} }