diff --git a/internal/app/bridge/bridge.go b/internal/app/bridge/bridge.go index c17d5c1..47b47f5 100644 --- a/internal/app/bridge/bridge.go +++ b/internal/app/bridge/bridge.go @@ -24,7 +24,7 @@ import ( "github.com/ProtonMail/proton-bridge/internal/api" "github.com/ProtonMail/proton-bridge/internal/app/base" - "github.com/ProtonMail/proton-bridge/internal/bridge" + pkgBridge "github.com/ProtonMail/proton-bridge/internal/bridge" "github.com/ProtonMail/proton-bridge/internal/config/settings" pkgTLS "github.com/ProtonMail/proton-bridge/internal/config/tls" "github.com/ProtonMail/proton-bridge/internal/constants" @@ -46,6 +46,10 @@ const ( flagLogSMTP = "log-smtp" flagNoWindow = "no-window" flagNonInteractive = "noninteractive" + + // Memory cache was estimated by empirical usage in past and it was set to 100MB. + // NOTE: This value must not be less than maximal size of one email (~30MB). + inMemoryCacheLimnit = 100 * (1 << 20) ) func New(base *base.Base) *cli.App { @@ -75,9 +79,9 @@ func run(b *base.Base, c *cli.Context) error { // nolint[funlen] return err } - cache, err := loadMessageCache(b) - if err != nil { - return err + cache, cacheErr := loadMessageCache(b) + if cacheErr != nil { + logrus.WithError(cacheErr).Error("Could not load local cache.") } builder := message.NewBuilder( @@ -85,10 +89,14 @@ func run(b *base.Base, c *cli.Context) error { // nolint[funlen] b.Settings.GetInt(settings.AttachmentWorkers), ) - bridge := bridge.New(b.Locations, b.Cache, b.Settings, b.SentryReporter, b.CrashHandler, b.Listener, cache, builder, b.CM, b.Creds, b.Updater, b.Versioner) + bridge := pkgBridge.New(b.Locations, b.Cache, b.Settings, b.SentryReporter, b.CrashHandler, b.Listener, cache, builder, b.CM, b.Creds, b.Updater, b.Versioner) imapBackend := imap.NewIMAPBackend(b.CrashHandler, b.Listener, b.Cache, b.Settings, bridge) smtpBackend := smtp.NewSMTPBackend(b.CrashHandler, b.Listener, b.Settings, bridge) + if cacheErr != nil { + bridge.AddError(pkgBridge.ErrLocalCacheUnavailable) + } + go func() { defer b.CrashHandler.HandlePanic() api.NewAPIServer(b.Settings, b.Listener).ListenAndServe() @@ -248,13 +256,12 @@ func checkAndHandleUpdate(u types.Updater, f frontend.Frontend, autoUpdate bool) f.NotifySilentUpdateInstalled() } +// loadMessageCache loads local cache in case it is enabled in settings and available. +// In any other case it is returning in-memory cache. Could also return an error in case +// local cache is enabled but unavailable (in-memory cache will be returned nevertheless). func loadMessageCache(b *base.Base) (cache.Cache, error) { if !b.Settings.GetBool(settings.CacheEnabledKey) { - // Memory cache was estimated by empirical usage in past and it - // was set to 100MB. - // NOTE: This value must not be less than maximal size of one - // email (~30MB). - return cache.NewInMemoryCache(100 << 20), nil + return cache.NewInMemoryCache(inMemoryCacheLimnit), nil } var compressor cache.Compressor @@ -283,10 +290,16 @@ func loadMessageCache(b *base.Base) (cache.Cache, error) { // build jobs. store.SetBuildAndCacheJobLimit(b.Settings.GetInt(settings.CacheConcurrencyWrite)) - return cache.NewOnDiskCache(path, compressor, cache.Options{ + messageCache, err := cache.NewOnDiskCache(path, compressor, cache.Options{ MinFreeAbs: uint64(b.Settings.GetInt(settings.CacheMinFreeAbsKey)), MinFreeRat: b.Settings.GetFloat64(settings.CacheMinFreeRatKey), ConcurrentRead: b.Settings.GetInt(settings.CacheConcurrencyRead), ConcurrentWrite: b.Settings.GetInt(settings.CacheConcurrencyWrite), }) + + if err != nil { + return cache.NewInMemoryCache(inMemoryCacheLimnit), err + } + + return messageCache, nil } diff --git a/internal/bridge/bridge.go b/internal/bridge/bridge.go index e4e4e8e..81d3fe0 100644 --- a/internal/bridge/bridge.go +++ b/internal/bridge/bridge.go @@ -19,6 +19,7 @@ package bridge import ( + "errors" "fmt" "strconv" "time" @@ -40,6 +41,8 @@ import ( var log = logrus.WithField("pkg", "bridge") //nolint[gochecknoglobals] +var ErrLocalCacheUnavailable = errors.New("local cache is unavailable") + type Bridge struct { *users.Users @@ -49,6 +52,8 @@ type Bridge struct { updater Updater versioner Versioner cacheProvider CacheProvider + // Bridge's global errors list. + errors []error } func New( @@ -241,3 +246,36 @@ func (b *Bridge) SetProxyAllowed(proxyAllowed bool) { func (b *Bridge) GetProxyAllowed() bool { return b.settings.GetBool(settings.AllowProxyKey) } + +// AddError add an error to a global error list if it does not contain it yet. Adding nil is noop. +func (b *Bridge) AddError(err error) { + if err == nil { + return + } + if b.HasError(err) { + return + } + + b.errors = append(b.errors, err) +} + +// DelError removes an error from global error list. +func (b *Bridge) DelError(err error) { + for idx, val := range b.errors { + if val == err { + b.errors = append(b.errors[:idx], b.errors[idx+1:]...) + return + } + } +} + +// HasError returnes true if global error list contains an err. +func (b *Bridge) HasError(err error) bool { + for _, val := range b.errors { + if val == err { + return true + } + } + + return false +} diff --git a/internal/frontend/qml/Banner.qml b/internal/frontend/qml/Banner.qml index 00e9703..0a839b7 100644 --- a/internal/frontend/qml/Banner.qml +++ b/internal/frontend/qml/Banner.qml @@ -80,13 +80,13 @@ Popup { } switch (root.notification.type) { - case Notification.NotificationType.Info: + case Notification.NotificationType.Info: return root.colorScheme.signal_info - case Notification.NotificationType.Success: + case Notification.NotificationType.Success: return root.colorScheme.signal_success - case Notification.NotificationType.Warning: + case Notification.NotificationType.Warning: return root.colorScheme.signal_warning - case Notification.NotificationType.Danger: + case Notification.NotificationType.Danger: return root.colorScheme.signal_danger } } @@ -118,13 +118,13 @@ Popup { } switch (root.notification.type) { - case Notification.NotificationType.Info: + case Notification.NotificationType.Info: return "./icons/ic-info-circle-filled.svg" - case Notification.NotificationType.Success: + case Notification.NotificationType.Success: return "./icons/ic-info-circle-filled.svg" - case Notification.NotificationType.Warning: + case Notification.NotificationType.Warning: return "./icons/ic-exclamation-circle-filled.svg" - case Notification.NotificationType.Danger: + case Notification.NotificationType.Danger: return "./icons/ic-exclamation-circle-filled.svg" } } @@ -136,7 +136,7 @@ Popup { Layout.alignment: Qt.AlignVCenter color: root.colorScheme.text_invert - text: root.notification ? root.notification.text : "" + text: root.notification ? root.notification.description : "" wrapMode: Text.WordWrap } @@ -152,13 +152,13 @@ Popup { } switch (root.notification.type) { - case Notification.NotificationType.Info: + case Notification.NotificationType.Info: return root.colorScheme.signal_info_active - case Notification.NotificationType.Success: + case Notification.NotificationType.Success: return root.colorScheme.signal_success_active - case Notification.NotificationType.Warning: + case Notification.NotificationType.Warning: return root.colorScheme.signal_warning_active - case Notification.NotificationType.Danger: + case Notification.NotificationType.Danger: return root.colorScheme.signal_danger_active } } @@ -190,22 +190,22 @@ Popup { var active switch (root.notification.type) { - case Notification.NotificationType.Info: + case Notification.NotificationType.Info: norm = root.colorScheme.signal_info hover = root.colorScheme.signal_info_hover active = root.colorScheme.signal_info_active break; - case Notification.NotificationType.Success: + case Notification.NotificationType.Success: norm = root.colorScheme.signal_success hover = root.colorScheme.signal_success_hover active = root.colorScheme.signal_success_active break; - case Notification.NotificationType.Warning: + case Notification.NotificationType.Warning: norm = root.colorScheme.signal_warning hover = root.colorScheme.signal_warning_hover active = root.colorScheme.signal_warning_active break; - case Notification.NotificationType.Danger: + case Notification.NotificationType.Danger: norm = root.colorScheme.signal_danger hover = root.colorScheme.signal_danger_hover active = root.colorScheme.signal_danger_active diff --git a/internal/frontend/qml/Bridge.qml b/internal/frontend/qml/Bridge.qml index e872c83..36c605e 100644 --- a/internal/frontend/qml/Bridge.qml +++ b/internal/frontend/qml/Bridge.qml @@ -52,6 +52,13 @@ QtObject { onVisibleChanged: { backend.dockIconVisible = visible } + + Connections { + target: root.backend + onCacheUnavailable: { + mainWindow.showAndRise() + } + } } property StatusWindow _statusWindow: StatusWindow { diff --git a/internal/frontend/qml/NotificationDialog.qml b/internal/frontend/qml/NotificationDialog.qml index b02c560..e94630b 100644 --- a/internal/frontend/qml/NotificationDialog.qml +++ b/internal/frontend/qml/NotificationDialog.qml @@ -55,12 +55,12 @@ Dialog { } switch (root.notification.type) { - case Notification.NotificationType.Info: + case Notification.NotificationType.Info: return "./icons/ic-info.svg" - case Notification.NotificationType.Success: + case Notification.NotificationType.Success: return "./icons/ic-success.svg" - case Notification.NotificationType.Warning: - case Notification.NotificationType.Danger: + case Notification.NotificationType.Warning: + case Notification.NotificationType.Danger: return "./icons/ic-alert.svg" } } @@ -70,7 +70,7 @@ Dialog { Layout.alignment: Qt.AlignHCenter Layout.bottomMargin: 8 colorScheme: root.colorScheme - text: root.notification.text + text: root.notification.title type: Label.LabelType.Title } diff --git a/internal/frontend/qml/Notifications/Notification.qml b/internal/frontend/qml/Notifications/Notification.qml index 5c267b5..254615b 100644 --- a/internal/frontend/qml/Notifications/Notification.qml +++ b/internal/frontend/qml/Notifications/Notification.qml @@ -30,8 +30,13 @@ QtObject { Danger = 3 } - property string text + // title is used in dialogs only + property string title + // description is used in banners and in dialogs as description property string description + // brief is used in status view only + property string brief + property string icon property list action property int type diff --git a/internal/frontend/qml/Notifications/Notifications.qml b/internal/frontend/qml/Notifications/Notifications.qml index 34205bd..a20aa58 100644 --- a/internal/frontend/qml/Notifications/Notifications.qml +++ b/internal/frontend/qml/Notifications/Notifications.qml @@ -74,7 +74,8 @@ QtObject { // Connection property Notification noInternet: Notification { - text: qsTr("No connection") + description: qsTr("Bridge is not able to contact the server, please check your internet connection.") + brief: qsTr("No connection") icon: "./icons/ic-no-connection.svg" type: Notification.NotificationType.Danger group: Notifications.Group.Connection @@ -93,8 +94,9 @@ QtObject { // Updates property Notification updateManualReady: Notification { - text: qsTr("Update to Bridge") + " " + (data ? data.version : "") + title: qsTr("Update to Bridge %1").arg(data ? data.version : "") description: qsTr("A new version of ProtonMail Bridge is available. See what's changed.") + brief: qsTr("Update available. (See what's new.)") icon: "./icons/ic-info-circle-filled.svg" type: Notification.NotificationType.Info group: Notifications.Group.Update | Notifications.Group.Dialogs @@ -135,7 +137,8 @@ QtObject { } property Notification updateManualRestartNeeded: Notification { - text: qsTr("Bridge update is ready") + description: qsTr("Bridge update is ready") + brief: description icon: "./icons/ic-info-circle-filled.svg" type: Notification.NotificationType.Info group: Notifications.Group.Update @@ -158,7 +161,8 @@ QtObject { } property Notification updateManualError: Notification { - text: qsTr("Bridge couldn’t update. Please update manually.") + description: qsTr("Bridge couldn’t update") + brief: description icon: "./icons/ic-exclamation-circle-filled.svg" type: Notification.NotificationType.Warning group: Notifications.Group.Update @@ -190,8 +194,9 @@ QtObject { } property Notification updateForce: Notification { - text: qsTr("Update to ProtonMail Bridge") + " " + (data ? data.version : "") + title: qsTr("Update to Bridge %1").arg(data ? data.version : "") description: qsTr("This version of Bridge is no longer supported, please update.") + brief: qsTr("Bridge is outdated") icon: "./icons/ic-exclamation-circle-filled.svg" type: Notification.NotificationType.Danger group: Notifications.Group.Update | Notifications.Group.Dialogs @@ -234,8 +239,9 @@ QtObject { } property Notification updateForceError: Notification { - text: qsTr("Bridge coudn’t update") - description: qsTr("You must update manually.") + title: qsTr("Bridge coudn’t update") + description: qsTr("You must update manually. Go to: https:/protonmail.com/bridge/download") + brief: title icon: "./icons/ic-exclamation-circle-filled.svg" type: Notification.NotificationType.Danger group: Notifications.Group.Update | Notifications.Group.Dialogs @@ -269,7 +275,8 @@ QtObject { } property Notification updateSilentRestartNeeded: Notification { - text: qsTr("Bridge update is ready") + description: qsTr("Bridge update is ready") + brief: description icon: "./icons/ic-info-circle-filled.svg" type: Notification.NotificationType.Info group: Notifications.Group.Update @@ -292,7 +299,8 @@ QtObject { } property Notification updateSilentError: Notification { - text: qsTr("Bridge couldn’t update") + description: qsTr("Bridge couldn’t update") + brief: description icon: "./icons/ic-exclamation-circle-filled.svg" type: Notification.NotificationType.Warning group: Notifications.Group.Update @@ -315,7 +323,7 @@ QtObject { } property Notification updateIsLatestVersion: Notification { - text: qsTr("Bridge is up to date") + description: qsTr("Bridge is up to date") icon: "./icons/ic-info-circle-filled.svg" type: Notification.NotificationType.Info group: Notifications.Group.Update @@ -337,7 +345,7 @@ QtObject { } property Notification enableBeta: Notification { - text: qsTr("Enable Beta access") + title: qsTr("Enable Beta access") description: qsTr("Be the first to get new updates and use new features. Bridge will update to the latest beta version.") icon: "./icons/ic-info-circle-filled.svg" type: Notification.NotificationType.Info @@ -370,7 +378,7 @@ QtObject { // login property Notification loginConnectionError: Notification { - text: qsTr("Bridge is not able to contact the server, please check your internet connection.") + description: qsTr("Bridge is not able to contact the server, please check your internet connection.") icon: "./icons/ic-exclamation-circle-filled.svg" type: Notification.NotificationType.Danger group: Notifications.Group.Configuration @@ -393,7 +401,7 @@ QtObject { } property Notification onlyPaidUsers: Notification { - text: qsTr("Bridge is exclusive to our paid plans. Upgrade your account to use Bridge.") + description: qsTr("Bridge is exclusive to our paid plans. Upgrade your account to use Bridge.") icon: "./icons/ic-exclamation-circle-filled.svg" type: Notification.NotificationType.Danger group: Notifications.Group.Configuration @@ -416,7 +424,7 @@ QtObject { } property Notification alreadyLoggedIn: Notification { - text: qsTr("This account is already signed it.") + description: qsTr("This account is already signed it.") icon: "./icons/ic-exclamation-circle-filled.svg" type: Notification.NotificationType.Info group: Notifications.Group.Configuration @@ -440,7 +448,7 @@ QtObject { // Bug reports property Notification bugReportSendSuccess: Notification { - text: qsTr("Thank you for the report. We'll get back to you as soon as we can.") + description: qsTr("Thank you for the report. We'll get back to you as soon as we can.") icon: "./icons/ic-info-circle-filled.svg" type: Notification.NotificationType.Success group: Notifications.Group.Configuration @@ -463,7 +471,7 @@ QtObject { } property Notification bugReportSendError: Notification { - text: qsTr("Report could not be sent. Try again or email us directly.") + description: qsTr("Report could not be sent. Try again or email us directly.") icon: "./icons/ic-exclamation-circle-filled.svg" type: Notification.NotificationType.Danger group: Notifications.Group.Configuration @@ -485,8 +493,9 @@ QtObject { // Cache property Notification cacheUnavailable: Notification { - text: qsTr("Cache location is unavailable") + title: qsTr("Cache location is unavailable") description: qsTr("Check the directory or change it in your settings.") + brief: qsTr("The current cache location is unavailable. Check the directory or change it in your settings.") type: Notification.NotificationType.Warning group: Notifications.Group.Configuration | Notifications.Group.Dialogs @@ -516,7 +525,7 @@ QtObject { } property Notification cacheCantMove: Notification { - text: qsTr("Can’t move cache") + title: qsTr("Can’t move cache") description: qsTr("The location you have selected is not available. Make sure you have enough free space or choose another location.") type: Notification.NotificationType.Warning group: Notifications.Group.Configuration | Notifications.Group.Dialogs @@ -546,7 +555,7 @@ QtObject { } property Notification cacheLocationChangeSuccess: Notification { - text: qsTr("Cache location successfully changed") + description: qsTr("Cache location successfully changed") icon: "./icons/ic-info-circle-filled.svg" type: Notification.NotificationType.Success group: Notifications.Group.Configuration @@ -571,7 +580,8 @@ QtObject { // Other property Notification accountChanged: Notification { - text: qsTr("The address list for your account has changed") + description: qsTr("The address list for .... account has changed. You need to reconfigure your email client.") + brief: qsTr("The address list for your account has changed. Reconfigure your email client.") icon: "./icons/ic-exclamation-circle-filled.svg" type: Notification.NotificationType.Danger group: Notifications.Group.Configuration @@ -586,8 +596,9 @@ QtObject { } property Notification diskFull: Notification { - text: qsTr("Your disk is almost full") + title: qsTr("Your disk is almost full") description: qsTr("Quit Bridge and free disk space or disable the local cache (not recommended).") + brief: qsTr("Your disk is almost full. Free disk space or disable the local cache.") type: Notification.NotificationType.Warning group: Notifications.Group.Configuration | Notifications.Group.Dialogs @@ -617,8 +628,8 @@ QtObject { } property Notification enableSplitMode: Notification { - text: qsTr("Enable split mode?") - description: qsTr("Changing between split and combined address mode will require you to delete your accounts(s) from your email client and begin the setup process from scratch.") + title: qsTr("Enable split mode?") + description: qsTr("Changing between split and combined address mode will require you to delete your account(s) from your email client and begin the setup process from scratch.") type: Notification.NotificationType.Warning group: Notifications.Group.Configuration | Notifications.Group.Dialogs @@ -632,7 +643,6 @@ QtObject { } } - Connections { target: (root && root.enableSplitMode && root.enableSplitMode.user ) ? root.enableSplitMode.user : null onToggleSplitModeFinished: { @@ -664,7 +674,7 @@ QtObject { } property Notification disableLocalCache: Notification { - text: qsTr("Disable local cache?") + title: qsTr("Disable local cache?") description: qsTr("This action will clear your local cache, including locally stored messages. Bridge will restart.") type: Notification.NotificationType.Warning group: Notifications.Group.Configuration | Notifications.Group.Dialogs @@ -676,7 +686,6 @@ QtObject { } } - Connections { target: root.backend onChangeLocalCacheFinished: { @@ -708,7 +717,7 @@ QtObject { } property Notification enableLocalCache: Notification { - text: qsTr("Enable local cache?") + title: qsTr("Enable local cache") description: qsTr("Bridge will restart.") type: Notification.NotificationType.Warning group: Notifications.Group.Configuration | Notifications.Group.Dialogs @@ -723,7 +732,6 @@ QtObject { } } - Connections { target: root.backend onChangeLocalCacheFinished: { @@ -755,7 +763,7 @@ QtObject { } property Notification resetBridge: Notification { - text: qsTr("Reset Bridge?") + title: qsTr("Reset Bridge?") description: qsTr("This will clear your accounts, preferences, and cached data. You will need to reconfigure your email client. Bridge will automatically restart.") type: Notification.NotificationType.Warning group: Notifications.Group.Configuration | Notifications.Group.Dialogs @@ -769,7 +777,6 @@ QtObject { } } - Connections { target: root.backend onResetFinished: { diff --git a/internal/frontend/qml/Status.qml b/internal/frontend/qml/Status.qml index 1418f0a..488a804 100644 --- a/internal/frontend/qml/Status.qml +++ b/internal/frontend/qml/Status.qml @@ -56,22 +56,22 @@ Item { } image.source = topmost.icon - label.text = topmost.text + label.text = topmost.brief switch (topmost.type) { - case Notification.NotificationType.Danger: + case Notification.NotificationType.Danger: image.color = root.colorScheme.signal_danger label.color = root.colorScheme.signal_danger break; - case Notification.NotificationType.Warning: + case Notification.NotificationType.Warning: image.color = root.colorScheme.signal_warning label.color = root.colorScheme.signal_warning break; - case Notification.NotificationType.Success: + case Notification.NotificationType.Success: image.color = root.colorScheme.signal_success label.color = root.colorScheme.signal_success break; - case Notification.NotificationType.Info: + case Notification.NotificationType.Info: image.color = root.colorScheme.signal_info label.color = root.colorScheme.signal_info break; diff --git a/internal/frontend/qml/StatusWindow.qml b/internal/frontend/qml/StatusWindow.qml index f4717c7..57d1e88 100644 --- a/internal/frontend/qml/StatusWindow.qml +++ b/internal/frontend/qml/StatusWindow.qml @@ -128,7 +128,7 @@ Window { Layout.topMargin: 12 Layout.bottomMargin: 12 - visible: (statusItem.activeNotification && statusItem.activeNotification.action) ? true : false + visible: statusItem.activeNotification && statusItem.activeNotification.action.length > 0 action: statusItem.activeNotification && statusItem.activeNotification.action.length > 0 ? statusItem.activeNotification.action[0] : null } } diff --git a/internal/frontend/qt/frontend_events.go b/internal/frontend/qt/frontend_events.go index 9d1897c..0b07e90 100644 --- a/internal/frontend/qt/frontend_events.go +++ b/internal/frontend/qt/frontend_events.go @@ -15,6 +15,7 @@ // You should have received a copy of the GNU General Public License // along with ProtonMail Bridge. If not, see . +//go:build build_qt // +build build_qt // Package qt provides communication between Qt/QML frontend and Go backend @@ -23,12 +24,18 @@ package qt import ( "strings" + "github.com/ProtonMail/proton-bridge/internal/bridge" "github.com/ProtonMail/proton-bridge/internal/events" ) func (f *FrontendQt) watchEvents() { f.WaitUntilFrontendIsReady() + // First we check bridge global errors for any error that should be shown on GUI. + if f.bridge.HasError(bridge.ErrLocalCacheUnavailable) { + f.qml.CacheUnavailable() + } + errorCh := f.eventListener.ProvideChannel(events.ErrorEvent) credentialsErrorCh := f.eventListener.ProvideChannel(events.CredentialsErrorEvent) noActiveKeyForRecipientCh := f.eventListener.ProvideChannel(events.NoActiveKeyForRecipientEvent) diff --git a/internal/frontend/types/types.go b/internal/frontend/types/types.go index 254b503..8a5690d 100644 --- a/internal/frontend/types/types.go +++ b/internal/frontend/types/types.go @@ -86,6 +86,7 @@ type Bridger interface { SetUpdateChannel(updater.UpdateChannel) GetKeychainApp() string SetKeychainApp(keychain string) + HasError(err error) bool } type bridgeWrap struct { diff --git a/internal/imap/backend.go b/internal/imap/backend.go index 8549305..7f72799 100644 --- a/internal/imap/backend.go +++ b/internal/imap/backend.go @@ -39,6 +39,7 @@ import ( "github.com/ProtonMail/proton-bridge/internal/bridge" "github.com/ProtonMail/proton-bridge/internal/config/settings" "github.com/ProtonMail/proton-bridge/internal/events" + "github.com/ProtonMail/proton-bridge/internal/users" "github.com/ProtonMail/proton-bridge/pkg/listener" "github.com/emersion/go-imap" goIMAPBackend "github.com/emersion/go-imap/backend" @@ -168,6 +169,10 @@ func (ib *imapBackend) Login(_ *imap.ConnInfo, username, password string) (goIMA // Called from go-imap in goroutines - we need to handle panics for each function. defer ib.panicHandler.HandlePanic() + if ib.bridge.HasError(bridge.ErrLocalCacheUnavailable) { + return nil, users.ErrLoggedOutUser + } + imapUser, err := ib.getUser(username) if err != nil { log.WithError(err).Warn("Cannot get user") diff --git a/internal/imap/bridge.go b/internal/imap/bridge.go index bd4580f..a98ec55 100644 --- a/internal/imap/bridge.go +++ b/internal/imap/bridge.go @@ -30,6 +30,7 @@ type cacheProvider interface { type bridger interface { GetUser(query string) (bridgeUser, error) + HasError(err error) bool } type bridgeUser interface { diff --git a/internal/smtp/backend.go b/internal/smtp/backend.go index 50f5a98..fa40324 100644 --- a/internal/smtp/backend.go +++ b/internal/smtp/backend.go @@ -23,6 +23,7 @@ import ( "github.com/ProtonMail/proton-bridge/internal/bridge" "github.com/ProtonMail/proton-bridge/internal/config/settings" + "github.com/ProtonMail/proton-bridge/internal/users" "github.com/ProtonMail/proton-bridge/pkg/confirmer" "github.com/ProtonMail/proton-bridge/pkg/listener" goSMTPBackend "github.com/emersion/go-smtp" @@ -77,6 +78,11 @@ func newSMTPBackend( func (sb *smtpBackend) Login(_ *goSMTPBackend.ConnectionState, username, password string) (goSMTPBackend.Session, error) { // Called from go-smtp in goroutines - we need to handle panics for each function. defer sb.panicHandler.HandlePanic() + + if sb.bridge.HasError(bridge.ErrLocalCacheUnavailable) { + return nil, users.ErrLoggedOutUser + } + username = strings.ToLower(username) user, err := sb.bridge.GetUser(username) diff --git a/internal/smtp/bridge.go b/internal/smtp/bridge.go index aa133c9..d616711 100644 --- a/internal/smtp/bridge.go +++ b/internal/smtp/bridge.go @@ -25,6 +25,7 @@ import ( type bridger interface { GetUser(query string) (bridgeUser, error) + HasError(err error) bool } type bridgeUser interface {