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/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
}

View File

@ -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
}

View File

@ -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

View File

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

View File

@ -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
}

View File

@ -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> action
property int type

View File

@ -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 couldnt update. Please update manually.")
description: qsTr("Bridge couldnt 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 coudnt update")
description: qsTr("You must update manually.")
title: qsTr("Bridge coudnt 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 couldnt update")
description: qsTr("Bridge couldnt 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("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.")
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: {

View File

@ -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;

View File

@ -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
}
}

View File

@ -15,6 +15,7 @@
// You should have received a copy of the GNU General Public License
// along with ProtonMail Bridge. If not, see <https://www.gnu.org/licenses/>.
//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)

View File

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

View File

@ -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")

View File

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

View File

@ -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)

View File

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