mirror of
https://github.com/ProtonMail/proton-bridge.git
synced 2026-02-04 08:18:34 +00:00
feat: early access
This commit is contained in:
4
Makefile
4
Makefile
@ -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
3
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-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=
|
||||||
|
|||||||
@ -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{
|
||||||
|
|||||||
@ -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))
|
||||||
|
}
|
||||||
|
|||||||
@ -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)
|
||||||
|
|||||||
@ -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" ) { }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -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()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -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()
|
||||||
|
|
||||||
|
|||||||
@ -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)
|
||||||
|
|||||||
@ -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"
|
|
||||||
@ -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"
|
||||||
|
)
|
||||||
@ -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
|
||||||
|
|
||||||
|
|||||||
@ -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
|
||||||
|
|
||||||
|
|||||||
@ -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
|
||||||
|
|
||||||
|
|||||||
@ -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
|
||||||
|
|
||||||
|
|||||||
@ -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)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -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
|
||||||
|
}
|
||||||
|
|||||||
@ -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
|
||||||
|
|
||||||
|
|||||||
@ -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
|
||||||
|
|
||||||
|
|||||||
@ -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
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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()
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user