1
0

GODT-1381: Use in-memory cache in case local cache is unavailable

- change: refactor GUI notification object
- add: global bridge errors
- add: when cache on disk cannot be initialized fallback to memory cache
- add: show notification for CoD failure
- change: do not allow login to IMAP and SMTP when CoD init failed
This commit is contained in:
Alexander Bilyak
2021-11-17 13:19:37 +01:00
committed by Jakub
parent 5af3e930ec
commit b82e2ca176
15 changed files with 161 additions and 70 deletions

View File

@ -24,7 +24,7 @@ import (
"github.com/ProtonMail/proton-bridge/internal/api" "github.com/ProtonMail/proton-bridge/internal/api"
"github.com/ProtonMail/proton-bridge/internal/app/base" "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" "github.com/ProtonMail/proton-bridge/internal/config/settings"
pkgTLS "github.com/ProtonMail/proton-bridge/internal/config/tls" pkgTLS "github.com/ProtonMail/proton-bridge/internal/config/tls"
"github.com/ProtonMail/proton-bridge/internal/constants" "github.com/ProtonMail/proton-bridge/internal/constants"
@ -46,6 +46,10 @@ const (
flagLogSMTP = "log-smtp" flagLogSMTP = "log-smtp"
flagNoWindow = "no-window" flagNoWindow = "no-window"
flagNonInteractive = "noninteractive" 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 { func New(base *base.Base) *cli.App {
@ -75,9 +79,9 @@ func run(b *base.Base, c *cli.Context) error { // nolint[funlen]
return err return err
} }
cache, err := loadMessageCache(b) cache, cacheErr := loadMessageCache(b)
if err != nil { if cacheErr != nil {
return err logrus.WithError(cacheErr).Error("Could not load local cache.")
} }
builder := message.NewBuilder( builder := message.NewBuilder(
@ -85,10 +89,14 @@ func run(b *base.Base, c *cli.Context) error { // nolint[funlen]
b.Settings.GetInt(settings.AttachmentWorkers), 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) imapBackend := imap.NewIMAPBackend(b.CrashHandler, b.Listener, b.Cache, b.Settings, bridge)
smtpBackend := smtp.NewSMTPBackend(b.CrashHandler, b.Listener, b.Settings, bridge) smtpBackend := smtp.NewSMTPBackend(b.CrashHandler, b.Listener, b.Settings, bridge)
if cacheErr != nil {
bridge.AddError(pkgBridge.ErrLocalCacheUnavailable)
}
go func() { go func() {
defer b.CrashHandler.HandlePanic() defer b.CrashHandler.HandlePanic()
api.NewAPIServer(b.Settings, b.Listener).ListenAndServe() api.NewAPIServer(b.Settings, b.Listener).ListenAndServe()
@ -248,13 +256,12 @@ func checkAndHandleUpdate(u types.Updater, f frontend.Frontend, autoUpdate bool)
f.NotifySilentUpdateInstalled() 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) { func loadMessageCache(b *base.Base) (cache.Cache, error) {
if !b.Settings.GetBool(settings.CacheEnabledKey) { if !b.Settings.GetBool(settings.CacheEnabledKey) {
// Memory cache was estimated by empirical usage in past and it return cache.NewInMemoryCache(inMemoryCacheLimnit), nil
// was set to 100MB.
// NOTE: This value must not be less than maximal size of one
// email (~30MB).
return cache.NewInMemoryCache(100 << 20), nil
} }
var compressor cache.Compressor var compressor cache.Compressor
@ -283,10 +290,16 @@ func loadMessageCache(b *base.Base) (cache.Cache, error) {
// build jobs. // build jobs.
store.SetBuildAndCacheJobLimit(b.Settings.GetInt(settings.CacheConcurrencyWrite)) 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)), MinFreeAbs: uint64(b.Settings.GetInt(settings.CacheMinFreeAbsKey)),
MinFreeRat: b.Settings.GetFloat64(settings.CacheMinFreeRatKey), MinFreeRat: b.Settings.GetFloat64(settings.CacheMinFreeRatKey),
ConcurrentRead: b.Settings.GetInt(settings.CacheConcurrencyRead), ConcurrentRead: b.Settings.GetInt(settings.CacheConcurrencyRead),
ConcurrentWrite: b.Settings.GetInt(settings.CacheConcurrencyWrite), ConcurrentWrite: b.Settings.GetInt(settings.CacheConcurrencyWrite),
}) })
if err != nil {
return cache.NewInMemoryCache(inMemoryCacheLimnit), err
}
return messageCache, nil
} }

View File

@ -19,6 +19,7 @@
package bridge package bridge
import ( import (
"errors"
"fmt" "fmt"
"strconv" "strconv"
"time" "time"
@ -40,6 +41,8 @@ import (
var log = logrus.WithField("pkg", "bridge") //nolint[gochecknoglobals] var log = logrus.WithField("pkg", "bridge") //nolint[gochecknoglobals]
var ErrLocalCacheUnavailable = errors.New("local cache is unavailable")
type Bridge struct { type Bridge struct {
*users.Users *users.Users
@ -49,6 +52,8 @@ type Bridge struct {
updater Updater updater Updater
versioner Versioner versioner Versioner
cacheProvider CacheProvider cacheProvider CacheProvider
// Bridge's global errors list.
errors []error
} }
func New( func New(
@ -241,3 +246,36 @@ func (b *Bridge) SetProxyAllowed(proxyAllowed bool) {
func (b *Bridge) GetProxyAllowed() bool { func (b *Bridge) GetProxyAllowed() bool {
return b.settings.GetBool(settings.AllowProxyKey) 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
}

View File

@ -80,13 +80,13 @@ Popup {
} }
switch (root.notification.type) { switch (root.notification.type) {
case Notification.NotificationType.Info: case Notification.NotificationType.Info:
return root.colorScheme.signal_info return root.colorScheme.signal_info
case Notification.NotificationType.Success: case Notification.NotificationType.Success:
return root.colorScheme.signal_success return root.colorScheme.signal_success
case Notification.NotificationType.Warning: case Notification.NotificationType.Warning:
return root.colorScheme.signal_warning return root.colorScheme.signal_warning
case Notification.NotificationType.Danger: case Notification.NotificationType.Danger:
return root.colorScheme.signal_danger return root.colorScheme.signal_danger
} }
} }
@ -118,13 +118,13 @@ Popup {
} }
switch (root.notification.type) { switch (root.notification.type) {
case Notification.NotificationType.Info: case Notification.NotificationType.Info:
return "./icons/ic-info-circle-filled.svg" return "./icons/ic-info-circle-filled.svg"
case Notification.NotificationType.Success: case Notification.NotificationType.Success:
return "./icons/ic-info-circle-filled.svg" return "./icons/ic-info-circle-filled.svg"
case Notification.NotificationType.Warning: case Notification.NotificationType.Warning:
return "./icons/ic-exclamation-circle-filled.svg" return "./icons/ic-exclamation-circle-filled.svg"
case Notification.NotificationType.Danger: case Notification.NotificationType.Danger:
return "./icons/ic-exclamation-circle-filled.svg" return "./icons/ic-exclamation-circle-filled.svg"
} }
} }
@ -136,7 +136,7 @@ Popup {
Layout.alignment: Qt.AlignVCenter Layout.alignment: Qt.AlignVCenter
color: root.colorScheme.text_invert color: root.colorScheme.text_invert
text: root.notification ? root.notification.text : "" text: root.notification ? root.notification.description : ""
wrapMode: Text.WordWrap wrapMode: Text.WordWrap
} }
@ -152,13 +152,13 @@ Popup {
} }
switch (root.notification.type) { switch (root.notification.type) {
case Notification.NotificationType.Info: case Notification.NotificationType.Info:
return root.colorScheme.signal_info_active return root.colorScheme.signal_info_active
case Notification.NotificationType.Success: case Notification.NotificationType.Success:
return root.colorScheme.signal_success_active return root.colorScheme.signal_success_active
case Notification.NotificationType.Warning: case Notification.NotificationType.Warning:
return root.colorScheme.signal_warning_active return root.colorScheme.signal_warning_active
case Notification.NotificationType.Danger: case Notification.NotificationType.Danger:
return root.colorScheme.signal_danger_active return root.colorScheme.signal_danger_active
} }
} }
@ -190,22 +190,22 @@ Popup {
var active var active
switch (root.notification.type) { switch (root.notification.type) {
case Notification.NotificationType.Info: case Notification.NotificationType.Info:
norm = root.colorScheme.signal_info norm = root.colorScheme.signal_info
hover = root.colorScheme.signal_info_hover hover = root.colorScheme.signal_info_hover
active = root.colorScheme.signal_info_active active = root.colorScheme.signal_info_active
break; break;
case Notification.NotificationType.Success: case Notification.NotificationType.Success:
norm = root.colorScheme.signal_success norm = root.colorScheme.signal_success
hover = root.colorScheme.signal_success_hover hover = root.colorScheme.signal_success_hover
active = root.colorScheme.signal_success_active active = root.colorScheme.signal_success_active
break; break;
case Notification.NotificationType.Warning: case Notification.NotificationType.Warning:
norm = root.colorScheme.signal_warning norm = root.colorScheme.signal_warning
hover = root.colorScheme.signal_warning_hover hover = root.colorScheme.signal_warning_hover
active = root.colorScheme.signal_warning_active active = root.colorScheme.signal_warning_active
break; break;
case Notification.NotificationType.Danger: case Notification.NotificationType.Danger:
norm = root.colorScheme.signal_danger norm = root.colorScheme.signal_danger
hover = root.colorScheme.signal_danger_hover hover = root.colorScheme.signal_danger_hover
active = root.colorScheme.signal_danger_active active = root.colorScheme.signal_danger_active

View File

@ -52,6 +52,13 @@ QtObject {
onVisibleChanged: { onVisibleChanged: {
backend.dockIconVisible = visible backend.dockIconVisible = visible
} }
Connections {
target: root.backend
onCacheUnavailable: {
mainWindow.showAndRise()
}
}
} }
property StatusWindow _statusWindow: StatusWindow { property StatusWindow _statusWindow: StatusWindow {

View File

@ -55,12 +55,12 @@ Dialog {
} }
switch (root.notification.type) { switch (root.notification.type) {
case Notification.NotificationType.Info: case Notification.NotificationType.Info:
return "./icons/ic-info.svg" return "./icons/ic-info.svg"
case Notification.NotificationType.Success: case Notification.NotificationType.Success:
return "./icons/ic-success.svg" return "./icons/ic-success.svg"
case Notification.NotificationType.Warning: case Notification.NotificationType.Warning:
case Notification.NotificationType.Danger: case Notification.NotificationType.Danger:
return "./icons/ic-alert.svg" return "./icons/ic-alert.svg"
} }
} }
@ -70,7 +70,7 @@ Dialog {
Layout.alignment: Qt.AlignHCenter Layout.alignment: Qt.AlignHCenter
Layout.bottomMargin: 8 Layout.bottomMargin: 8
colorScheme: root.colorScheme colorScheme: root.colorScheme
text: root.notification.text text: root.notification.title
type: Label.LabelType.Title type: Label.LabelType.Title
} }

View File

@ -30,8 +30,13 @@ QtObject {
Danger = 3 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 property string description
// brief is used in status view only
property string brief
property string icon property string icon
property list<Action> action property list<Action> action
property int type property int type

View File

@ -74,7 +74,8 @@ QtObject {
// Connection // Connection
property Notification noInternet: Notification { 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" icon: "./icons/ic-no-connection.svg"
type: Notification.NotificationType.Danger type: Notification.NotificationType.Danger
group: Notifications.Group.Connection group: Notifications.Group.Connection
@ -93,8 +94,9 @@ QtObject {
// Updates // Updates
property Notification updateManualReady: Notification { 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.") 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" icon: "./icons/ic-info-circle-filled.svg"
type: Notification.NotificationType.Info type: Notification.NotificationType.Info
group: Notifications.Group.Update | Notifications.Group.Dialogs group: Notifications.Group.Update | Notifications.Group.Dialogs
@ -135,7 +137,8 @@ QtObject {
} }
property Notification updateManualRestartNeeded: Notification { 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" icon: "./icons/ic-info-circle-filled.svg"
type: Notification.NotificationType.Info type: Notification.NotificationType.Info
group: Notifications.Group.Update group: Notifications.Group.Update
@ -158,7 +161,8 @@ QtObject {
} }
property Notification updateManualError: Notification { property Notification updateManualError: Notification {
text: qsTr("Bridge couldnt update. Please update manually.") description: qsTr("Bridge couldnt update")
brief: description
icon: "./icons/ic-exclamation-circle-filled.svg" icon: "./icons/ic-exclamation-circle-filled.svg"
type: Notification.NotificationType.Warning type: Notification.NotificationType.Warning
group: Notifications.Group.Update group: Notifications.Group.Update
@ -190,8 +194,9 @@ QtObject {
} }
property Notification updateForce: Notification { 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.") description: qsTr("This version of Bridge is no longer supported, please update.")
brief: qsTr("Bridge is outdated")
icon: "./icons/ic-exclamation-circle-filled.svg" icon: "./icons/ic-exclamation-circle-filled.svg"
type: Notification.NotificationType.Danger type: Notification.NotificationType.Danger
group: Notifications.Group.Update | Notifications.Group.Dialogs group: Notifications.Group.Update | Notifications.Group.Dialogs
@ -234,8 +239,9 @@ QtObject {
} }
property Notification updateForceError: Notification { property Notification updateForceError: Notification {
text: qsTr("Bridge coudnt update") title: qsTr("Bridge coudnt update")
description: qsTr("You must update manually.") description: qsTr("You must update manually. Go to: https:/protonmail.com/bridge/download")
brief: title
icon: "./icons/ic-exclamation-circle-filled.svg" icon: "./icons/ic-exclamation-circle-filled.svg"
type: Notification.NotificationType.Danger type: Notification.NotificationType.Danger
group: Notifications.Group.Update | Notifications.Group.Dialogs group: Notifications.Group.Update | Notifications.Group.Dialogs
@ -269,7 +275,8 @@ QtObject {
} }
property Notification updateSilentRestartNeeded: Notification { 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" icon: "./icons/ic-info-circle-filled.svg"
type: Notification.NotificationType.Info type: Notification.NotificationType.Info
group: Notifications.Group.Update group: Notifications.Group.Update
@ -292,7 +299,8 @@ QtObject {
} }
property Notification updateSilentError: Notification { property Notification updateSilentError: Notification {
text: qsTr("Bridge couldnt update") description: qsTr("Bridge couldnt update")
brief: description
icon: "./icons/ic-exclamation-circle-filled.svg" icon: "./icons/ic-exclamation-circle-filled.svg"
type: Notification.NotificationType.Warning type: Notification.NotificationType.Warning
group: Notifications.Group.Update group: Notifications.Group.Update
@ -315,7 +323,7 @@ QtObject {
} }
property Notification updateIsLatestVersion: Notification { 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" icon: "./icons/ic-info-circle-filled.svg"
type: Notification.NotificationType.Info type: Notification.NotificationType.Info
group: Notifications.Group.Update group: Notifications.Group.Update
@ -337,7 +345,7 @@ QtObject {
} }
property Notification enableBeta: Notification { 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.") 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" icon: "./icons/ic-info-circle-filled.svg"
type: Notification.NotificationType.Info type: Notification.NotificationType.Info
@ -370,7 +378,7 @@ QtObject {
// login // login
property Notification loginConnectionError: Notification { 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" icon: "./icons/ic-exclamation-circle-filled.svg"
type: Notification.NotificationType.Danger type: Notification.NotificationType.Danger
group: Notifications.Group.Configuration group: Notifications.Group.Configuration
@ -393,7 +401,7 @@ QtObject {
} }
property Notification onlyPaidUsers: Notification { 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" icon: "./icons/ic-exclamation-circle-filled.svg"
type: Notification.NotificationType.Danger type: Notification.NotificationType.Danger
group: Notifications.Group.Configuration group: Notifications.Group.Configuration
@ -416,7 +424,7 @@ QtObject {
} }
property Notification alreadyLoggedIn: Notification { 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" icon: "./icons/ic-exclamation-circle-filled.svg"
type: Notification.NotificationType.Info type: Notification.NotificationType.Info
group: Notifications.Group.Configuration group: Notifications.Group.Configuration
@ -440,7 +448,7 @@ QtObject {
// Bug reports // Bug reports
property Notification bugReportSendSuccess: Notification { 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" icon: "./icons/ic-info-circle-filled.svg"
type: Notification.NotificationType.Success type: Notification.NotificationType.Success
group: Notifications.Group.Configuration group: Notifications.Group.Configuration
@ -463,7 +471,7 @@ QtObject {
} }
property Notification bugReportSendError: Notification { 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" icon: "./icons/ic-exclamation-circle-filled.svg"
type: Notification.NotificationType.Danger type: Notification.NotificationType.Danger
group: Notifications.Group.Configuration group: Notifications.Group.Configuration
@ -485,8 +493,9 @@ QtObject {
// Cache // Cache
property Notification cacheUnavailable: Notification { 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.") 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 type: Notification.NotificationType.Warning
group: Notifications.Group.Configuration | Notifications.Group.Dialogs group: Notifications.Group.Configuration | Notifications.Group.Dialogs
@ -516,7 +525,7 @@ QtObject {
} }
property Notification cacheCantMove: Notification { property Notification cacheCantMove: Notification {
text: qsTr("Cant move cache") title: qsTr("Cant move cache")
description: qsTr("The location you have selected is not available. Make sure you have enough free space or choose another location.") 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 type: Notification.NotificationType.Warning
group: Notifications.Group.Configuration | Notifications.Group.Dialogs group: Notifications.Group.Configuration | Notifications.Group.Dialogs
@ -546,7 +555,7 @@ QtObject {
} }
property Notification cacheLocationChangeSuccess: Notification { property Notification cacheLocationChangeSuccess: Notification {
text: qsTr("Cache location successfully changed") description: qsTr("Cache location successfully changed")
icon: "./icons/ic-info-circle-filled.svg" icon: "./icons/ic-info-circle-filled.svg"
type: Notification.NotificationType.Success type: Notification.NotificationType.Success
group: Notifications.Group.Configuration group: Notifications.Group.Configuration
@ -571,7 +580,8 @@ QtObject {
// Other // Other
property Notification accountChanged: Notification { 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" icon: "./icons/ic-exclamation-circle-filled.svg"
type: Notification.NotificationType.Danger type: Notification.NotificationType.Danger
group: Notifications.Group.Configuration group: Notifications.Group.Configuration
@ -586,8 +596,9 @@ QtObject {
} }
property Notification diskFull: Notification { 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).") 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 type: Notification.NotificationType.Warning
group: Notifications.Group.Configuration | Notifications.Group.Dialogs group: Notifications.Group.Configuration | Notifications.Group.Dialogs
@ -617,8 +628,8 @@ QtObject {
} }
property Notification enableSplitMode: Notification { property Notification enableSplitMode: Notification {
text: qsTr("Enable split mode?") title: 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.") 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 type: Notification.NotificationType.Warning
group: Notifications.Group.Configuration | Notifications.Group.Dialogs group: Notifications.Group.Configuration | Notifications.Group.Dialogs
@ -632,7 +643,6 @@ QtObject {
} }
} }
Connections { Connections {
target: (root && root.enableSplitMode && root.enableSplitMode.user ) ? root.enableSplitMode.user : null target: (root && root.enableSplitMode && root.enableSplitMode.user ) ? root.enableSplitMode.user : null
onToggleSplitModeFinished: { onToggleSplitModeFinished: {
@ -664,7 +674,7 @@ QtObject {
} }
property Notification disableLocalCache: Notification { 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.") description: qsTr("This action will clear your local cache, including locally stored messages. Bridge will restart.")
type: Notification.NotificationType.Warning type: Notification.NotificationType.Warning
group: Notifications.Group.Configuration | Notifications.Group.Dialogs group: Notifications.Group.Configuration | Notifications.Group.Dialogs
@ -676,7 +686,6 @@ QtObject {
} }
} }
Connections { Connections {
target: root.backend target: root.backend
onChangeLocalCacheFinished: { onChangeLocalCacheFinished: {
@ -708,7 +717,7 @@ QtObject {
} }
property Notification enableLocalCache: Notification { property Notification enableLocalCache: Notification {
text: qsTr("Enable local cache?") title: qsTr("Enable local cache")
description: qsTr("Bridge will restart.") description: qsTr("Bridge will restart.")
type: Notification.NotificationType.Warning type: Notification.NotificationType.Warning
group: Notifications.Group.Configuration | Notifications.Group.Dialogs group: Notifications.Group.Configuration | Notifications.Group.Dialogs
@ -723,7 +732,6 @@ QtObject {
} }
} }
Connections { Connections {
target: root.backend target: root.backend
onChangeLocalCacheFinished: { onChangeLocalCacheFinished: {
@ -755,7 +763,7 @@ QtObject {
} }
property Notification resetBridge: Notification { 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.") 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 type: Notification.NotificationType.Warning
group: Notifications.Group.Configuration | Notifications.Group.Dialogs group: Notifications.Group.Configuration | Notifications.Group.Dialogs
@ -769,7 +777,6 @@ QtObject {
} }
} }
Connections { Connections {
target: root.backend target: root.backend
onResetFinished: { onResetFinished: {

View File

@ -56,22 +56,22 @@ Item {
} }
image.source = topmost.icon image.source = topmost.icon
label.text = topmost.text label.text = topmost.brief
switch (topmost.type) { switch (topmost.type) {
case Notification.NotificationType.Danger: case Notification.NotificationType.Danger:
image.color = root.colorScheme.signal_danger image.color = root.colorScheme.signal_danger
label.color = root.colorScheme.signal_danger label.color = root.colorScheme.signal_danger
break; break;
case Notification.NotificationType.Warning: case Notification.NotificationType.Warning:
image.color = root.colorScheme.signal_warning image.color = root.colorScheme.signal_warning
label.color = root.colorScheme.signal_warning label.color = root.colorScheme.signal_warning
break; break;
case Notification.NotificationType.Success: case Notification.NotificationType.Success:
image.color = root.colorScheme.signal_success image.color = root.colorScheme.signal_success
label.color = root.colorScheme.signal_success label.color = root.colorScheme.signal_success
break; break;
case Notification.NotificationType.Info: case Notification.NotificationType.Info:
image.color = root.colorScheme.signal_info image.color = root.colorScheme.signal_info
label.color = root.colorScheme.signal_info label.color = root.colorScheme.signal_info
break; break;

View File

@ -128,7 +128,7 @@ Window {
Layout.topMargin: 12 Layout.topMargin: 12
Layout.bottomMargin: 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 action: statusItem.activeNotification && statusItem.activeNotification.action.length > 0 ? statusItem.activeNotification.action[0] : null
} }
} }

View File

@ -15,6 +15,7 @@
// You should have received a copy of the GNU General Public License // You should have received a copy of the GNU General Public License
// along with ProtonMail Bridge. If not, see <https://www.gnu.org/licenses/>. // along with ProtonMail Bridge. If not, see <https://www.gnu.org/licenses/>.
//go:build build_qt
// +build build_qt // +build build_qt
// Package qt provides communication between Qt/QML frontend and Go backend // Package qt provides communication between Qt/QML frontend and Go backend
@ -23,12 +24,18 @@ package qt
import ( import (
"strings" "strings"
"github.com/ProtonMail/proton-bridge/internal/bridge"
"github.com/ProtonMail/proton-bridge/internal/events" "github.com/ProtonMail/proton-bridge/internal/events"
) )
func (f *FrontendQt) watchEvents() { func (f *FrontendQt) watchEvents() {
f.WaitUntilFrontendIsReady() 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) errorCh := f.eventListener.ProvideChannel(events.ErrorEvent)
credentialsErrorCh := f.eventListener.ProvideChannel(events.CredentialsErrorEvent) credentialsErrorCh := f.eventListener.ProvideChannel(events.CredentialsErrorEvent)
noActiveKeyForRecipientCh := f.eventListener.ProvideChannel(events.NoActiveKeyForRecipientEvent) noActiveKeyForRecipientCh := f.eventListener.ProvideChannel(events.NoActiveKeyForRecipientEvent)

View File

@ -86,6 +86,7 @@ type Bridger interface {
SetUpdateChannel(updater.UpdateChannel) SetUpdateChannel(updater.UpdateChannel)
GetKeychainApp() string GetKeychainApp() string
SetKeychainApp(keychain string) SetKeychainApp(keychain string)
HasError(err error) bool
} }
type bridgeWrap struct { type bridgeWrap struct {

View File

@ -39,6 +39,7 @@ import (
"github.com/ProtonMail/proton-bridge/internal/bridge" "github.com/ProtonMail/proton-bridge/internal/bridge"
"github.com/ProtonMail/proton-bridge/internal/config/settings" "github.com/ProtonMail/proton-bridge/internal/config/settings"
"github.com/ProtonMail/proton-bridge/internal/events" "github.com/ProtonMail/proton-bridge/internal/events"
"github.com/ProtonMail/proton-bridge/internal/users"
"github.com/ProtonMail/proton-bridge/pkg/listener" "github.com/ProtonMail/proton-bridge/pkg/listener"
"github.com/emersion/go-imap" "github.com/emersion/go-imap"
goIMAPBackend "github.com/emersion/go-imap/backend" 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. // Called from go-imap in goroutines - we need to handle panics for each function.
defer ib.panicHandler.HandlePanic() defer ib.panicHandler.HandlePanic()
if ib.bridge.HasError(bridge.ErrLocalCacheUnavailable) {
return nil, users.ErrLoggedOutUser
}
imapUser, err := ib.getUser(username) imapUser, err := ib.getUser(username)
if err != nil { if err != nil {
log.WithError(err).Warn("Cannot get user") log.WithError(err).Warn("Cannot get user")

View File

@ -30,6 +30,7 @@ type cacheProvider interface {
type bridger interface { type bridger interface {
GetUser(query string) (bridgeUser, error) GetUser(query string) (bridgeUser, error)
HasError(err error) bool
} }
type bridgeUser interface { type bridgeUser interface {

View File

@ -23,6 +23,7 @@ import (
"github.com/ProtonMail/proton-bridge/internal/bridge" "github.com/ProtonMail/proton-bridge/internal/bridge"
"github.com/ProtonMail/proton-bridge/internal/config/settings" "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/confirmer"
"github.com/ProtonMail/proton-bridge/pkg/listener" "github.com/ProtonMail/proton-bridge/pkg/listener"
goSMTPBackend "github.com/emersion/go-smtp" goSMTPBackend "github.com/emersion/go-smtp"
@ -77,6 +78,11 @@ func newSMTPBackend(
func (sb *smtpBackend) Login(_ *goSMTPBackend.ConnectionState, username, password string) (goSMTPBackend.Session, error) { 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. // Called from go-smtp in goroutines - we need to handle panics for each function.
defer sb.panicHandler.HandlePanic() defer sb.panicHandler.HandlePanic()
if sb.bridge.HasError(bridge.ErrLocalCacheUnavailable) {
return nil, users.ErrLoggedOutUser
}
username = strings.ToLower(username) username = strings.ToLower(username)
user, err := sb.bridge.GetUser(username) user, err := sb.bridge.GetUser(username)

View File

@ -25,6 +25,7 @@ import (
type bridger interface { type bridger interface {
GetUser(query string) (bridgeUser, error) GetUser(query string) (bridgeUser, error)
HasError(err error) bool
} }
type bridgeUser interface { type bridgeUser interface {