diff --git a/cmd/launcher/main.go b/cmd/launcher/main.go index 462682ae..9b6302f9 100644 --- a/cmd/launcher/main.go +++ b/cmd/launcher/main.go @@ -56,6 +56,7 @@ const ( func main() { //nolint:funlen logrus.SetLevel(logrus.DebugLevel) l := logrus.WithField("launcher_version", constants.Version) + reporter := sentry.NewReporter(appName, constants.Version, useragent.New()) crashHandler := crash.NewHandler(reporter.ReportException) @@ -75,7 +76,7 @@ func main() { //nolint:funlen crashHandler.AddRecoveryAction(logging.DumpStackTrace(logsPath)) if err := logging.Init(logsPath, os.Getenv("VERBOSITY")); err != nil { - logrus.WithError(err).Fatal("Failed to setup logging") + l.WithError(err).Fatal("Failed to setup logging") } updatesPath, err := locations.ProvideUpdatesPath() @@ -240,7 +241,7 @@ func getPathToUpdatedExecutable( } // Skip versions that are less or equal to launcher version. - if currentVersion != nil && !version.SemVer().GreaterThan(currentVersion) { + if currentVersion != nil && !versioner.IsNewerIgnorePrerelease(version.SemVer(), currentVersion) { continue } diff --git a/internal/bridge/updates.go b/internal/bridge/updates.go index 478661cf..d1b6444a 100644 --- a/internal/bridge/updates.go +++ b/internal/bridge/updates.go @@ -19,10 +19,12 @@ package bridge import ( "context" + "errors" "github.com/ProtonMail/proton-bridge/v2/internal/events" "github.com/ProtonMail/proton-bridge/v2/internal/safe" "github.com/ProtonMail/proton-bridge/v2/internal/updater" + "github.com/ProtonMail/proton-bridge/v2/internal/versioner" "github.com/sirupsen/logrus" ) @@ -58,7 +60,7 @@ func (bridge *Bridge) handleUpdate(version updater.VersionInfo) { }) switch { - case !version.Version.GreaterThan(bridge.curVersion): + case !versioner.IsNewerIgnorePrerelease(version.Version, bridge.curVersion): log.Debug("No update available") bridge.publish(events.UpdateNotAvailable{}) @@ -68,7 +70,7 @@ func (bridge *Bridge) handleUpdate(version updater.VersionInfo) { bridge.publish(events.UpdateNotAvailable{}) - case bridge.curVersion.LessThan(version.MinAuto): + case versioner.IsNewerIgnorePrerelease(version.MinAuto, bridge.curVersion): log.Info("An update is available but is incompatible with this version") bridge.publish(events.UpdateAvailable{ @@ -88,7 +90,7 @@ func (bridge *Bridge) handleUpdate(version updater.VersionInfo) { default: safe.RLock(func() { - if version.Version.GreaterThan(bridge.newVersion) { + if versioner.IsNewerIgnorePrerelease(version.Version, bridge.newVersion) { log.Info("An update is available") select { @@ -127,15 +129,21 @@ func (bridge *Bridge) installUpdate(ctx context.Context, job installJob) { Silent: job.silent, }) - if err := bridge.updater.InstallUpdate(ctx, bridge.api, job.version); err != nil { - log.Error("The update could not be installed") + err := bridge.updater.InstallUpdate(ctx, bridge.api, job.version) + + switch { + case errors.Is(err, updater.ErrUpdateAlreadyInstalled): + log.Info("The update was already installed") + + case err != nil: + log.WithError(err).Error("The update could not be installed") bridge.publish(events.UpdateFailed{ Version: job.version, Silent: job.silent, Error: err, }) - } else { + default: log.Info("The update was installed successfully") bridge.publish(events.UpdateInstalled{ diff --git a/internal/updater/install_darwin.go b/internal/updater/install_darwin.go index 56a30453..002c5f92 100644 --- a/internal/updater/install_darwin.go +++ b/internal/updater/install_darwin.go @@ -61,3 +61,7 @@ func (i *InstallerDarwin) InstallUpdate(_ *semver.Version, r io.Reader) error { return syncFolders(oldBundle, newBundle) } + +func (i *InstallerDarwin) IsAlreadyInstalled(version *semver.Version) bool { + return false +} diff --git a/internal/updater/install_default.go b/internal/updater/install_default.go index ab424ef2..9cfaeaf2 100644 --- a/internal/updater/install_default.go +++ b/internal/updater/install_default.go @@ -25,6 +25,7 @@ import ( "github.com/Masterminds/semver/v3" "github.com/ProtonMail/proton-bridge/v2/internal/versioner" + "github.com/sirupsen/logrus" ) type InstallerDefault struct { @@ -40,3 +41,15 @@ func NewInstaller(versioner *versioner.Versioner) *InstallerDefault { func (i *InstallerDefault) InstallUpdate(version *semver.Version, r io.Reader) error { return i.versioner.InstallNewVersion(version, r) } + +func (i *InstallerDefault) IsAlreadyInstalled(version *semver.Version) bool { + versions, err := i.versioner.ListVersions() + if err != nil { + logrus.WithField("version", version). + WithError(err).Error("Failed to determine whether version is installed") + + return false + } + + return versions.HasVersion(version) +} diff --git a/internal/updater/updater.go b/internal/updater/updater.go index 23b410da..8d9aac7a 100644 --- a/internal/updater/updater.go +++ b/internal/updater/updater.go @@ -30,8 +30,9 @@ import ( ) var ( - ErrDownloadVerify = errors.New("failed to download or verify the update") - ErrInstall = errors.New("failed to install the update") + ErrDownloadVerify = errors.New("failed to download or verify the update") + ErrInstall = errors.New("failed to install the update") + ErrUpdateAlreadyInstalled = errors.New("update is already installed") ) type Downloader interface { @@ -39,6 +40,7 @@ type Downloader interface { } type Installer interface { + IsAlreadyInstalled(*semver.Version) bool InstallUpdate(*semver.Version, io.Reader) error } @@ -84,6 +86,10 @@ func (u *Updater) GetVersionInfo(ctx context.Context, downloader Downloader, cha } func (u *Updater) InstallUpdate(ctx context.Context, downloader Downloader, update VersionInfo) error { + if u.installer.IsAlreadyInstalled(update.Version) { + return ErrUpdateAlreadyInstalled + } + b, err := downloader.DownloadAndVerify( ctx, u.verifier, diff --git a/internal/versioner/version.go b/internal/versioner/version.go index a55aec35..a8cce75a 100644 --- a/internal/versioner/version.go +++ b/internal/versioner/version.go @@ -50,6 +50,16 @@ func (v Versions) Swap(i, j int) { v[i], v[j] = v[j], v[i] } +func (v Versions) HasVersion(want *semver.Version) bool { + for i := range v { + if v[i].version.Equal(want) { + return true + } + } + + return false +} + func (v *Version) String() string { return fmt.Sprintf("%v", v.version) } @@ -101,13 +111,7 @@ func (v *Version) VerifyFiles(kr *crypto.KeyRing) error { // GetExecutable returns the full path to the executable of the given version. // It returns an error if the executable is missing or does not have executable permissions set. func (v *Version) GetExecutable(name string) (string, error) { - exe := filepath.Join(v.path, getExeName(name)) - - if !fileExists(exe) || !fileIsExecutable(exe) { - return "", ErrNoExecutable - } - - return exe, nil + return getExecutableInDirectory(name, v.path) } // Remove removes this version directory. diff --git a/internal/versioner/versioner.go b/internal/versioner/versioner.go index be815c51..c69cb28b 100644 --- a/internal/versioner/versioner.go +++ b/internal/versioner/versioner.go @@ -72,6 +72,10 @@ func (v *Versioner) ListVersions() (Versions, error) { // GetExecutableInDirectory returns the full path to the executable in the given directory, if present. // It returns an error if the executable is missing or does not have executable permissions set. func (v *Versioner) GetExecutableInDirectory(name, directory string) (string, error) { + return getExecutableInDirectory(name, directory) +} + +func getExecutableInDirectory(name, directory string) (string, error) { exe := filepath.Join(directory, getExeName(name)) if !fileExists(exe) || !fileIsExecutable(exe) { @@ -80,3 +84,10 @@ func (v *Versioner) GetExecutableInDirectory(name, directory string) (string, er return exe, nil } + +func IsNewerIgnorePrerelease(a, b *semver.Version) bool { + aN, _ := a.SetPrerelease("") + bN, _ := b.SetPrerelease("") + + return aN.GreaterThan(&bN) +} diff --git a/internal/versioner/versioner_test.go b/internal/versioner/versioner_test.go index fef55469..7c4c9d56 100644 --- a/internal/versioner/versioner_test.go +++ b/internal/versioner/versioner_test.go @@ -27,6 +27,21 @@ import ( "github.com/stretchr/testify/require" ) +func TestIsNewerIgnorePrelease(t *testing.T) { + // older + assert.False(t, IsNewerIgnorePrerelease(semver.MustParse("2.5.0"), semver.MustParse("2.5.1"))) + assert.False(t, IsNewerIgnorePrerelease(semver.MustParse("2.5.0"), semver.MustParse("2.5.0"))) + assert.False(t, IsNewerIgnorePrerelease(semver.MustParse("2.5.0"), semver.MustParse("2.5.0+qa"))) + assert.False(t, IsNewerIgnorePrerelease(semver.MustParse("2.5.0"), semver.MustParse("2.5.0-dev"))) + assert.False(t, IsNewerIgnorePrerelease(semver.MustParse("2.5.0"), semver.MustParse("2.5.0-dev+qa"))) + assert.False(t, IsNewerIgnorePrerelease(semver.MustParse("2.5.0+qa"), semver.MustParse("2.5.0-dev"))) + assert.False(t, IsNewerIgnorePrerelease(semver.MustParse("2.5.0-dev"), semver.MustParse("2.5.0+qa"))) + + // not older + assert.True(t, IsNewerIgnorePrerelease(semver.MustParse("2.5.0"), semver.MustParse("2.4.9-dev+qa"))) + assert.True(t, IsNewerIgnorePrerelease(semver.MustParse("2.5.0-dev+qa"), semver.MustParse("2.4.9"))) +} + func TestListVersions(t *testing.T) { dir := t.TempDir()