forked from Silverfish/proton-bridge
GODT-1978: Auto-updates
This commit is contained in:
@ -74,6 +74,7 @@ type Bridge struct {
|
|||||||
// updater is the bridge's updater.
|
// updater is the bridge's updater.
|
||||||
updater Updater
|
updater Updater
|
||||||
curVersion *semver.Version
|
curVersion *semver.Version
|
||||||
|
installCh chan installJob
|
||||||
|
|
||||||
// focusService is used to raise the bridge window when needed.
|
// focusService is used to raise the bridge window when needed.
|
||||||
focusService *focus.Service
|
focusService *focus.Service
|
||||||
@ -241,6 +242,7 @@ func newBridge(
|
|||||||
|
|
||||||
updater: updater,
|
updater: updater,
|
||||||
curVersion: curVersion,
|
curVersion: curVersion,
|
||||||
|
installCh: make(chan installJob, 1),
|
||||||
|
|
||||||
focusService: focusService,
|
focusService: focusService,
|
||||||
autostarter: autostarter,
|
autostarter: autostarter,
|
||||||
@ -340,18 +342,25 @@ func (bridge *Bridge) init(tlsReporter TLSReporter) error {
|
|||||||
defer bridge.goLoad()
|
defer bridge.goLoad()
|
||||||
|
|
||||||
// Check for updates when triggered.
|
// Check for updates when triggered.
|
||||||
bridge.goUpdate = bridge.tasks.PeriodicOrTrigger(constants.UpdateCheckInterval, 0, func(context.Context) {
|
bridge.goUpdate = bridge.tasks.PeriodicOrTrigger(constants.UpdateCheckInterval, 0, func(ctx context.Context) {
|
||||||
logrus.Info("Checking for updates")
|
logrus.Info("Checking for updates")
|
||||||
|
|
||||||
version, err := bridge.updater.GetVersionInfo(bridge.api, bridge.vault.GetUpdateChannel())
|
version, err := bridge.updater.GetVersionInfo(ctx, bridge.api, bridge.vault.GetUpdateChannel())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logrus.WithError(err).Error("Failed to get version info")
|
logrus.WithError(err).Error("Failed to check for updates")
|
||||||
} else if err := bridge.handleUpdate(version); err != nil {
|
} else {
|
||||||
logrus.WithError(err).Error("Failed to handle update")
|
bridge.handleUpdate(version)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
defer bridge.goUpdate()
|
defer bridge.goUpdate()
|
||||||
|
|
||||||
|
// Install updates when available.
|
||||||
|
bridge.tasks.Once(func(ctx context.Context) {
|
||||||
|
async.RangeContext(ctx, bridge.installCh, func(job installJob) {
|
||||||
|
bridge.installUpdate(ctx, job)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -237,7 +237,8 @@ func TestBridge_CheckUpdate(t *testing.T) {
|
|||||||
MinAuto: v2_3_0,
|
MinAuto: v2_3_0,
|
||||||
RolloutProportion: 1.0,
|
RolloutProportion: 1.0,
|
||||||
},
|
},
|
||||||
CanInstall: true,
|
Silent: false,
|
||||||
|
Compatible: true,
|
||||||
}, <-updateCh)
|
}, <-updateCh)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
@ -259,13 +260,14 @@ func TestBridge_AutoUpdate(t *testing.T) {
|
|||||||
// Check for updates.
|
// Check for updates.
|
||||||
bridge.CheckForUpdates()
|
bridge.CheckForUpdates()
|
||||||
|
|
||||||
// We should receive an event indicating that the update was installed.
|
// We should receive an event indicating that the update was silently installed.
|
||||||
require.Equal(t, events.UpdateInstalled{
|
require.Equal(t, events.UpdateInstalled{
|
||||||
Version: updater.VersionInfo{
|
Version: updater.VersionInfo{
|
||||||
Version: v2_4_0,
|
Version: v2_4_0,
|
||||||
MinAuto: v2_3_0,
|
MinAuto: v2_3_0,
|
||||||
RolloutProportion: 1.0,
|
RolloutProportion: 1.0,
|
||||||
},
|
},
|
||||||
|
Silent: true,
|
||||||
}, <-updateCh)
|
}, <-updateCh)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
@ -294,7 +296,8 @@ func TestBridge_ManualUpdate(t *testing.T) {
|
|||||||
MinAuto: v2_4_0,
|
MinAuto: v2_4_0,
|
||||||
RolloutProportion: 1.0,
|
RolloutProportion: 1.0,
|
||||||
},
|
},
|
||||||
CanInstall: false,
|
Silent: false,
|
||||||
|
Compatible: false,
|
||||||
}, <-updateCh)
|
}, <-updateCh)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|||||||
@ -1,6 +1,7 @@
|
|||||||
package bridge
|
package bridge
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/url"
|
"net/url"
|
||||||
"os"
|
"os"
|
||||||
@ -129,13 +130,13 @@ func (testUpdater *TestUpdater) SetLatestVersion(version, minAuto *semver.Versio
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (testUpdater *TestUpdater) GetVersionInfo(downloader updater.Downloader, channel updater.Channel) (updater.VersionInfo, error) {
|
func (testUpdater *TestUpdater) GetVersionInfo(ctx context.Context, downloader updater.Downloader, channel updater.Channel) (updater.VersionInfo, error) {
|
||||||
testUpdater.lock.RLock()
|
testUpdater.lock.RLock()
|
||||||
defer testUpdater.lock.RUnlock()
|
defer testUpdater.lock.RUnlock()
|
||||||
|
|
||||||
return testUpdater.latest, nil
|
return testUpdater.latest, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (testUpdater *TestUpdater) InstallUpdate(downloader updater.Downloader, update updater.VersionInfo) error {
|
func (testUpdater *TestUpdater) InstallUpdate(ctx context.Context, downloader updater.Downloader, update updater.VersionInfo) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|||||||
@ -18,6 +18,8 @@
|
|||||||
package bridge
|
package bridge
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
|
|
||||||
"github.com/ProtonMail/proton-bridge/v2/internal/updater"
|
"github.com/ProtonMail/proton-bridge/v2/internal/updater"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -51,6 +53,6 @@ type Autostarter interface {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type Updater interface {
|
type Updater interface {
|
||||||
GetVersionInfo(downloader updater.Downloader, channel updater.Channel) (updater.VersionInfo, error)
|
GetVersionInfo(context.Context, updater.Downloader, updater.Channel) (updater.VersionInfo, error)
|
||||||
InstallUpdate(downloader updater.Downloader, update updater.VersionInfo) error
|
InstallUpdate(context.Context, updater.Downloader, updater.VersionInfo) error
|
||||||
}
|
}
|
||||||
|
|||||||
@ -18,43 +18,123 @@
|
|||||||
package bridge
|
package bridge
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
|
|
||||||
"github.com/ProtonMail/proton-bridge/v2/internal/events"
|
"github.com/ProtonMail/proton-bridge/v2/internal/events"
|
||||||
"github.com/ProtonMail/proton-bridge/v2/internal/updater"
|
"github.com/ProtonMail/proton-bridge/v2/internal/updater"
|
||||||
|
"github.com/sirupsen/logrus"
|
||||||
)
|
)
|
||||||
|
|
||||||
func (bridge *Bridge) CheckForUpdates() {
|
func (bridge *Bridge) CheckForUpdates() {
|
||||||
bridge.goUpdate()
|
bridge.goUpdate()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (bridge *Bridge) handleUpdate(version updater.VersionInfo) error {
|
func (bridge *Bridge) InstallUpdate(version updater.VersionInfo) {
|
||||||
|
log := logrus.WithFields(logrus.Fields{
|
||||||
|
"version": version.Version,
|
||||||
|
"current": bridge.curVersion,
|
||||||
|
"channel": bridge.vault.GetUpdateChannel(),
|
||||||
|
})
|
||||||
|
|
||||||
|
select {
|
||||||
|
case bridge.installCh <- installJob{version: version, silent: false}:
|
||||||
|
log.Info("The update will be installed manually")
|
||||||
|
|
||||||
|
default:
|
||||||
|
log.Info("An update is already being installed")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (bridge *Bridge) handleUpdate(version updater.VersionInfo) {
|
||||||
|
log := logrus.WithFields(logrus.Fields{
|
||||||
|
"version": version.Version,
|
||||||
|
"current": bridge.curVersion,
|
||||||
|
"channel": bridge.vault.GetUpdateChannel(),
|
||||||
|
})
|
||||||
|
|
||||||
|
bridge.publish(events.UpdateLatest{
|
||||||
|
Version: version,
|
||||||
|
})
|
||||||
|
|
||||||
switch {
|
switch {
|
||||||
case !version.Version.GreaterThan(bridge.curVersion):
|
case !version.Version.GreaterThan(bridge.curVersion):
|
||||||
|
log.Debug("No update available")
|
||||||
|
|
||||||
bridge.publish(events.UpdateNotAvailable{})
|
bridge.publish(events.UpdateNotAvailable{})
|
||||||
|
|
||||||
case version.RolloutProportion < bridge.vault.GetUpdateRollout():
|
case version.RolloutProportion < bridge.vault.GetUpdateRollout():
|
||||||
|
log.Info("An update is available but has not been rolled out yet")
|
||||||
|
|
||||||
bridge.publish(events.UpdateNotAvailable{})
|
bridge.publish(events.UpdateNotAvailable{})
|
||||||
|
|
||||||
case bridge.curVersion.LessThan(version.MinAuto):
|
case bridge.curVersion.LessThan(version.MinAuto):
|
||||||
|
log.Info("An update is available but is incompatible with this version")
|
||||||
|
|
||||||
bridge.publish(events.UpdateAvailable{
|
bridge.publish(events.UpdateAvailable{
|
||||||
Version: version,
|
Version: version,
|
||||||
CanInstall: false,
|
Compatible: false,
|
||||||
|
Silent: false,
|
||||||
})
|
})
|
||||||
|
|
||||||
case !bridge.vault.GetAutoUpdate():
|
case !bridge.vault.GetAutoUpdate():
|
||||||
|
log.Info("An update is available but auto-update is disabled")
|
||||||
|
|
||||||
bridge.publish(events.UpdateAvailable{
|
bridge.publish(events.UpdateAvailable{
|
||||||
Version: version,
|
Version: version,
|
||||||
CanInstall: true,
|
Compatible: true,
|
||||||
|
Silent: false,
|
||||||
})
|
})
|
||||||
|
|
||||||
default:
|
default:
|
||||||
if err := bridge.updater.InstallUpdate(bridge.api, version); err != nil {
|
log.Info("An update is available")
|
||||||
return err
|
|
||||||
|
bridge.publish(events.UpdateAvailable{
|
||||||
|
Version: version,
|
||||||
|
Compatible: true,
|
||||||
|
Silent: true,
|
||||||
|
})
|
||||||
|
|
||||||
|
select {
|
||||||
|
case bridge.installCh <- installJob{version: version, silent: true}:
|
||||||
|
log.Info("The update will be installed silently")
|
||||||
|
|
||||||
|
default:
|
||||||
|
log.Info("An update is already being installed")
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type installJob struct {
|
||||||
|
version updater.VersionInfo
|
||||||
|
silent bool
|
||||||
|
}
|
||||||
|
|
||||||
|
func (bridge *Bridge) installUpdate(ctx context.Context, job installJob) {
|
||||||
|
log := logrus.WithFields(logrus.Fields{
|
||||||
|
"version": job.version.Version,
|
||||||
|
"current": bridge.curVersion,
|
||||||
|
"channel": bridge.vault.GetUpdateChannel(),
|
||||||
|
})
|
||||||
|
|
||||||
|
bridge.publish(events.UpdateInstalling{
|
||||||
|
Version: job.version,
|
||||||
|
Silent: job.silent,
|
||||||
|
})
|
||||||
|
|
||||||
|
if err := bridge.updater.InstallUpdate(ctx, bridge.api, job.version); err != nil {
|
||||||
|
log.Error("The update could not be installed")
|
||||||
|
|
||||||
|
bridge.publish(events.UpdateFailed{
|
||||||
|
Version: job.version,
|
||||||
|
Silent: job.silent,
|
||||||
|
Error: err,
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
log.Info("The update was installed successfully")
|
||||||
|
|
||||||
bridge.publish(events.UpdateInstalled{
|
bridge.publish(events.UpdateInstalled{
|
||||||
Version: version,
|
Version: job.version,
|
||||||
|
Silent: job.silent,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -17,7 +17,11 @@
|
|||||||
|
|
||||||
package events
|
package events
|
||||||
|
|
||||||
|
import "fmt"
|
||||||
|
|
||||||
type Event interface {
|
type Event interface {
|
||||||
|
fmt.Stringer
|
||||||
|
|
||||||
_isEvent()
|
_isEvent()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -23,18 +23,37 @@ import (
|
|||||||
"github.com/ProtonMail/proton-bridge/v2/internal/updater"
|
"github.com/ProtonMail/proton-bridge/v2/internal/updater"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// UpdateLatest is published when the latest version of bridge is known.
|
||||||
|
type UpdateLatest struct {
|
||||||
|
eventBase
|
||||||
|
|
||||||
|
Version updater.VersionInfo
|
||||||
|
}
|
||||||
|
|
||||||
|
func (event UpdateLatest) String() string {
|
||||||
|
return fmt.Sprintf("UpdateLatest: Version: %s", event.Version.Version)
|
||||||
|
}
|
||||||
|
|
||||||
|
// UpdateAvailable is published when an update is available.
|
||||||
|
// If the update is compatible (can be installed automatically), Compatible is true.
|
||||||
|
// If the update will be installed silently (without user interaction), Silent is true.
|
||||||
type UpdateAvailable struct {
|
type UpdateAvailable struct {
|
||||||
eventBase
|
eventBase
|
||||||
|
|
||||||
Version updater.VersionInfo
|
Version updater.VersionInfo
|
||||||
|
|
||||||
CanInstall bool
|
// Compatible is true if the update can be installed automatically.
|
||||||
|
Compatible bool
|
||||||
|
|
||||||
|
// Silent is true if the update will be installed silently.
|
||||||
|
Silent bool
|
||||||
}
|
}
|
||||||
|
|
||||||
func (event UpdateAvailable) String() string {
|
func (event UpdateAvailable) String() string {
|
||||||
return fmt.Sprintf("UpdateAvailable: Version %s, CanInstall %t", event.Version.Version, event.CanInstall)
|
return fmt.Sprintf("UpdateAvailable: Version %s, Compatible: %t, Silent: %t", event.Version.Version, event.Compatible, event.Silent)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// UpdateNotAvailable is published when no update is available.
|
||||||
type UpdateNotAvailable struct {
|
type UpdateNotAvailable struct {
|
||||||
eventBase
|
eventBase
|
||||||
}
|
}
|
||||||
@ -43,20 +62,54 @@ func (event UpdateNotAvailable) String() string {
|
|||||||
return "UpdateNotAvailable"
|
return "UpdateNotAvailable"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// UpdateInstalling is published when bridge begins installing an update.
|
||||||
|
type UpdateInstalling struct {
|
||||||
|
eventBase
|
||||||
|
|
||||||
|
Version updater.VersionInfo
|
||||||
|
|
||||||
|
Silent bool
|
||||||
|
}
|
||||||
|
|
||||||
|
func (event UpdateInstalling) String() string {
|
||||||
|
return fmt.Sprintf("UpdateInstalling: Version %s, Silent: %t", event.Version.Version, event.Silent)
|
||||||
|
}
|
||||||
|
|
||||||
|
// UpdateInstalled is published when an update has been installed.
|
||||||
type UpdateInstalled struct {
|
type UpdateInstalled struct {
|
||||||
eventBase
|
eventBase
|
||||||
|
|
||||||
Version updater.VersionInfo
|
Version updater.VersionInfo
|
||||||
|
|
||||||
|
Silent bool
|
||||||
|
}
|
||||||
|
|
||||||
|
func (event UpdateInstalled) String() string {
|
||||||
|
return fmt.Sprintf("UpdateInstalled: Version %s, Silent: %t", event.Version.Version, event.Silent)
|
||||||
|
}
|
||||||
|
|
||||||
|
// UpdateFailed is published when an update fails to be installed.
|
||||||
|
type UpdateFailed struct {
|
||||||
|
eventBase
|
||||||
|
|
||||||
|
Version updater.VersionInfo
|
||||||
|
|
||||||
|
Silent bool
|
||||||
|
|
||||||
|
Error error
|
||||||
|
}
|
||||||
|
|
||||||
|
func (event UpdateFailed) String() string {
|
||||||
|
return fmt.Sprintf("UpdateFailed: Version %s, Silent: %t, Error: %s", event.Version.Version, event.Silent, event.Error)
|
||||||
|
}
|
||||||
|
|
||||||
|
// UpdateForced is published when the bridge version is too old and must be updated.
|
||||||
|
type UpdateForced struct {
|
||||||
|
eventBase
|
||||||
|
|
||||||
|
Version updater.VersionInfo
|
||||||
}
|
}
|
||||||
|
|
||||||
func (event UpdateInstalled) String() string {
|
|
||||||
return fmt.Sprintf("UpdateInstalled: Version %s", event.Version.Version)
|
|
||||||
}
|
|
||||||
|
|
||||||
type UpdateForced struct {
|
|
||||||
eventBase
|
|
||||||
}
|
|
||||||
|
|
||||||
func (event UpdateForced) String() string {
|
func (event UpdateForced) String() string {
|
||||||
return "UpdateForced"
|
return fmt.Sprintf("UpdateForced: Version %s", event.Version.Version)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -337,7 +337,17 @@ func (f *frontendCLI) watchEvents(eventCh <-chan events.Event) { // nolint:funle
|
|||||||
)
|
)
|
||||||
|
|
||||||
case events.UpdateAvailable:
|
case events.UpdateAvailable:
|
||||||
f.Printf("An update is available (version %v)\n", event.Version.Version)
|
if !event.Compatible {
|
||||||
|
f.Printf("A new version (%v) is available but it cannot be installed automatically.\n", event.Version.Version)
|
||||||
|
} else if !event.Silent {
|
||||||
|
f.Printf("A new version (%v) is available.\n", event.Version.Version)
|
||||||
|
}
|
||||||
|
|
||||||
|
case events.UpdateInstalled:
|
||||||
|
f.Printf("A new version (%v) was installed.\n", event.Version.Version)
|
||||||
|
|
||||||
|
case events.UpdateFailed:
|
||||||
|
f.Printf("A new version (%v) failed to be installed (%v).\n", event.Version.Version, event.Error)
|
||||||
|
|
||||||
case events.UpdateForced:
|
case events.UpdateForced:
|
||||||
f.notifyNeedUpgrade()
|
f.notifyNeedUpgrade()
|
||||||
|
|||||||
@ -18,12 +18,24 @@
|
|||||||
package cli
|
package cli
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"github.com/ProtonMail/proton-bridge/v2/internal/events"
|
||||||
"github.com/ProtonMail/proton-bridge/v2/internal/updater"
|
"github.com/ProtonMail/proton-bridge/v2/internal/updater"
|
||||||
"github.com/abiosoft/ishell"
|
"github.com/abiosoft/ishell"
|
||||||
)
|
)
|
||||||
|
|
||||||
func (f *frontendCLI) checkUpdates(c *ishell.Context) {
|
func (f *frontendCLI) checkUpdates(c *ishell.Context) {
|
||||||
|
updateCh, done := f.bridge.GetEvents(events.UpdateAvailable{}, events.UpdateNotAvailable{})
|
||||||
|
defer done()
|
||||||
|
|
||||||
f.bridge.CheckForUpdates()
|
f.bridge.CheckForUpdates()
|
||||||
|
|
||||||
|
switch (<-updateCh).(type) {
|
||||||
|
case events.UpdateAvailable:
|
||||||
|
// ... this is handled by the main event loop
|
||||||
|
|
||||||
|
case events.UpdateNotAvailable:
|
||||||
|
f.Println("Bridge is already up to date.")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *frontendCLI) enableAutoUpdates(c *ishell.Context) {
|
func (f *frontendCLI) enableAutoUpdates(c *ishell.Context) {
|
||||||
|
|||||||
@ -34,6 +34,7 @@ import (
|
|||||||
"github.com/ProtonMail/proton-bridge/v2/internal/crash"
|
"github.com/ProtonMail/proton-bridge/v2/internal/crash"
|
||||||
"github.com/ProtonMail/proton-bridge/v2/internal/events"
|
"github.com/ProtonMail/proton-bridge/v2/internal/events"
|
||||||
"github.com/ProtonMail/proton-bridge/v2/internal/locations"
|
"github.com/ProtonMail/proton-bridge/v2/internal/locations"
|
||||||
|
"github.com/ProtonMail/proton-bridge/v2/internal/safe"
|
||||||
"github.com/ProtonMail/proton-bridge/v2/internal/updater"
|
"github.com/ProtonMail/proton-bridge/v2/internal/updater"
|
||||||
"github.com/ProtonMail/proton-bridge/v2/pkg/restarter"
|
"github.com/ProtonMail/proton-bridge/v2/pkg/restarter"
|
||||||
"github.com/google/uuid"
|
"github.com/google/uuid"
|
||||||
@ -67,7 +68,12 @@ type Service struct { // nolint:structcheck
|
|||||||
restarter *restarter.Restarter
|
restarter *restarter.Restarter
|
||||||
bridge *bridge.Bridge
|
bridge *bridge.Bridge
|
||||||
eventCh <-chan events.Event
|
eventCh <-chan events.Event
|
||||||
newVersionInfo updater.VersionInfo
|
|
||||||
|
latest updater.VersionInfo
|
||||||
|
latestLock safe.RWMutex
|
||||||
|
|
||||||
|
target updater.VersionInfo
|
||||||
|
targetLock safe.RWMutex
|
||||||
|
|
||||||
authClient *liteapi.Client
|
authClient *liteapi.Client
|
||||||
auth liteapi.Auth
|
auth liteapi.Auth
|
||||||
@ -82,6 +88,8 @@ type Service struct { // nolint:structcheck
|
|||||||
}
|
}
|
||||||
|
|
||||||
// NewService returns a new instance of the service.
|
// NewService returns a new instance of the service.
|
||||||
|
//
|
||||||
|
// nolint:funlen
|
||||||
func NewService(
|
func NewService(
|
||||||
panicHandler *crash.Handler,
|
panicHandler *crash.Handler,
|
||||||
restarter *restarter.Restarter,
|
restarter *restarter.Restarter,
|
||||||
@ -121,6 +129,12 @@ func NewService(
|
|||||||
bridge: bridge,
|
bridge: bridge,
|
||||||
eventCh: eventCh,
|
eventCh: eventCh,
|
||||||
|
|
||||||
|
latest: updater.VersionInfo{},
|
||||||
|
latestLock: safe.NewRWMutex(),
|
||||||
|
|
||||||
|
target: updater.VersionInfo{},
|
||||||
|
targetLock: safe.NewRWMutex(),
|
||||||
|
|
||||||
log: logrus.WithField("pkg", "grpc"),
|
log: logrus.WithField("pkg", "grpc"),
|
||||||
initializing: sync.WaitGroup{},
|
initializing: sync.WaitGroup{},
|
||||||
initializationDone: sync.Once{},
|
initializationDone: sync.Once{},
|
||||||
@ -178,33 +192,12 @@ func (s *Service) Loop() error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Service) NotifyManualUpdate(version updater.VersionInfo, canInstall bool) {
|
|
||||||
if canInstall {
|
|
||||||
_ = s.SendEvent(NewUpdateManualReadyEvent(version.Version.String()))
|
|
||||||
} else {
|
|
||||||
_ = s.SendEvent(NewUpdateErrorEvent(UpdateErrorType_UPDATE_MANUAL_ERROR))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *Service) SetVersion(update updater.VersionInfo) {
|
|
||||||
s.newVersionInfo = update
|
|
||||||
_ = s.SendEvent(NewUpdateVersionChangedEvent())
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *Service) NotifySilentUpdateInstalled() {
|
|
||||||
_ = s.SendEvent(NewUpdateSilentRestartNeededEvent())
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *Service) NotifySilentUpdateError(err error) {
|
|
||||||
s.log.WithError(err).Error("In app update failed, asking for manual.")
|
|
||||||
_ = s.SendEvent(NewUpdateErrorEvent(UpdateErrorType_UPDATE_SILENT_ERROR))
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *Service) WaitUntilFrontendIsReady() {
|
func (s *Service) WaitUntilFrontendIsReady() {
|
||||||
s.initializing.Wait()
|
s.initializing.Wait()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Service) watchEvents() { //nolint:funlen
|
// nolint:funlen,gocyclo
|
||||||
|
func (s *Service) watchEvents() {
|
||||||
// GODT-1949 Better error events.
|
// GODT-1949 Better error events.
|
||||||
for _, err := range s.bridge.GetErrors() {
|
for _, err := range s.bridge.GetErrors() {
|
||||||
switch {
|
switch {
|
||||||
@ -270,11 +263,43 @@ func (s *Service) watchEvents() { //nolint:funlen
|
|||||||
_ = s.SendEvent(NewUserDisconnectedEvent(user.Username))
|
_ = s.SendEvent(NewUserDisconnectedEvent(user.Username))
|
||||||
}
|
}
|
||||||
|
|
||||||
case events.TLSIssue:
|
case events.UpdateLatest:
|
||||||
_ = s.SendEvent(NewMailApiCertIssue())
|
safe.RLock(func() {
|
||||||
|
s.latest = event.Version
|
||||||
|
}, s.latestLock)
|
||||||
|
|
||||||
|
case events.UpdateAvailable:
|
||||||
|
switch {
|
||||||
|
case !event.Compatible:
|
||||||
|
_ = s.SendEvent(NewUpdateErrorEvent(UpdateErrorType_UPDATE_MANUAL_ERROR))
|
||||||
|
|
||||||
|
case !event.Silent:
|
||||||
|
safe.RLock(func() {
|
||||||
|
s.target = event.Version
|
||||||
|
}, s.targetLock)
|
||||||
|
|
||||||
|
_ = s.SendEvent(NewUpdateManualReadyEvent(event.Version.Version.String()))
|
||||||
|
}
|
||||||
|
|
||||||
|
case events.UpdateInstalled:
|
||||||
|
if event.Silent {
|
||||||
|
_ = s.SendEvent(NewUpdateSilentRestartNeededEvent())
|
||||||
|
} else {
|
||||||
|
_ = s.SendEvent(NewUpdateManualRestartNeededEvent())
|
||||||
|
}
|
||||||
|
|
||||||
|
case events.UpdateFailed:
|
||||||
|
if event.Silent {
|
||||||
|
_ = s.SendEvent(NewUpdateErrorEvent(UpdateErrorType_UPDATE_SILENT_ERROR))
|
||||||
|
} else {
|
||||||
|
_ = s.SendEvent(NewUpdateErrorEvent(UpdateErrorType_UPDATE_MANUAL_ERROR))
|
||||||
|
}
|
||||||
|
|
||||||
case events.UpdateForced:
|
case events.UpdateForced:
|
||||||
_ = s.SendEvent(NewUpdateForceEvent(s.newVersionInfo.Version.String()))
|
_ = s.SendEvent(NewUpdateForceEvent(event.Version.Version.String()))
|
||||||
|
|
||||||
|
case events.TLSIssue:
|
||||||
|
_ = s.SendEvent(NewMailApiCertIssue())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -26,7 +26,9 @@ import (
|
|||||||
"github.com/Masterminds/semver/v3"
|
"github.com/Masterminds/semver/v3"
|
||||||
"github.com/ProtonMail/proton-bridge/v2/internal/bridge"
|
"github.com/ProtonMail/proton-bridge/v2/internal/bridge"
|
||||||
"github.com/ProtonMail/proton-bridge/v2/internal/constants"
|
"github.com/ProtonMail/proton-bridge/v2/internal/constants"
|
||||||
|
"github.com/ProtonMail/proton-bridge/v2/internal/events"
|
||||||
"github.com/ProtonMail/proton-bridge/v2/internal/frontend/theme"
|
"github.com/ProtonMail/proton-bridge/v2/internal/frontend/theme"
|
||||||
|
"github.com/ProtonMail/proton-bridge/v2/internal/safe"
|
||||||
"github.com/ProtonMail/proton-bridge/v2/internal/updater"
|
"github.com/ProtonMail/proton-bridge/v2/internal/updater"
|
||||||
"github.com/ProtonMail/proton-bridge/v2/pkg/keychain"
|
"github.com/ProtonMail/proton-bridge/v2/pkg/keychain"
|
||||||
"github.com/ProtonMail/proton-bridge/v2/pkg/ports"
|
"github.com/ProtonMail/proton-bridge/v2/pkg/ports"
|
||||||
@ -257,11 +259,17 @@ func (s *Service) DependencyLicensesLink(_ context.Context, _ *emptypb.Empty) (*
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (s *Service) ReleaseNotesPageLink(ctx context.Context, _ *emptypb.Empty) (*wrapperspb.StringValue, error) {
|
func (s *Service) ReleaseNotesPageLink(ctx context.Context, _ *emptypb.Empty) (*wrapperspb.StringValue, error) {
|
||||||
return wrapperspb.String(s.newVersionInfo.ReleaseNotesPage), nil
|
s.latestLock.RLock()
|
||||||
|
defer s.latestLock.RUnlock()
|
||||||
|
|
||||||
|
return wrapperspb.String(s.latest.ReleaseNotesPage), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Service) LandingPageLink(_ context.Context, _ *emptypb.Empty) (*wrapperspb.StringValue, error) {
|
func (s *Service) LandingPageLink(_ context.Context, _ *emptypb.Empty) (*wrapperspb.StringValue, error) {
|
||||||
return wrapperspb.String(s.newVersionInfo.LandingPage), nil
|
s.latestLock.RLock()
|
||||||
|
defer s.latestLock.RUnlock()
|
||||||
|
|
||||||
|
return wrapperspb.String(s.latest.LandingPage), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Service) SetColorSchemeName(ctx context.Context, name *wrapperspb.StringValue) (*emptypb.Empty, error) {
|
func (s *Service) SetColorSchemeName(ctx context.Context, name *wrapperspb.StringValue) (*emptypb.Empty, error) {
|
||||||
@ -486,15 +494,28 @@ func (s *Service) LoginAbort(ctx context.Context, loginAbort *LoginAbortRequest)
|
|||||||
return &emptypb.Empty{}, nil
|
return &emptypb.Empty{}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
|
||||||
func (s *Service) CheckUpdate(context.Context, *emptypb.Empty) (*emptypb.Empty, error) {
|
func (s *Service) CheckUpdate(context.Context, *emptypb.Empty) (*emptypb.Empty, error) {
|
||||||
s.log.Debug("CheckUpdate")
|
s.log.Debug("CheckUpdate")
|
||||||
|
|
||||||
go func() {
|
go func() {
|
||||||
defer s.panicHandler.HandlePanic()
|
defer s.panicHandler.HandlePanic()
|
||||||
|
|
||||||
s.checkUpdateAndNotify(true)
|
updateCh, done := s.bridge.GetEvents(events.UpdateAvailable{}, events.UpdateNotAvailable{})
|
||||||
|
defer done()
|
||||||
|
|
||||||
|
s.bridge.CheckForUpdates()
|
||||||
|
|
||||||
|
switch (<-updateCh).(type) {
|
||||||
|
case events.UpdateAvailable:
|
||||||
|
// ... this is handled by the main event loop
|
||||||
|
|
||||||
|
case events.UpdateNotAvailable:
|
||||||
|
_ = s.SendEvent(NewUpdateIsLatestVersionEvent())
|
||||||
|
}
|
||||||
|
|
||||||
|
_ = s.SendEvent(NewUpdateCheckFinishedEvent())
|
||||||
}()
|
}()
|
||||||
|
|
||||||
return &emptypb.Empty{}, nil
|
return &emptypb.Empty{}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -504,18 +525,18 @@ func (s *Service) InstallUpdate(ctx context.Context, _ *emptypb.Empty) (*emptypb
|
|||||||
go func() {
|
go func() {
|
||||||
defer s.panicHandler.HandlePanic()
|
defer s.panicHandler.HandlePanic()
|
||||||
|
|
||||||
s.installUpdate()
|
safe.RLock(func() {
|
||||||
|
s.bridge.InstallUpdate(s.latest)
|
||||||
|
}, s.targetLock)
|
||||||
}()
|
}()
|
||||||
|
|
||||||
return &emptypb.Empty{}, nil
|
return &emptypb.Empty{}, nil
|
||||||
}
|
}
|
||||||
*/
|
|
||||||
|
|
||||||
func (s *Service) SetIsAutomaticUpdateOn(ctx context.Context, isOn *wrapperspb.BoolValue) (*emptypb.Empty, error) {
|
func (s *Service) SetIsAutomaticUpdateOn(ctx context.Context, isOn *wrapperspb.BoolValue) (*emptypb.Empty, error) {
|
||||||
s.log.WithField("isOn", isOn.Value).Debug("SetIsAutomaticUpdateOn")
|
s.log.WithField("isOn", isOn.Value).Debug("SetIsAutomaticUpdateOn")
|
||||||
|
|
||||||
currentlyOn := s.bridge.GetAutoUpdate()
|
if currentlyOn := s.bridge.GetAutoUpdate(); currentlyOn == isOn.Value {
|
||||||
if currentlyOn == isOn.Value {
|
|
||||||
return &emptypb.Empty{}, nil
|
return &emptypb.Empty{}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -548,6 +569,7 @@ func (s *Service) SetDiskCachePath(ctx context.Context, newPath *wrapperspb.Stri
|
|||||||
}()
|
}()
|
||||||
|
|
||||||
path := newPath.Value
|
path := newPath.Value
|
||||||
|
|
||||||
//goland:noinspection GoBoolExpressions
|
//goland:noinspection GoBoolExpressions
|
||||||
if (runtime.GOOS == "windows") && (path[0] == '/') {
|
if (runtime.GOOS == "windows") && (path[0] == '/') {
|
||||||
path = path[1:]
|
path = path[1:]
|
||||||
|
|||||||
@ -1,85 +0,0 @@
|
|||||||
// Copyright (c) 2022 Proton AG
|
|
||||||
//
|
|
||||||
// This file is part of Proton Mail Bridge.
|
|
||||||
//
|
|
||||||
// Proton Mail 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.
|
|
||||||
//
|
|
||||||
// Proton Mail 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 Proton Mail Bridge. If not, see <https://www.gnu.org/licenses/>.
|
|
||||||
|
|
||||||
package grpc
|
|
||||||
|
|
||||||
/*
|
|
||||||
func (s *Service) checkUpdate() {
|
|
||||||
version, err := s.updater.Check()
|
|
||||||
if err != nil {
|
|
||||||
s.log.WithError(err).Error("An error occurred while checking for updates")
|
|
||||||
s.SetVersion(updater.VersionInfo{})
|
|
||||||
return
|
|
||||||
}
|
|
||||||
s.SetVersion(version)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *Service) updateForce() {
|
|
||||||
s.updateCheckMutex.Lock()
|
|
||||||
defer s.updateCheckMutex.Unlock()
|
|
||||||
s.checkUpdate()
|
|
||||||
_ = s.SendEvent(NewUpdateForceEvent(s.newVersionInfo.Version.String()))
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *Service) checkUpdateAndNotify(isReqFromUser bool) {
|
|
||||||
s.updateCheckMutex.Lock()
|
|
||||||
defer func() {
|
|
||||||
s.updateCheckMutex.Unlock()
|
|
||||||
_ = s.SendEvent(NewUpdateCheckFinishedEvent())
|
|
||||||
}()
|
|
||||||
|
|
||||||
s.checkUpdate()
|
|
||||||
version := s.newVersionInfo
|
|
||||||
if version.Version.String() == "" {
|
|
||||||
if isReqFromUser {
|
|
||||||
_ = s.SendEvent(NewUpdateErrorEvent(UpdateErrorType_UPDATE_MANUAL_ERROR))
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if !s.updater.IsUpdateApplicable(s.newVersionInfo) {
|
|
||||||
s.log.Info("No need to update")
|
|
||||||
if isReqFromUser {
|
|
||||||
_ = s.SendEvent(NewUpdateIsLatestVersionEvent())
|
|
||||||
}
|
|
||||||
} else if isReqFromUser {
|
|
||||||
s.NotifyManualUpdate(s.newVersionInfo, s.updater.CanInstall(s.newVersionInfo))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *Service) installUpdate() {
|
|
||||||
s.updateCheckMutex.Lock()
|
|
||||||
defer s.updateCheckMutex.Unlock()
|
|
||||||
|
|
||||||
if !s.updater.CanInstall(s.newVersionInfo) {
|
|
||||||
s.log.Warning("Skipping update installation, current version too old")
|
|
||||||
_ = s.SendEvent(NewUpdateErrorEvent(UpdateErrorType_UPDATE_MANUAL_ERROR))
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := s.updater.InstallUpdate(s.newVersionInfo); err != nil {
|
|
||||||
if errors.Cause(err) == updater.ErrDownloadVerify {
|
|
||||||
s.log.WithError(err).Warning("Skipping update installation due to temporary error")
|
|
||||||
} else {
|
|
||||||
s.log.WithError(err).Error("The update couldn't be installed")
|
|
||||||
_ = s.SendEvent(NewUpdateErrorEvent(UpdateErrorType_UPDATE_MANUAL_ERROR))
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
_ = s.SendEvent(NewUpdateSilentRestartNeededEvent())
|
|
||||||
}
|
|
||||||
*/
|
|
||||||
@ -177,16 +177,6 @@ func (l *Locations) ProvideUpdatesPath() (string, error) {
|
|||||||
return l.getUpdatesPath(), nil
|
return l.getUpdatesPath(), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetUpdatesPath returns a new location for update files used for migration scripts only.
|
|
||||||
func (l *Locations) GetUpdatesPath() string {
|
|
||||||
return l.getUpdatesPath()
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetOldUpdatesPath returns a former location for update files used for migration scripts only.
|
|
||||||
func (l *Locations) GetOldUpdatesPath() string {
|
|
||||||
return filepath.Join(l.userCache, "updates")
|
|
||||||
}
|
|
||||||
|
|
||||||
func (l *Locations) getGluonPath() string {
|
func (l *Locations) getGluonPath() string {
|
||||||
return filepath.Join(l.userData, "gluon")
|
return filepath.Join(l.userData, "gluon")
|
||||||
}
|
}
|
||||||
@ -208,17 +198,7 @@ func (l *Locations) getGoIMAPCachePath() string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (l *Locations) getUpdatesPath() string {
|
func (l *Locations) getUpdatesPath() string {
|
||||||
// In order to properly update Bridge 1.6.X and higher we need to
|
return filepath.Join(l.userData, "updates")
|
||||||
// change the launcher first. Since this is not part of automatic
|
|
||||||
// updates the migration must wait until manual update. Until that
|
|
||||||
// we need to keep old path.
|
|
||||||
if l.configName == "bridge" {
|
|
||||||
return l.GetOldUpdatesPath()
|
|
||||||
}
|
|
||||||
|
|
||||||
// Users might use tools to regularly clear caches, which would mean always
|
|
||||||
// removing updates, therefore Bridge updates have to be somewhere else.
|
|
||||||
return filepath.Join(l.userConfig, "updates")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Clear removes everything except the lock and update files.
|
// Clear removes everything except the lock and update files.
|
||||||
|
|||||||
@ -47,7 +47,6 @@ func TestClearRemovesEverythingExceptLockAndUpdateFiles(t *testing.T) {
|
|||||||
|
|
||||||
assert.NoError(t, l.Clear())
|
assert.NoError(t, l.Clear())
|
||||||
|
|
||||||
assert.DirExists(t, l.getSettingsPath())
|
|
||||||
assert.NoFileExists(t, filepath.Join(l.getSettingsPath(), "prefs.json"))
|
assert.NoFileExists(t, filepath.Join(l.getSettingsPath(), "prefs.json"))
|
||||||
assert.NoDirExists(t, l.getLogsPath())
|
assert.NoDirExists(t, l.getLogsPath())
|
||||||
assert.DirExists(t, l.getUpdatesPath())
|
assert.DirExists(t, l.getUpdatesPath())
|
||||||
|
|||||||
@ -58,9 +58,9 @@ func NewUpdater(installer Installer, verifier *crypto.KeyRing, product, platform
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (u *Updater) GetVersionInfo(downloader Downloader, channel Channel) (VersionInfo, error) {
|
func (u *Updater) GetVersionInfo(ctx context.Context, downloader Downloader, channel Channel) (VersionInfo, error) {
|
||||||
b, err := downloader.DownloadAndVerify(
|
b, err := downloader.DownloadAndVerify(
|
||||||
context.Background(),
|
ctx,
|
||||||
u.verifier,
|
u.verifier,
|
||||||
u.getVersionFileURL(),
|
u.getVersionFileURL(),
|
||||||
u.getVersionFileURL()+".sig",
|
u.getVersionFileURL()+".sig",
|
||||||
@ -83,9 +83,9 @@ func (u *Updater) GetVersionInfo(downloader Downloader, channel Channel) (Versio
|
|||||||
return version, nil
|
return version, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (u *Updater) InstallUpdate(downloader Downloader, update VersionInfo) error {
|
func (u *Updater) InstallUpdate(ctx context.Context, downloader Downloader, update VersionInfo) error {
|
||||||
b, err := downloader.DownloadAndVerify(
|
b, err := downloader.DownloadAndVerify(
|
||||||
context.Background(),
|
ctx,
|
||||||
u.verifier,
|
u.verifier,
|
||||||
update.Package,
|
update.Package,
|
||||||
update.Package+".sig",
|
update.Package+".sig",
|
||||||
|
|||||||
@ -214,7 +214,7 @@ func (s *scenario) bridgeSendsAnUpdateAvailableEventForVersion(version string) e
|
|||||||
return errors.New("expected update available event, got none")
|
return errors.New("expected update available event, got none")
|
||||||
}
|
}
|
||||||
|
|
||||||
if !event.CanInstall {
|
if !event.Compatible {
|
||||||
return errors.New("expected update event to be installable")
|
return errors.New("expected update event to be installable")
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -231,7 +231,7 @@ func (s *scenario) bridgeSendsAManualUpdateEventForVersion(version string) error
|
|||||||
return errors.New("expected update available event, got none")
|
return errors.New("expected update available event, got none")
|
||||||
}
|
}
|
||||||
|
|
||||||
if event.CanInstall {
|
if event.Compatible {
|
||||||
return errors.New("expected update event to not be installable")
|
return errors.New("expected update event to not be installable")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user