diff --git a/.gitignore b/.gitignore index 897ac864..a7185b5a 100644 --- a/.gitignore +++ b/.gitignore @@ -31,6 +31,8 @@ vendor-cache cmd/Desktop-Bridge/deploy cmd/Import-Export/deploy proton-bridge +cmd/Desktop-Bridge/*.exe +cmd/launcher/*.exe # Jetbrains (CLion, Golang) cmake build dirs cmake-build-*/ diff --git a/cmd/launcher/main.go b/cmd/launcher/main.go index 07eb55ef..89b3bcd3 100644 --- a/cmd/launcher/main.go +++ b/cmd/launcher/main.go @@ -22,6 +22,7 @@ import ( "os" "path/filepath" "runtime" + "time" "github.com/Masterminds/semver/v3" "github.com/ProtonMail/gopenpgp/v2/crypto" @@ -33,6 +34,9 @@ import ( "github.com/ProtonMail/proton-bridge/v2/internal/sentry" "github.com/ProtonMail/proton-bridge/v2/internal/updater" "github.com/ProtonMail/proton-bridge/v2/internal/versioner" + "github.com/bradenaw/juniper/xslices" + "github.com/elastic/go-sysinfo" + "github.com/elastic/go-sysinfo/types" "github.com/pkg/errors" "github.com/sirupsen/logrus" "golang.org/x/sys/execabs" @@ -46,6 +50,7 @@ const ( FlagCLI = "--cli" FlagLauncher = "--launcher" + FlagWait = "--wait" ) func main() { //nolint:funlen @@ -92,7 +97,7 @@ func main() { //nolint:funlen exeToLaunch := guiName args := os.Args[1:] - if isCliMode(&args) { + if inCLIMode(args) { exeToLaunch = exeName } @@ -108,6 +113,12 @@ func main() { //nolint:funlen logrus.WithError(err).Fatal("Failed to determine path to launcher") } + var wait bool + args, wait = findAndStrip(args, FlagWait) + if wait { + waitForProcessToFinish(exe) + } + cmd := execabs.Command(exe, appendLauncherPath(launcher, args)...) //nolint:gosec cmd.Stdin = os.Stdin @@ -115,7 +126,8 @@ func main() { //nolint:funlen cmd.Stderr = os.Stderr // On windows, if you use Run(), a terminal stays open; we don't want that. - if runtime.GOOS == "windows" { + if //goland:noinspection GoBoolExpressions + runtime.GOOS == "windows" { err = cmd.Start() } else { err = cmd.Run() @@ -152,14 +164,21 @@ func appendLauncherPath(path string, args []string) []string { return res } -func isCliMode(args *[]string) bool { - for _, v := range *args { - if v == FlagCLI { - return true - } - } +func inCLIMode(args []string) bool { + return sliceContains(args, FlagCLI) +} - return false +// sliceContains checks if a value is present in a list. +func sliceContains[T comparable](list []T, s T) bool { + return xslices.Any(list, func(arg T) bool { return arg == s }) +} + +// findAndStrip check if a value is present in s list and remove all occurrences of the value from this list. +func findAndStrip[T comparable](slice []T, v T) (strippedList []T, found bool) { + strippedList = xslices.Filter(slice, func(value T) bool { + return value != v + }) + return strippedList, len(strippedList) != len(slice) } func getPathToUpdatedExecutable( @@ -222,3 +241,44 @@ func getFallbackExecutable(name string, versioner *versioner.Versioner) (string, return versioner.GetExecutableInDirectory(name, filepath.Dir(launcher)) } + +// waitForProcessToFinish waits until the process with the given path is finished. +func waitForProcessToFinish(exePath string) { + for { + processes, err := sysinfo.Processes() + if err != nil { + logrus.WithError(err).Error("Could not determine running processes") + return + } + + exeInfo, err := os.Stat(exePath) + if err != nil { + logrus.WithError(err).WithField("file", exeInfo).Error("Could not retrieve file info") + return + } + + if xslices.Any(processes, func(process types.Process) bool { + info, err := process.Info() + if err != nil { + logrus.WithError(err).Error("Could not retrieve process info") + } + + return sameFile(exeInfo, info.Exe) + }) { + logrus.Infof("Waiting for %v to finish.", exeInfo.Name()) + time.Sleep(1 * time.Second) + continue + } + + return + } +} + +func sameFile(info os.FileInfo, path string) bool { + pathInfo, err := os.Stat(path) + if err != nil { + logrus.WithError(err).WithField("file", path).Error("Could not retrieve file info") + } + + return os.SameFile(pathInfo, info) +} diff --git a/cmd/launcher/main_test.go b/cmd/launcher/main_test.go new file mode 100644 index 00000000..4d0e823a --- /dev/null +++ b/cmd/launcher/main_test.go @@ -0,0 +1,58 @@ +// 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 . + +package main + +import ( + "testing" + + "github.com/bradenaw/juniper/xslices" + "github.com/stretchr/testify/assert" +) + +func TestSliceContains(t *testing.T) { + assert.True(t, sliceContains([]string{"a", "b", "c"}, "a")) + assert.True(t, sliceContains([]int{1, 2, 3}, 2)) + assert.False(t, sliceContains([]string{"a", "b", "c"}, "A")) + assert.False(t, sliceContains([]int{1, 2, 3}, 4)) + assert.False(t, sliceContains([]string{}, "a")) + assert.True(t, sliceContains([]string{"a", "a"}, "a")) +} + +func TestFindAndStrip(t *testing.T) { + list := []string{"a", "b", "c", "c", "b", "c"} + + result, found := findAndStrip(list, "a") + assert.True(t, found) + assert.True(t, xslices.Equal(result, []string{"b", "c", "c", "b", "c"})) + + result, found = findAndStrip(list, "c") + assert.True(t, found) + assert.True(t, xslices.Equal(result, []string{"a", "b", "b"})) + + result, found = findAndStrip([]string{"c", "c", "c"}, "c") + assert.True(t, found) + assert.True(t, xslices.Equal(result, []string{})) + + result, found = findAndStrip(list, "A") + assert.False(t, found) + assert.True(t, xslices.Equal(result, list)) + + result, found = findAndStrip([]string{}, "a") + assert.False(t, found) + assert.True(t, xslices.Equal(result, []string{})) +} diff --git a/internal/app/base/restart.go b/internal/app/base/restart.go index a7014d60..efd9ef54 100644 --- a/internal/app/base/restart.go +++ b/internal/app/base/restart.go @@ -42,6 +42,8 @@ func (b *Base) restartApp(crash bool) error { args = forceLauncherFlag(args, b.launcher) } + args = append(args, "--wait") + logrus. WithField("command", b.command). WithField("args", args). diff --git a/internal/frontend/bridge-gui/bridge-gui/EventStreamWorker.cpp b/internal/frontend/bridge-gui/bridge-gui/EventStreamWorker.cpp index fe9223a0..a0cddb68 100644 --- a/internal/frontend/bridge-gui/bridge-gui/EventStreamWorker.cpp +++ b/internal/frontend/bridge-gui/bridge-gui/EventStreamWorker.cpp @@ -31,8 +31,8 @@ using namespace bridgepp; EventStreamReader::EventStreamReader(QObject *parent) : Worker(parent) { - connect(this, &EventStreamReader::started, [&]() { app().log().debug("EventStreamReader started"); }); - connect(this, &EventStreamReader::finished, [&]() { app().log().debug("EventStreamReader finished"); }); + connect(this, &EventStreamReader::started, this, &EventStreamReader::onStarted); + connect(this, &EventStreamReader::finished, this, &EventStreamReader::onFinished); connect(this, &EventStreamReader::error, &app().log(), &Log::error); } @@ -57,3 +57,27 @@ void EventStreamReader::run() emit error(e.qwhat()); } } + + +//**************************************************************************************************************************************************** +// +//**************************************************************************************************************************************************** +void EventStreamReader::onStarted() const +{ + app().log().debug("EventStreamReader started"); +} + + +//**************************************************************************************************************************************************** +// +//**************************************************************************************************************************************************** +void EventStreamReader::onFinished() const +{ + app().log().debug("EventStreamReader finished"); + if (!app().bridgeMonitor()) + { + // no bridge monitor means we are in a debug environment, running in attached mode. Event stream has terminated, so bridge is shutting + // down. Because we're in attached mode, bridge-gui will not get notified that bridge is going down, so we shutdown manually here. + qApp->exit(EXIT_SUCCESS); + } +} diff --git a/internal/frontend/bridge-gui/bridge-gui/EventStreamWorker.h b/internal/frontend/bridge-gui/bridge-gui/EventStreamWorker.h index e9976b19..b9aa9ba8 100644 --- a/internal/frontend/bridge-gui/bridge-gui/EventStreamWorker.h +++ b/internal/frontend/bridge-gui/bridge-gui/EventStreamWorker.h @@ -39,6 +39,8 @@ public: // member functions public slots: void run() override; ///< Run the reader. + void onStarted() const; ///< Slot for the 'started' signal. + void onFinished() const; ///< Slot for the 'finished' signal. signals: void eventReceived(QString eventString); ///< signal for events. diff --git a/internal/frontend/bridge-gui/bridge-gui/QMLBackend.cpp b/internal/frontend/bridge-gui/bridge-gui/QMLBackend.cpp index ec200c59..24f5e326 100644 --- a/internal/frontend/bridge-gui/bridge-gui/QMLBackend.cpp +++ b/internal/frontend/bridge-gui/bridge-gui/QMLBackend.cpp @@ -229,6 +229,10 @@ void QMLBackend::restart() app().grpc().quit(); } + +//**************************************************************************************************************************************************** +/// \param[in] launcher The path to the launcher. +//**************************************************************************************************************************************************** void QMLBackend::forceLauncher(QString launcher) { app().grpc().forceLauncher(launcher); @@ -270,8 +274,9 @@ void QMLBackend::changeColorScheme(QString const &scheme) //**************************************************************************************************************************************************** void QMLBackend::toggleUseSSLforSMTP(bool makeItActive) { - if (app().grpc().setUseSSLForSMTP(makeItActive).ok()) - emit useSSLforSMTPChanged(makeItActive); + // if call succeed, app will restart. No need to emit a value change signal, because it will trigger a read-back via gRPC that will fail. + emit hideMainWindow(); + app().grpc().setUseSSLForSMTP(makeItActive); } @@ -281,11 +286,21 @@ void QMLBackend::toggleUseSSLforSMTP(bool makeItActive) //**************************************************************************************************************************************************** void QMLBackend::changePorts(int imapPort, int smtpPort) { - if (app().grpc().changePorts(imapPort, smtpPort).ok()) - { - emit portIMAPChanged(imapPort); - emit portSMTPChanged(smtpPort); - } + // if call succeed, app will restart. No need to emit a value change signal, because it will trigger a read-back via gRPC that will fail. + emit hideMainWindow(); + app().grpc().changePorts(imapPort, smtpPort); +} + + +//**************************************************************************************************************************************************** +/// \param[in] enable Is cache enabled? +/// \param[in] path The path of the cache. +//**************************************************************************************************************************************************** +void QMLBackend::changeLocalCache(bool enable, QUrl const &path) +{ + // if call succeed, app will restart. No need to emit a value change signal, because it will trigger a read-back via gRPC that will fail. + emit hideMainWindow(); + app().grpc().changeLocalCache(enable, path); } @@ -353,3 +368,5 @@ void QMLBackend::onResetFinished() emit resetFinished(); this->restart(); } + + diff --git a/internal/frontend/bridge-gui/bridge-gui/QMLBackend.h b/internal/frontend/bridge-gui/bridge-gui/QMLBackend.h index ab7f6830..6ae1fa83 100644 --- a/internal/frontend/bridge-gui/bridge-gui/QMLBackend.h +++ b/internal/frontend/bridge-gui/bridge-gui/QMLBackend.h @@ -143,7 +143,7 @@ public slots: // slot for signals received from QML -> To be forwarded to Bridge void toggleAutostart(bool active); // _ func(makeItActive bool) `slot:"toggleAutostart"` void toggleBeta(bool active); // _ func(makeItActive bool) `slot:"toggleBeta"` void changeColorScheme(QString const &scheme); // _ func(string) `slot:"changeColorScheme"` - void changeLocalCache(bool enable, QUrl const& path) { app().grpc().changeLocalCache(enable, path); } // _ func(enableDiskCache bool, diskCachePath core.QUrl) `slot:"changeLocalCache"` + void changeLocalCache(bool enable, QUrl const& path); // _ func(enableDiskCache bool, diskCachePath core.QUrl) `slot:"changeLocalCache"` void login(QString const& username, QString const& password) { app().grpc().login(username, password);} // _ func(username, password string) `slot:"login"` void login2FA(QString const& username, QString const& code) { app().grpc().login2FA(username, code);} // _ func(username, code string) `slot:"login2FA"` void login2Password(QString const& username, QString const& password) { app().grpc().login2Passwords(username, password);} // _ func(username, password string) `slot:"login2Password"` @@ -211,6 +211,7 @@ signals: // Signals received from the Go backend, to be forwarded to QML void bugReportSendSuccess(); // _ func() `signal:"bugReportSendSuccess"` void bugReportSendError(); // _ func() `signal:"bugReportSendError"` void showMainWindow(); // _ func() `signal:showMainWindow` + void hideMainWindow(); private: // member functions void retrieveUserList(); ///< Retrieve the list of users via gRPC. diff --git a/internal/frontend/bridge-gui/bridge-gui/main.cpp b/internal/frontend/bridge-gui/bridge-gui/main.cpp index 2256f33c..ede55b78 100644 --- a/internal/frontend/bridge-gui/bridge-gui/main.cpp +++ b/internal/frontend/bridge-gui/bridge-gui/main.cpp @@ -224,12 +224,14 @@ void closeBridgeApp() //**************************************************************************************************************************************************** int main(int argc, char *argv[]) { + // The application instance is needed to display system message boxes. As we may have to do it in the exception handler, + // application instance is create outside the try/catch clause. + if (QSysInfo::productType() != "windows") + QCoreApplication::setAttribute(Qt::AA_UseSoftwareOpenGL); + QApplication guiApp(argc, argv); + try { - if (QSysInfo::productType() != "windows") - QCoreApplication::setAttribute(Qt::AA_UseSoftwareOpenGL); - - QApplication guiApp(argc, argv); initQtApplication(); Log &log = initLog(); @@ -302,6 +304,7 @@ int main(int argc, char *argv[]) } catch (Exception const &e) { + QMessageBox::critical(nullptr, "Error", e.qwhat()); QTextStream(stderr) << e.qwhat() << "\n"; return EXIT_FAILURE; } diff --git a/internal/frontend/bridge-gui/bridge-gui/qml/Bridge.qml b/internal/frontend/bridge-gui/bridge-gui/qml/Bridge.qml index 75cc2a6f..80127650 100644 --- a/internal/frontend/bridge-gui/bridge-gui/qml/Bridge.qml +++ b/internal/frontend/bridge-gui/bridge-gui/qml/Bridge.qml @@ -59,6 +59,10 @@ QtObject { mainWindow.showAndRise() } function onColorSchemeNameChanged(scheme) { root.setColorScheme() } + + function onHideMainWindow() { + mainWindow.hide(); + } } } diff --git a/internal/frontend/bridge-gui/bridgepp/bridgepp/GRPC/GRPCClient.cpp b/internal/frontend/bridge-gui/bridgepp/bridgepp/GRPC/GRPCClient.cpp index 89e3a3a8..a0cd87aa 100644 --- a/internal/frontend/bridge-gui/bridgepp/bridgepp/GRPC/GRPCClient.cpp +++ b/internal/frontend/bridge-gui/bridgepp/bridgepp/GRPC/GRPCClient.cpp @@ -288,7 +288,9 @@ grpc::Status GRPCClient::reportBug(QString const &description, QString const &ad //**************************************************************************************************************************************************** grpc::Status GRPCClient::useSSLForSMTP(bool &outUseSSL) { - return this->logGRPCCallStatus(this->getBool(&Bridge::Stub::UseSslForSmtp, outUseSSL), __FUNCTION__); + Status status = this->getBool(&Bridge::Stub::UseSslForSmtp, outUseSSL); + return this->logGRPCCallStatus(status, __FUNCTION__); + } @@ -363,7 +365,8 @@ grpc::Status GRPCClient::setIsDoHEnabled(bool enabled) grpc::Status GRPCClient::quit() { grpc::ClientContext ctx; - return this->logGRPCCallStatus(stub_->Quit(&ctx, empty, &empty), __FUNCTION__); + // quitting will shut down the gRPC service, to we may get an 'Unavailable' response for the call + return this->logGRPCCallStatus(stub_->Quit(&ctx, empty, &empty), __FUNCTION__, { StatusCode::UNAVAILABLE }); } @@ -373,7 +376,8 @@ grpc::Status GRPCClient::quit() grpc::Status GRPCClient::restart() { grpc::ClientContext ctx; - return this->logGRPCCallStatus(stub_->Restart(&ctx, empty, &empty), __FUNCTION__); + // restarting will shut down the gRPC service, to we may get an 'Unavailable' response for the call + return this->logGRPCCallStatus(stub_->Restart(&ctx, empty, &empty), __FUNCTION__, { StatusCode::UNAVAILABLE }); } @@ -882,11 +886,11 @@ void GRPCClient::logError(QString const &message) /// \param[in] status The status /// \param[in] callName The call name. //**************************************************************************************************************************************************** -grpc::Status GRPCClient::logGRPCCallStatus(Status const &status, QString const &callName) +grpc::Status GRPCClient::logGRPCCallStatus(Status const &status, QString const &callName, QList allowedErrors) { if (log_) { - if (status.ok()) + if (status.ok() || allowedErrors.contains(status.error_code())) log_->debug(QString("%1()").arg(callName)); else log_->error(QString("%1() FAILED").arg(callName)); diff --git a/internal/frontend/bridge-gui/bridgepp/bridgepp/GRPC/GRPCClient.h b/internal/frontend/bridge-gui/bridgepp/bridgepp/GRPC/GRPCClient.h index 15ea4e67..d1c645f3 100644 --- a/internal/frontend/bridge-gui/bridgepp/bridgepp/GRPC/GRPCClient.h +++ b/internal/frontend/bridge-gui/bridgepp/bridgepp/GRPC/GRPCClient.h @@ -201,7 +201,7 @@ private slots: private: void logDebug(QString const &message); ///< Log an event. void logError(QString const &message); ///< Log an event. - grpc::Status logGRPCCallStatus(grpc::Status const &status, QString const &callName); ///< Log the status of a gRPC code. + grpc::Status logGRPCCallStatus(grpc::Status const &status, QString const &callName, QList allowedErrors = {}); ///< Log the status of a gRPC code. grpc::Status simpleMethod(SimpleMethod method); ///< perform a gRPC call to a bool setter. grpc::Status setBool(BoolSetter setter, bool value); ///< perform a gRPC call to a bool setter. grpc::Status getBool(BoolGetter getter, bool &outValue); ///< perform a gRPC call to a bool getter. diff --git a/internal/frontend/grpc/service.go b/internal/frontend/grpc/service.go index 579349c6..bc927d14 100644 --- a/internal/frontend/grpc/service.go +++ b/internal/frontend/grpc/service.go @@ -20,6 +20,7 @@ package grpc //go:generate protoc --go_out=. --go_opt=paths=source_relative --go-grpc_out=. --go-grpc_opt=paths=source_relative bridge.proto import ( + "context" cryptotls "crypto/tls" "net" "strings" @@ -39,6 +40,7 @@ import ( "github.com/sirupsen/logrus" "google.golang.org/grpc" "google.golang.org/grpc/credentials" + "google.golang.org/protobuf/types/known/emptypb" ) // Service is the RPC service struct. @@ -223,7 +225,7 @@ func (s *Service) watchEvents() { // nolint:funlen case <-secondInstanceCh: _ = s.SendEvent(NewShowMainWindowEvent()) case <-restartBridgeCh: - s.restart() + _, _ = s.Restart(context.Background(), &emptypb.Empty{}) case address := <-addressChangedCh: _ = s.SendEvent(NewMailAddressChangeEvent(address)) case address := <-addressChangedLogoutCh: @@ -311,10 +313,6 @@ func (s *Service) waitForUserChangeDone(done <-chan string, userID string) { } } -func (s *Service) restart() { - s.restarter.SetToRestart() -} - func (s *Service) triggerReset() { defer func() { _ = s.SendEvent(NewResetFinishedEvent()) diff --git a/internal/frontend/grpc/service_methods.go b/internal/frontend/grpc/service_methods.go index aa22174d..c0b49661 100644 --- a/internal/frontend/grpc/service_methods.go +++ b/internal/frontend/grpc/service_methods.go @@ -38,8 +38,6 @@ import ( "google.golang.org/protobuf/types/known/wrapperspb" ) -var ErrNotImplemented = status.Errorf(codes.Unimplemented, "Not implemented") - func (s *Service) AddLogEntry(_ context.Context, request *AddLogEntryRequest) (*emptypb.Empty, error) { entry := s.log if len(request.Package) > 0 { @@ -77,31 +75,30 @@ func (s *Service) GuiReady(context.Context, *emptypb.Empty) (*emptypb.Empty, err // Quit implement the Quit gRPC service call. func (s *Service) Quit(ctx context.Context, empty *emptypb.Empty) (*emptypb.Empty, error) { s.log.Info("Quit") - var err error - if s.eventStreamCh != nil { - if _, err = s.StopEventStream(ctx, empty); err != nil { - s.log.WithError(err).Error("Quit failed.") - } - } - // The following call is launched as a goroutine, as it will wait for current calls to end, including this one. - go func() { s.grpcServer.GracefulStop() }() - - return &emptypb.Empty{}, err -} - -// Restart implement the Restart gRPC service call. -func (s *Service) Restart(context.Context, *emptypb.Empty) (*emptypb.Empty, error) { - s.log.Info("Restart") + // Windows is notably slow at Quitting. We do it in a goroutine to speed things up a bit. go func() { - defer s.panicHandler.HandlePanic() + var err error + if s.eventStreamCh != nil { + if _, err = s.StopEventStream(ctx, empty); err != nil { + s.log.WithError(err).Error("Quit failed.") + } + } - s.restart() + // The following call is launched as a goroutine, as it will wait for current calls to end, including this one. + s.grpcServer.GracefulStop() }() return &emptypb.Empty{}, nil } +// Restart implement the Restart gRPC service call. +func (s *Service) Restart(ctx context.Context, empty *emptypb.Empty) (*emptypb.Empty, error) { + s.log.Info("Restart") + s.restarter.SetToRestart() + return s.Quit(ctx, empty) +} + func (s *Service) ShowOnStartup(context.Context, *emptypb.Empty) (*wrapperspb.BoolValue, error) { s.log.Info("ShowOnStartup") @@ -486,11 +483,12 @@ func (s *Service) DiskCachePath(context.Context, *emptypb.Empty) (*wrapperspb.St return wrapperspb.String(s.bridge.Get(settings.CacheLocationKey)), nil } -func (s *Service) ChangeLocalCache(_ context.Context, change *ChangeLocalCacheRequest) (*emptypb.Empty, error) { +func (s *Service) ChangeLocalCache(ctx context.Context, change *ChangeLocalCacheRequest) (*emptypb.Empty, error) { s.log.WithField("enableDiskCache", change.EnableDiskCache). WithField("diskCachePath", change.DiskCachePath). Info("DiskCachePath") + defer func() { _, _ = s.Restart(ctx, &emptypb.Empty{}) }() defer func() { _ = s.SendEvent(NewCacheChangeLocalCacheFinishedEvent()) }() defer func() { _ = s.SendEvent(NewIsCacheOnDiskEnabledChanged(s.bridge.GetBool(settings.CacheEnabledKey))) }() defer func() { _ = s.SendEvent(NewDiskCachePathChanged(s.bridge.Get(settings.CacheCompressionKey))) }() @@ -523,7 +521,6 @@ func (s *Service) ChangeLocalCache(_ context.Context, change *ChangeLocalCacheRe } _ = s.SendEvent(NewCacheLocationChangeSuccessEvent()) - s.restart() return &emptypb.Empty{}, nil } @@ -542,19 +539,18 @@ func (s *Service) IsDoHEnabled(context.Context, *emptypb.Empty) (*wrapperspb.Boo return wrapperspb.Bool(s.bridge.GetProxyAllowed()), nil } -func (s *Service) SetUseSslForSmtp(_ context.Context, useSsl *wrapperspb.BoolValue) (*emptypb.Empty, error) { //nolint:revive,stylecheck +func (s *Service) SetUseSslForSmtp(ctx context.Context, useSsl *wrapperspb.BoolValue) (*emptypb.Empty, error) { //nolint:revive,stylecheck s.log.WithField("useSsl", useSsl.Value).Info("SetUseSslForSmtp") if s.bridge.GetBool(settings.SMTPSSLKey) == useSsl.Value { return &emptypb.Empty{}, nil } - defer func() { _ = s.SendEvent(NewMailSettingsUseSslForSmtpFinishedEvent()) }() - s.bridge.SetBool(settings.SMTPSSLKey, useSsl.Value) - s.restart() - return &emptypb.Empty{}, nil + defer func() { _, _ = s.Restart(ctx, &emptypb.Empty{}) }() + + return &emptypb.Empty{}, s.SendEvent(NewMailSettingsUseSslForSmtpFinishedEvent()) } func (s *Service) UseSslForSmtp(context.Context, *emptypb.Empty) (*wrapperspb.BoolValue, error) { //nolint:revive,stylecheck @@ -581,16 +577,15 @@ func (s *Service) SmtpPort(context.Context, *emptypb.Empty) (*wrapperspb.Int32Va return wrapperspb.Int32(int32(s.bridge.GetInt(settings.SMTPPortKey))), nil } -func (s *Service) ChangePorts(_ context.Context, ports *ChangePortsRequest) (*emptypb.Empty, error) { +func (s *Service) ChangePorts(ctx context.Context, ports *ChangePortsRequest) (*emptypb.Empty, error) { s.log.WithField("imapPort", ports.ImapPort).WithField("smtpPort", ports.SmtpPort).Info("ChangePorts") - defer func() { _ = s.SendEvent(NewMailSettingsChangePortFinishedEvent()) }() - s.bridge.SetInt(settings.IMAPPortKey, int(ports.ImapPort)) s.bridge.SetInt(settings.SMTPPortKey, int(ports.SmtpPort)) - s.restart() - return &emptypb.Empty{}, nil + defer func() { _, _ = s.Restart(ctx, &emptypb.Empty{}) }() + + return &emptypb.Empty{}, s.SendEvent(NewMailSettingsChangePortFinishedEvent()) } func (s *Service) IsPortFree(_ context.Context, port *wrapperspb.Int32Value) (*wrapperspb.BoolValue, error) { diff --git a/internal/frontend/grpc/service_user.go b/internal/frontend/grpc/service_user.go index 14c8e390..9a6af477 100644 --- a/internal/frontend/grpc/service_user.go +++ b/internal/frontend/grpc/service_user.go @@ -124,7 +124,7 @@ func (s *Service) RemoveUser(_ context.Context, userID *wrapperspb.StringValue) return &emptypb.Empty{}, nil } -func (s *Service) ConfigureUserAppleMail(_ context.Context, request *ConfigureAppleMailRequest) (*emptypb.Empty, error) { +func (s *Service) ConfigureUserAppleMail(ctx context.Context, request *ConfigureAppleMailRequest) (*emptypb.Empty, error) { s.log.WithField("UserID", request.UserID).WithField("Address", request.Address).Info("ConfigureUserAppleMail") restart, err := s.bridge.ConfigureAppleMail(request.UserID, request.Address) @@ -137,7 +137,7 @@ func (s *Service) ConfigureUserAppleMail(_ context.Context, request *ConfigureAp if restart { s.log.Warn("Detected Catalina or newer with bad SMTP SSL settings, now using SSL, bridge needs to restart") time.Sleep(2 * time.Second) - s.restart() + return s.Restart(ctx, &emptypb.Empty{}) } return &emptypb.Empty{}, nil