forked from Silverfish/proton-bridge
GODT-1779: Remove go-imap
This commit is contained in:
@ -22,7 +22,7 @@ import (
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/ProtonMail/proton-bridge/v2/internal/users"
|
||||
"github.com/ProtonMail/proton-bridge/v2/internal/bridge"
|
||||
"github.com/abiosoft/ishell"
|
||||
)
|
||||
|
||||
@ -35,6 +35,7 @@ func (f *frontendCLI) completeUsernames(args []string) (usernames []string) {
|
||||
if len(args) == 1 {
|
||||
arg = args[0]
|
||||
}
|
||||
|
||||
for _, userID := range f.bridge.GetUserIDs() {
|
||||
user, err := f.bridge.GetUserInfo(userID)
|
||||
if err != nil {
|
||||
@ -50,8 +51,7 @@ func (f *frontendCLI) completeUsernames(args []string) (usernames []string) {
|
||||
// noAccountWrapper is a decorator for functions which need any account to be properly functional.
|
||||
func (f *frontendCLI) noAccountWrapper(callback func(*ishell.Context)) func(*ishell.Context) {
|
||||
return func(c *ishell.Context) {
|
||||
users := f.bridge.GetUserIDs()
|
||||
if len(users) == 0 {
|
||||
if len(f.bridge.GetUserIDs()) == 0 {
|
||||
f.Println("No active accounts. Please add account to continue.")
|
||||
} else {
|
||||
callback(c)
|
||||
@ -59,9 +59,9 @@ func (f *frontendCLI) noAccountWrapper(callback func(*ishell.Context)) func(*ish
|
||||
}
|
||||
}
|
||||
|
||||
func (f *frontendCLI) askUserByIndexOrName(c *ishell.Context) users.UserInfo {
|
||||
func (f *frontendCLI) askUserByIndexOrName(c *ishell.Context) bridge.UserInfo {
|
||||
user := f.getUserByIndexOrName("")
|
||||
if user.ID != "" {
|
||||
if user.UserID != "" {
|
||||
return user
|
||||
}
|
||||
|
||||
@ -69,24 +69,24 @@ func (f *frontendCLI) askUserByIndexOrName(c *ishell.Context) users.UserInfo {
|
||||
indexRange := fmt.Sprintf("number between 0 and %d", numberOfAccounts-1)
|
||||
if len(c.Args) == 0 {
|
||||
f.Printf("Please choose %s or username.\n", indexRange)
|
||||
return users.UserInfo{}
|
||||
return bridge.UserInfo{}
|
||||
}
|
||||
arg := c.Args[0]
|
||||
user = f.getUserByIndexOrName(arg)
|
||||
if user.ID == "" {
|
||||
if user.UserID == "" {
|
||||
f.Printf("Wrong input '%s'. Choose %s or username.\n", bold(arg), indexRange)
|
||||
return users.UserInfo{}
|
||||
return bridge.UserInfo{}
|
||||
}
|
||||
return user
|
||||
}
|
||||
|
||||
func (f *frontendCLI) getUserByIndexOrName(arg string) users.UserInfo {
|
||||
func (f *frontendCLI) getUserByIndexOrName(arg string) bridge.UserInfo {
|
||||
userIDs := f.bridge.GetUserIDs()
|
||||
numberOfAccounts := len(userIDs)
|
||||
if numberOfAccounts == 0 {
|
||||
return users.UserInfo{}
|
||||
return bridge.UserInfo{}
|
||||
}
|
||||
res := make([]users.UserInfo, len(userIDs))
|
||||
res := make([]bridge.UserInfo, len(userIDs))
|
||||
for idx, userID := range userIDs {
|
||||
user, err := f.bridge.GetUserInfo(userID)
|
||||
if err != nil {
|
||||
@ -99,7 +99,7 @@ func (f *frontendCLI) getUserByIndexOrName(arg string) users.UserInfo {
|
||||
}
|
||||
if index, err := strconv.Atoi(arg); err == nil {
|
||||
if index < 0 || index >= numberOfAccounts {
|
||||
return users.UserInfo{}
|
||||
return bridge.UserInfo{}
|
||||
}
|
||||
return res[index]
|
||||
}
|
||||
@ -108,5 +108,5 @@ func (f *frontendCLI) getUserByIndexOrName(arg string) users.UserInfo {
|
||||
return user
|
||||
}
|
||||
}
|
||||
return users.UserInfo{}
|
||||
return bridge.UserInfo{}
|
||||
}
|
||||
|
||||
@ -22,8 +22,7 @@ import (
|
||||
"strings"
|
||||
|
||||
"github.com/ProtonMail/proton-bridge/v2/internal/bridge"
|
||||
"github.com/ProtonMail/proton-bridge/v2/internal/config/settings"
|
||||
"github.com/ProtonMail/proton-bridge/v2/internal/users"
|
||||
"github.com/ProtonMail/proton-bridge/v2/internal/constants"
|
||||
"github.com/abiosoft/ishell"
|
||||
)
|
||||
|
||||
@ -40,7 +39,7 @@ func (f *frontendCLI) listAccounts(c *ishell.Context) {
|
||||
connected = "connected"
|
||||
}
|
||||
mode := "split"
|
||||
if user.Mode == users.CombinedMode {
|
||||
if user.AddressMode == bridge.CombinedMode {
|
||||
mode = "combined"
|
||||
}
|
||||
f.Printf(spacing, idx, user.Username, connected, mode)
|
||||
@ -50,7 +49,7 @@ func (f *frontendCLI) listAccounts(c *ishell.Context) {
|
||||
|
||||
func (f *frontendCLI) showAccountInfo(c *ishell.Context) {
|
||||
user := f.askUserByIndexOrName(c)
|
||||
if user.ID == "" {
|
||||
if user.UserID == "" {
|
||||
return
|
||||
}
|
||||
|
||||
@ -59,8 +58,8 @@ func (f *frontendCLI) showAccountInfo(c *ishell.Context) {
|
||||
return
|
||||
}
|
||||
|
||||
if user.Mode == users.CombinedMode {
|
||||
f.showAccountAddressInfo(user, user.Addresses[user.Primary])
|
||||
if user.AddressMode == bridge.CombinedMode {
|
||||
f.showAccountAddressInfo(user, user.Addresses[0])
|
||||
} else {
|
||||
for _, address := range user.Addresses {
|
||||
f.showAccountAddressInfo(user, address)
|
||||
@ -68,25 +67,31 @@ func (f *frontendCLI) showAccountInfo(c *ishell.Context) {
|
||||
}
|
||||
}
|
||||
|
||||
func (f *frontendCLI) showAccountAddressInfo(user users.UserInfo, address string) {
|
||||
func (f *frontendCLI) showAccountAddressInfo(user bridge.UserInfo, address string) {
|
||||
imapSecurity := "STARTTLS"
|
||||
if f.bridge.GetIMAPSSL() {
|
||||
imapSecurity = "SSL"
|
||||
}
|
||||
|
||||
smtpSecurity := "STARTTLS"
|
||||
if f.bridge.GetBool(settings.SMTPSSLKey) {
|
||||
if f.bridge.GetSMTPSSL() {
|
||||
smtpSecurity = "SSL"
|
||||
}
|
||||
|
||||
f.Println(bold("Configuration for " + address))
|
||||
f.Printf("IMAP Settings\nAddress: %s\nIMAP port: %d\nUsername: %s\nPassword: %s\nSecurity: %s\n",
|
||||
bridge.Host,
|
||||
f.bridge.GetInt(settings.IMAPPortKey),
|
||||
constants.Host,
|
||||
f.bridge.GetIMAPPort(),
|
||||
address,
|
||||
user.Password,
|
||||
"STARTTLS",
|
||||
user.BridgePass,
|
||||
imapSecurity,
|
||||
)
|
||||
f.Println("")
|
||||
f.Printf("SMTP Settings\nAddress: %s\nSMTP port: %d\nUsername: %s\nPassword: %s\nSecurity: %s\n",
|
||||
bridge.Host,
|
||||
f.bridge.GetInt(settings.SMTPPortKey),
|
||||
constants.Host,
|
||||
f.bridge.GetSMTPPort(),
|
||||
address,
|
||||
user.Password,
|
||||
user.BridgePass,
|
||||
smtpSecurity,
|
||||
)
|
||||
f.Println("")
|
||||
@ -99,8 +104,8 @@ func (f *frontendCLI) loginAccount(c *ishell.Context) { //nolint:funlen
|
||||
loginName := ""
|
||||
if len(c.Args) > 0 {
|
||||
user := f.getUserByIndexOrName(c.Args[0])
|
||||
if user.ID != "" {
|
||||
loginName = user.Addresses[user.Primary]
|
||||
if user.UserID != "" {
|
||||
loginName = user.Addresses[0]
|
||||
}
|
||||
}
|
||||
|
||||
@ -119,41 +124,23 @@ func (f *frontendCLI) loginAccount(c *ishell.Context) { //nolint:funlen
|
||||
}
|
||||
|
||||
f.Println("Authenticating ... ")
|
||||
client, auth, err := f.bridge.Login(loginName, []byte(password))
|
||||
|
||||
userID, err := f.bridge.LoginUser(
|
||||
context.Background(),
|
||||
loginName,
|
||||
password,
|
||||
func() (string, error) {
|
||||
return f.readStringInAttempts("Two factor code", c.ReadLine, isNotEmpty), nil
|
||||
},
|
||||
func() ([]byte, error) {
|
||||
return []byte(f.readStringInAttempts("Mailbox password", c.ReadPassword, isNotEmpty)), nil
|
||||
},
|
||||
)
|
||||
if err != nil {
|
||||
f.processAPIError(err)
|
||||
return
|
||||
}
|
||||
|
||||
if auth.HasTwoFactor() {
|
||||
twoFactor := f.readStringInAttempts("Two factor code", c.ReadLine, isNotEmpty)
|
||||
if twoFactor == "" {
|
||||
return
|
||||
}
|
||||
|
||||
err = client.Auth2FA(context.Background(), twoFactor)
|
||||
if err != nil {
|
||||
f.processAPIError(err)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
mailboxPassword := password
|
||||
if auth.HasMailboxPassword() {
|
||||
mailboxPassword = f.readStringInAttempts("Mailbox password", c.ReadPassword, isNotEmpty)
|
||||
}
|
||||
if mailboxPassword == "" {
|
||||
return
|
||||
}
|
||||
|
||||
f.Println("Adding account ...")
|
||||
userID, err := f.bridge.FinishLogin(client, auth, []byte(mailboxPassword))
|
||||
if err != nil {
|
||||
log.WithField("username", loginName).WithError(err).Error("Login was unsuccessful")
|
||||
f.Println("Adding account was unsuccessful:", err)
|
||||
return
|
||||
}
|
||||
|
||||
user, err := f.bridge.GetUserInfo(userID)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
@ -167,11 +154,12 @@ func (f *frontendCLI) logoutAccount(c *ishell.Context) {
|
||||
defer f.ShowPrompt(true)
|
||||
|
||||
user := f.askUserByIndexOrName(c)
|
||||
if user.ID == "" {
|
||||
if user.UserID == "" {
|
||||
return
|
||||
}
|
||||
|
||||
if f.yesNoQuestion("Are you sure you want to logout account " + bold(user.Username)) {
|
||||
if err := f.bridge.LogoutUser(user.ID); err != nil {
|
||||
if err := f.bridge.LogoutUser(context.Background(), user.UserID); err != nil {
|
||||
f.printAndLogError("Logging out failed: ", err)
|
||||
}
|
||||
}
|
||||
@ -182,12 +170,12 @@ func (f *frontendCLI) deleteAccount(c *ishell.Context) {
|
||||
defer f.ShowPrompt(true)
|
||||
|
||||
user := f.askUserByIndexOrName(c)
|
||||
if user.ID == "" {
|
||||
if user.UserID == "" {
|
||||
return
|
||||
}
|
||||
|
||||
if f.yesNoQuestion("Are you sure you want to " + bold("remove account "+user.Username)) {
|
||||
clearCache := f.yesNoQuestion("Do you want to remove cache for this account")
|
||||
if err := f.bridge.DeleteUser(user.ID, clearCache); err != nil {
|
||||
if err := f.bridge.DeleteUser(context.Background(), user.UserID); err != nil {
|
||||
f.printAndLogError("Cannot delete account: ", err)
|
||||
return
|
||||
}
|
||||
@ -205,10 +193,13 @@ func (f *frontendCLI) deleteAccounts(c *ishell.Context) {
|
||||
for _, userID := range f.bridge.GetUserIDs() {
|
||||
user, err := f.bridge.GetUserInfo(userID)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
f.printAndLogError("Cannot get user info: ", err)
|
||||
return
|
||||
}
|
||||
if err := f.bridge.DeleteUser(user.ID, false); err != nil {
|
||||
|
||||
if err := f.bridge.DeleteUser(context.Background(), user.UserID); err != nil {
|
||||
f.printAndLogError("Cannot delete account ", user.Username, ": ", err)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
@ -223,37 +214,50 @@ func (f *frontendCLI) deleteEverything(c *ishell.Context) {
|
||||
return
|
||||
}
|
||||
|
||||
f.bridge.FactoryReset()
|
||||
f.bridge.FactoryReset(context.Background())
|
||||
|
||||
c.Println("Everything cleared")
|
||||
|
||||
// Clearing data removes everything (db, preferences, ...) so everything has to be stopped and started again.
|
||||
f.restarter.SetToRestart()
|
||||
|
||||
f.Stop()
|
||||
}
|
||||
|
||||
func (f *frontendCLI) changeMode(c *ishell.Context) {
|
||||
user := f.askUserByIndexOrName(c)
|
||||
if user.ID == "" {
|
||||
if user.UserID == "" {
|
||||
return
|
||||
}
|
||||
|
||||
var targetMode users.AddressMode
|
||||
var targetMode bridge.AddressMode
|
||||
|
||||
if user.Mode == users.CombinedMode {
|
||||
targetMode = users.SplitMode
|
||||
if user.AddressMode == bridge.CombinedMode {
|
||||
targetMode = bridge.SplitMode
|
||||
} else {
|
||||
targetMode = users.CombinedMode
|
||||
targetMode = bridge.CombinedMode
|
||||
}
|
||||
|
||||
if !f.yesNoQuestion("Are you sure you want to change the mode for account " + bold(user.Username) + " to " + bold(targetMode)) {
|
||||
return
|
||||
}
|
||||
|
||||
if err := f.bridge.SetAddressMode(user.ID, targetMode); err != nil {
|
||||
if err := f.bridge.SetAddressMode(user.UserID, targetMode); err != nil {
|
||||
f.printAndLogError("Cannot switch address mode:", err)
|
||||
}
|
||||
|
||||
f.Printf("Address mode for account %s changed to %s\n", user.Username, targetMode)
|
||||
}
|
||||
|
||||
func (f *frontendCLI) configureAppleMail(c *ishell.Context) {
|
||||
user := f.askUserByIndexOrName(c)
|
||||
if user.UserID == "" {
|
||||
return
|
||||
}
|
||||
|
||||
if !f.yesNoQuestion("Are you sure you want to configure Apple Mail for " + bold(user.Username) + " with address " + bold(user.Addresses[0])) {
|
||||
return
|
||||
}
|
||||
|
||||
if err := f.bridge.ConfigureAppleMail(user.UserID, user.Addresses[0]); err != nil {
|
||||
f.printAndLogError(err)
|
||||
return
|
||||
}
|
||||
|
||||
f.Printf("Apple Mail configured for %v with address %v\n", user.Username, user.Addresses[0])
|
||||
}
|
||||
|
||||
@ -19,11 +19,13 @@
|
||||
package cli
|
||||
|
||||
import (
|
||||
"errors"
|
||||
|
||||
"github.com/ProtonMail/proton-bridge/v2/internal/bridge"
|
||||
"github.com/ProtonMail/proton-bridge/v2/internal/constants"
|
||||
"github.com/ProtonMail/proton-bridge/v2/internal/events"
|
||||
"github.com/ProtonMail/proton-bridge/v2/internal/frontend/types"
|
||||
"github.com/ProtonMail/proton-bridge/v2/internal/updater"
|
||||
"github.com/ProtonMail/proton-bridge/v2/pkg/listener"
|
||||
"github.com/ProtonMail/proton-bridge/v2/internal/vault"
|
||||
"gitlab.protontech.ch/go/liteapi"
|
||||
|
||||
"github.com/abiosoft/ishell"
|
||||
"github.com/sirupsen/logrus"
|
||||
@ -34,30 +36,14 @@ var log = logrus.WithField("pkg", "frontend/cli") //nolint:gochecknoglobals
|
||||
type frontendCLI struct {
|
||||
*ishell.Shell
|
||||
|
||||
eventListener listener.Listener
|
||||
updater types.Updater
|
||||
bridge types.Bridger
|
||||
|
||||
restarter types.Restarter
|
||||
bridge *bridge.Bridge
|
||||
}
|
||||
|
||||
// New returns a new CLI frontend configured with the given options.
|
||||
func New( //nolint:funlen
|
||||
panicHandler types.PanicHandler,
|
||||
|
||||
eventListener listener.Listener,
|
||||
updater types.Updater,
|
||||
bridge types.Bridger,
|
||||
restarter types.Restarter,
|
||||
) *frontendCLI { //nolint:revive
|
||||
func New(bridge *bridge.Bridge) *frontendCLI {
|
||||
fe := &frontendCLI{
|
||||
Shell: ishell.New(),
|
||||
|
||||
eventListener: eventListener,
|
||||
updater: updater,
|
||||
bridge: bridge,
|
||||
|
||||
restarter: restarter,
|
||||
Shell: ishell.New(),
|
||||
bridge: bridge,
|
||||
}
|
||||
|
||||
// Clear commands.
|
||||
@ -66,12 +52,6 @@ func New( //nolint:funlen
|
||||
Help: "remove stored accounts and preferences. (alias: cl)",
|
||||
Aliases: []string{"cl"},
|
||||
}
|
||||
clearCmd.AddCmd(&ishell.Cmd{
|
||||
Name: "cache",
|
||||
Help: "remove stored preferences for accounts (aliases: c, prefs, preferences)",
|
||||
Aliases: []string{"c", "prefs", "preferences"},
|
||||
Func: fe.deleteCache,
|
||||
})
|
||||
clearCmd.AddCmd(&ishell.Cmd{
|
||||
Name: "accounts",
|
||||
Help: "remove all accounts from keychain. (aliases: a, k, keychain)",
|
||||
@ -100,15 +80,30 @@ func New( //nolint:funlen
|
||||
Completer: fe.completeUsernames,
|
||||
})
|
||||
changeCmd.AddCmd(&ishell.Cmd{
|
||||
Name: "port",
|
||||
Help: "change port numbers of IMAP and SMTP servers. (alias: p)",
|
||||
Aliases: []string{"p"},
|
||||
Func: fe.changePort,
|
||||
Name: "change-location",
|
||||
Help: "change the location of the encrypted message cache",
|
||||
Func: fe.setGluonLocation,
|
||||
})
|
||||
changeCmd.AddCmd(&ishell.Cmd{
|
||||
Name: "imap-port",
|
||||
Help: "change port number of IMAP server.",
|
||||
Func: fe.changeIMAPPort,
|
||||
})
|
||||
changeCmd.AddCmd(&ishell.Cmd{
|
||||
Name: "smtp-port",
|
||||
Help: "change port number of SMTP server.",
|
||||
Func: fe.changeSMTPPort,
|
||||
})
|
||||
changeCmd.AddCmd(&ishell.Cmd{
|
||||
Name: "imap-security",
|
||||
Help: "change IMAP SSL settings servers.(alias: ssl-imap, starttls-imap)",
|
||||
Aliases: []string{"ssl-imap", "starttls-imap"},
|
||||
Func: fe.changeIMAPSecurity,
|
||||
})
|
||||
changeCmd.AddCmd(&ishell.Cmd{
|
||||
Name: "smtp-security",
|
||||
Help: "change port numbers of IMAP and SMTP servers.(alias: ssl, starttls)",
|
||||
Aliases: []string{"ssl", "starttls"},
|
||||
Help: "change SMTP SSL settings servers.(alias: ssl-smtp, starttls-smtp)",
|
||||
Aliases: []string{"ssl-smtp", "starttls-smtp"},
|
||||
Func: fe.changeSMTPSecurity,
|
||||
})
|
||||
fe.AddCmd(changeCmd)
|
||||
@ -130,6 +125,22 @@ func New( //nolint:funlen
|
||||
})
|
||||
fe.AddCmd(dohCmd)
|
||||
|
||||
// Apple Mail commands.
|
||||
configureCmd := &ishell.Cmd{
|
||||
Name: "configure-apple-mail",
|
||||
Help: "Configures Apple Mail to use ProtonMail Bridge",
|
||||
Func: fe.configureAppleMail,
|
||||
}
|
||||
fe.AddCmd(configureCmd)
|
||||
|
||||
// TLS commands.
|
||||
exportTLSCmd := &ishell.Cmd{
|
||||
Name: "export-tls",
|
||||
Help: "Export the TLS certificate used by the Bridge",
|
||||
Func: fe.exportTLSCerts,
|
||||
}
|
||||
fe.AddCmd(exportTLSCmd)
|
||||
|
||||
// All mail visibility commands.
|
||||
allMailCmd := &ishell.Cmd{
|
||||
Name: "all-mail-visibility",
|
||||
@ -147,28 +158,6 @@ func New( //nolint:funlen
|
||||
})
|
||||
fe.AddCmd(allMailCmd)
|
||||
|
||||
// Cache-On-Disk commands.
|
||||
codCmd := &ishell.Cmd{
|
||||
Name: "local-cache",
|
||||
Help: "manage the local encrypted message cache",
|
||||
}
|
||||
codCmd.AddCmd(&ishell.Cmd{
|
||||
Name: "enable",
|
||||
Help: "enable the local cache",
|
||||
Func: fe.enableCacheOnDisk,
|
||||
})
|
||||
codCmd.AddCmd(&ishell.Cmd{
|
||||
Name: "disable",
|
||||
Help: "disable the local cache",
|
||||
Func: fe.disableCacheOnDisk,
|
||||
})
|
||||
codCmd.AddCmd(&ishell.Cmd{
|
||||
Name: "change-location",
|
||||
Help: "change the location of the local cache",
|
||||
Func: fe.setCacheOnDiskLocation,
|
||||
})
|
||||
fe.AddCmd(codCmd)
|
||||
|
||||
// Updates commands.
|
||||
updatesCmd := &ishell.Cmd{
|
||||
Name: "updates",
|
||||
@ -224,7 +213,6 @@ func New( //nolint:funlen
|
||||
Aliases: []string{"man"},
|
||||
Func: fe.printManual,
|
||||
})
|
||||
|
||||
fe.AddCmd(&ishell.Cmd{
|
||||
Name: "credits",
|
||||
Help: "print used resources.",
|
||||
@ -267,55 +255,122 @@ func New( //nolint:funlen
|
||||
Completer: fe.completeUsernames,
|
||||
})
|
||||
|
||||
// System commands.
|
||||
fe.AddCmd(&ishell.Cmd{
|
||||
Name: "restart",
|
||||
Help: "restart the bridge.",
|
||||
Func: fe.restart,
|
||||
})
|
||||
go fe.watchEvents()
|
||||
|
||||
go func() {
|
||||
defer panicHandler.HandlePanic()
|
||||
fe.watchEvents()
|
||||
}()
|
||||
return fe
|
||||
}
|
||||
|
||||
func (f *frontendCLI) watchEvents() {
|
||||
errorCh := f.eventListener.ProvideChannel(events.ErrorEvent)
|
||||
credentialsErrorCh := f.eventListener.ProvideChannel(events.CredentialsErrorEvent)
|
||||
internetConnChangedCh := f.eventListener.ProvideChannel(events.InternetConnChangedEvent)
|
||||
addressChangedCh := f.eventListener.ProvideChannel(events.AddressChangedEvent)
|
||||
addressChangedLogoutCh := f.eventListener.ProvideChannel(events.AddressChangedLogoutEvent)
|
||||
logoutCh := f.eventListener.ProvideChannel(events.LogoutEvent)
|
||||
certIssue := f.eventListener.ProvideChannel(events.TLSCertIssue)
|
||||
for {
|
||||
select {
|
||||
case errorDetails := <-errorCh:
|
||||
f.Println("Bridge failed:", errorDetails)
|
||||
case <-credentialsErrorCh:
|
||||
eventCh, done := f.bridge.GetEvents()
|
||||
defer done()
|
||||
|
||||
// TODO: Better error events.
|
||||
for _, err := range f.bridge.GetErrors() {
|
||||
switch {
|
||||
case errors.Is(err, vault.ErrCorrupt):
|
||||
f.notifyCredentialsError()
|
||||
case stat := <-internetConnChangedCh:
|
||||
if stat == events.InternetOff {
|
||||
|
||||
case errors.Is(err, vault.ErrInsecure):
|
||||
f.notifyCredentialsError()
|
||||
|
||||
case errors.Is(err, bridge.ErrServeIMAP):
|
||||
f.Println("IMAP server error:", err)
|
||||
|
||||
case errors.Is(err, bridge.ErrServeSMTP):
|
||||
f.Println("SMTP server error:", err)
|
||||
}
|
||||
}
|
||||
|
||||
for event := range eventCh {
|
||||
switch event := event.(type) {
|
||||
case events.ConnStatus:
|
||||
switch event.Status {
|
||||
case liteapi.StatusUp:
|
||||
f.notifyInternetOn()
|
||||
|
||||
case liteapi.StatusDown:
|
||||
f.notifyInternetOff()
|
||||
}
|
||||
if stat == events.InternetOn {
|
||||
f.notifyInternetOn()
|
||||
}
|
||||
case address := <-addressChangedCh:
|
||||
f.Printf("Address changed for %s. You may need to reconfigure your email client.", address)
|
||||
case address := <-addressChangedLogoutCh:
|
||||
f.notifyLogout(address)
|
||||
case userID := <-logoutCh:
|
||||
user, err := f.bridge.GetUserInfo(userID)
|
||||
|
||||
case events.UserDeauth:
|
||||
user, err := f.bridge.GetUserInfo(event.UserID)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
f.notifyLogout(user.Username)
|
||||
case <-certIssue:
|
||||
|
||||
case events.UserAddressChanged:
|
||||
user, err := f.bridge.GetUserInfo(event.UserID)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
f.Printf("Address changed for %s. You may need to reconfigure your email client.\n", user.Username)
|
||||
|
||||
case events.UserAddressDeleted:
|
||||
f.notifyLogout(event.Address)
|
||||
|
||||
case events.SyncStarted:
|
||||
user, err := f.bridge.GetUserInfo(event.UserID)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
f.Printf("A sync has begun for %s.\n", user.Username)
|
||||
|
||||
case events.SyncFinished:
|
||||
user, err := f.bridge.GetUserInfo(event.UserID)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
f.Printf("A sync has finished for %s.\n", user.Username)
|
||||
|
||||
case events.SyncProgress:
|
||||
user, err := f.bridge.GetUserInfo(event.UserID)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
f.Printf(
|
||||
"Sync (%v): %.1f%% (Elapsed: %0.1fs, ETA: %0.1fs)\n",
|
||||
user.Username,
|
||||
100*event.Progress,
|
||||
event.Elapsed.Seconds(),
|
||||
event.Remaining.Seconds(),
|
||||
)
|
||||
|
||||
case events.UpdateAvailable:
|
||||
f.Printf("An update is available (version %v)\n", event.Version.Version)
|
||||
|
||||
case events.UpdateForced:
|
||||
f.notifyNeedUpgrade()
|
||||
|
||||
case events.TLSIssue:
|
||||
f.notifyCertIssue()
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
errorCh := f.eventListener.ProvideChannel(events.ErrorEvent)
|
||||
credentialsErrorCh := f.eventListener.ProvideChannel(events.CredentialsErrorEvent)
|
||||
for {
|
||||
select {
|
||||
case errorDetails := <-errorCh:
|
||||
f.Println("Bridge failed:", errorDetails)
|
||||
case <-credentialsErrorCh:
|
||||
f.notifyCredentialsError()
|
||||
case stat := <-internetConnChangedCh:
|
||||
if stat == events.InternetOff {
|
||||
f.notifyInternetOff()
|
||||
}
|
||||
if stat == events.InternetOn {
|
||||
f.notifyInternetOn()
|
||||
}
|
||||
}
|
||||
}
|
||||
*/
|
||||
}
|
||||
|
||||
// Loop starts the frontend loop with an interactive shell.
|
||||
@ -340,12 +395,3 @@ func (f *frontendCLI) Loop() error {
|
||||
f.Run()
|
||||
return nil
|
||||
}
|
||||
|
||||
func (f *frontendCLI) NotifyManualUpdate(update updater.VersionInfo, canInstall bool) {
|
||||
// NOTE: Save the update somewhere so that it can be installed when user chooses "install now".
|
||||
}
|
||||
|
||||
func (f *frontendCLI) WaitUntilFrontendIsReady() {}
|
||||
func (f *frontendCLI) SetVersion(version updater.VersionInfo) {}
|
||||
func (f *frontendCLI) NotifySilentUpdateInstalled() {}
|
||||
func (f *frontendCLI) NotifySilentUpdateError(err error) {}
|
||||
|
||||
@ -18,28 +18,21 @@
|
||||
package cli
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/ProtonMail/proton-bridge/v2/internal/config/settings"
|
||||
"github.com/ProtonMail/proton-bridge/v2/internal/bridge"
|
||||
"github.com/ProtonMail/proton-bridge/v2/pkg/ports"
|
||||
"github.com/abiosoft/ishell"
|
||||
)
|
||||
|
||||
var currentPort = "" //nolint:gochecknoglobals
|
||||
|
||||
func (f *frontendCLI) restart(c *ishell.Context) {
|
||||
if f.yesNoQuestion("Are you sure you want to restart the Bridge") {
|
||||
f.Println("Restarting Bridge...")
|
||||
f.restarter.SetToRestart()
|
||||
f.Stop()
|
||||
}
|
||||
}
|
||||
|
||||
func (f *frontendCLI) printLogDir(c *ishell.Context) {
|
||||
if path, err := f.bridge.ProvideLogsPath(); err != nil {
|
||||
if path, err := f.bridge.GetLogsPath(); err != nil {
|
||||
f.Println("Failed to determine location of log files")
|
||||
} else {
|
||||
f.Println("Log files are stored in\n\n ", path)
|
||||
@ -50,79 +43,91 @@ func (f *frontendCLI) printManual(c *ishell.Context) {
|
||||
f.Println("More instructions about the Bridge can be found at\n\n https://protonmail.com/bridge")
|
||||
}
|
||||
|
||||
func (f *frontendCLI) deleteCache(c *ishell.Context) {
|
||||
func (f *frontendCLI) printCredits(c *ishell.Context) {
|
||||
for _, pkg := range strings.Split(bridge.Credits, ";") {
|
||||
f.Println(pkg)
|
||||
}
|
||||
}
|
||||
|
||||
func (f *frontendCLI) changeIMAPSecurity(c *ishell.Context) {
|
||||
f.ShowPrompt(false)
|
||||
defer f.ShowPrompt(true)
|
||||
|
||||
if !f.yesNoQuestion("Do you really want to remove all stored preferences") {
|
||||
return
|
||||
newSecurity := "SSL"
|
||||
if f.bridge.GetIMAPSSL() {
|
||||
newSecurity = "STARTTLS"
|
||||
}
|
||||
|
||||
if err := f.bridge.ClearData(); err != nil {
|
||||
f.printAndLogError("Cache clear failed: ", err.Error())
|
||||
return
|
||||
msg := fmt.Sprintf("Are you sure you want to change IMAP setting to %q", newSecurity)
|
||||
|
||||
if f.yesNoQuestion(msg) {
|
||||
if err := f.bridge.SetIMAPSSL(!f.bridge.GetIMAPSSL()); err != nil {
|
||||
f.printAndLogError(err)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
f.Println("Cached cleared, restarting bridge")
|
||||
|
||||
// Clearing data removes everything (db, preferences, ...) so everything has to be stopped and started again.
|
||||
f.restarter.SetToRestart()
|
||||
|
||||
f.Stop()
|
||||
}
|
||||
|
||||
func (f *frontendCLI) changeSMTPSecurity(c *ishell.Context) {
|
||||
f.ShowPrompt(false)
|
||||
defer f.ShowPrompt(true)
|
||||
|
||||
isSSL := f.bridge.GetBool(settings.SMTPSSLKey)
|
||||
newSecurity := "SSL"
|
||||
if isSSL {
|
||||
if f.bridge.GetSMTPSSL() {
|
||||
newSecurity = "STARTTLS"
|
||||
}
|
||||
|
||||
msg := fmt.Sprintf("Are you sure you want to change SMTP setting to %q and restart the Bridge", newSecurity)
|
||||
msg := fmt.Sprintf("Are you sure you want to change SMTP setting to %q", newSecurity)
|
||||
|
||||
if f.yesNoQuestion(msg) {
|
||||
f.bridge.SetBool(settings.SMTPSSLKey, !isSSL)
|
||||
f.Println("Restarting Bridge...")
|
||||
f.restarter.SetToRestart()
|
||||
f.Stop()
|
||||
if err := f.bridge.SetSMTPSSL(!f.bridge.GetSMTPSSL()); err != nil {
|
||||
f.printAndLogError(err)
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (f *frontendCLI) changePort(c *ishell.Context) {
|
||||
func (f *frontendCLI) changeIMAPPort(c *ishell.Context) {
|
||||
f.ShowPrompt(false)
|
||||
defer f.ShowPrompt(true)
|
||||
|
||||
currentPort = f.bridge.Get(settings.IMAPPortKey)
|
||||
newIMAPPort := f.readStringInAttempts("Set IMAP port (current "+currentPort+")", c.ReadLine, f.isPortFree)
|
||||
newIMAPPort := f.readStringInAttempts(fmt.Sprintf("Set IMAP port (current %v)", f.bridge.GetIMAPPort()), c.ReadLine, f.isPortFree)
|
||||
if newIMAPPort == "" {
|
||||
newIMAPPort = currentPort
|
||||
}
|
||||
imapPortChanged := newIMAPPort != currentPort
|
||||
|
||||
currentPort = f.bridge.Get(settings.SMTPPortKey)
|
||||
newSMTPPort := f.readStringInAttempts("Set SMTP port (current "+currentPort+")", c.ReadLine, f.isPortFree)
|
||||
if newSMTPPort == "" {
|
||||
newSMTPPort = currentPort
|
||||
}
|
||||
smtpPortChanged := newSMTPPort != currentPort
|
||||
|
||||
if newIMAPPort == newSMTPPort {
|
||||
f.Println("SMTP and IMAP ports must be different!")
|
||||
f.printAndLogError(errors.New("failed to get new port"))
|
||||
return
|
||||
}
|
||||
|
||||
if imapPortChanged || smtpPortChanged {
|
||||
f.Println("Saving values IMAP:", newIMAPPort, "SMTP:", newSMTPPort)
|
||||
f.bridge.Set(settings.IMAPPortKey, newIMAPPort)
|
||||
f.bridge.Set(settings.SMTPPortKey, newSMTPPort)
|
||||
f.Println("Restarting Bridge...")
|
||||
f.restarter.SetToRestart()
|
||||
f.Stop()
|
||||
} else {
|
||||
f.Println("Nothing changed")
|
||||
newIMAPPortInt, err := strconv.Atoi(newIMAPPort)
|
||||
if err != nil {
|
||||
f.printAndLogError(err)
|
||||
return
|
||||
}
|
||||
|
||||
if err := f.bridge.SetIMAPPort(newIMAPPortInt); err != nil {
|
||||
f.printAndLogError(err)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
func (f *frontendCLI) changeSMTPPort(c *ishell.Context) {
|
||||
f.ShowPrompt(false)
|
||||
defer f.ShowPrompt(true)
|
||||
|
||||
newSMTPPort := f.readStringInAttempts(fmt.Sprintf("Set SMTP port (current %v)", f.bridge.GetSMTPPort()), c.ReadLine, f.isPortFree)
|
||||
if newSMTPPort == "" {
|
||||
f.printAndLogError(errors.New("failed to get new port"))
|
||||
return
|
||||
}
|
||||
|
||||
newSMTPPortInt, err := strconv.Atoi(newSMTPPort)
|
||||
if err != nil {
|
||||
f.printAndLogError(err)
|
||||
return
|
||||
}
|
||||
|
||||
if err := f.bridge.SetSMTPPort(newSMTPPortInt); err != nil {
|
||||
f.printAndLogError(err)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
@ -135,7 +140,10 @@ func (f *frontendCLI) allowProxy(c *ishell.Context) {
|
||||
f.Println("Bridge is currently set to NOT use alternative routing to connect to Proton if it is being blocked.")
|
||||
|
||||
if f.yesNoQuestion("Are you sure you want to allow bridge to do this") {
|
||||
f.bridge.SetProxyAllowed(true)
|
||||
if err := f.bridge.SetProxyAllowed(true); err != nil {
|
||||
f.printAndLogError(err)
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -148,12 +156,15 @@ func (f *frontendCLI) disallowProxy(c *ishell.Context) {
|
||||
f.Println("Bridge is currently set to use alternative routing to connect to Proton if it is being blocked.")
|
||||
|
||||
if f.yesNoQuestion("Are you sure you want to stop bridge from doing this") {
|
||||
f.bridge.SetProxyAllowed(false)
|
||||
if err := f.bridge.SetProxyAllowed(false); err != nil {
|
||||
f.printAndLogError(err)
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (f *frontendCLI) hideAllMail(c *ishell.Context) {
|
||||
if !f.bridge.IsAllMailVisible() {
|
||||
if !f.bridge.GetShowAllMail() {
|
||||
f.Println("All Mail folder is not listed in your local client.")
|
||||
return
|
||||
}
|
||||
@ -161,12 +172,15 @@ func (f *frontendCLI) hideAllMail(c *ishell.Context) {
|
||||
f.Println("All Mail folder is listed in your client right now.")
|
||||
|
||||
if f.yesNoQuestion("Do you want to hide All Mail folder") {
|
||||
f.bridge.SetIsAllMailVisible(false)
|
||||
if err := f.bridge.SetShowAllMail(false); err != nil {
|
||||
f.printAndLogError(err)
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (f *frontendCLI) showAllMail(c *ishell.Context) {
|
||||
if f.bridge.IsAllMailVisible() {
|
||||
if f.bridge.GetShowAllMail() {
|
||||
f.Println("All Mail folder is listed in your local client.")
|
||||
return
|
||||
}
|
||||
@ -174,68 +188,47 @@ func (f *frontendCLI) showAllMail(c *ishell.Context) {
|
||||
f.Println("All Mail folder is not listed in your client right now.")
|
||||
|
||||
if f.yesNoQuestion("Do you want to show All Mail folder") {
|
||||
f.bridge.SetIsAllMailVisible(true)
|
||||
if err := f.bridge.SetShowAllMail(true); err != nil {
|
||||
f.printAndLogError(err)
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (f *frontendCLI) enableCacheOnDisk(c *ishell.Context) {
|
||||
if f.bridge.GetBool(settings.CacheEnabledKey) {
|
||||
f.Println("The local cache is already enabled.")
|
||||
return
|
||||
func (f *frontendCLI) setGluonLocation(c *ishell.Context) {
|
||||
if gluonDir := f.bridge.GetGluonDir(); gluonDir != "" {
|
||||
f.Println("The current message cache location is:", gluonDir)
|
||||
}
|
||||
|
||||
if f.yesNoQuestion("Are you sure you want to enable the local cache") {
|
||||
if err := f.bridge.EnableCache(); err != nil {
|
||||
f.Println("The local cache could not be enabled.")
|
||||
if location := f.readStringInAttempts("Enter a new location for the message cache", c.ReadLine, f.isCacheLocationUsable); location != "" {
|
||||
if err := f.bridge.SetGluonDir(context.Background(), location); err != nil {
|
||||
f.printAndLogError(err)
|
||||
return
|
||||
}
|
||||
|
||||
f.restarter.SetToRestart()
|
||||
f.Stop()
|
||||
}
|
||||
}
|
||||
|
||||
func (f *frontendCLI) disableCacheOnDisk(c *ishell.Context) {
|
||||
if !f.bridge.GetBool(settings.CacheEnabledKey) {
|
||||
f.Println("The local cache is already disabled.")
|
||||
return
|
||||
}
|
||||
func (f *frontendCLI) exportTLSCerts(c *ishell.Context) {
|
||||
if location := f.readStringInAttempts("Enter a path to which to export the TLS certificate used for IMAP and SMTP", c.ReadLine, f.isCacheLocationUsable); location != "" {
|
||||
cert, key := f.bridge.GetBridgeTLSCert()
|
||||
|
||||
if f.yesNoQuestion("Are you sure you want to disable the local cache") {
|
||||
if err := f.bridge.DisableCache(); err != nil {
|
||||
f.Println("The local cache could not be disabled.")
|
||||
if err := os.WriteFile(filepath.Join(location, "cert.pem"), cert, 0600); err != nil {
|
||||
f.printAndLogError(err)
|
||||
return
|
||||
}
|
||||
|
||||
f.restarter.SetToRestart()
|
||||
f.Stop()
|
||||
}
|
||||
}
|
||||
|
||||
func (f *frontendCLI) setCacheOnDiskLocation(c *ishell.Context) {
|
||||
if !f.bridge.GetBool(settings.CacheEnabledKey) {
|
||||
f.Println("The local cache must be enabled.")
|
||||
return
|
||||
}
|
||||
|
||||
if location := f.bridge.Get(settings.CacheLocationKey); location != "" {
|
||||
f.Println("The current local cache location is:", location)
|
||||
}
|
||||
|
||||
if location := f.readStringInAttempts("Enter a new location for the cache", c.ReadLine, f.isCacheLocationUsable); location != "" {
|
||||
if err := f.bridge.MigrateCache(f.bridge.Get(settings.CacheLocationKey), location); err != nil {
|
||||
f.Println("The local cache location could not be changed.")
|
||||
if err := os.WriteFile(filepath.Join(location, "key.pem"), key, 0600); err != nil {
|
||||
f.printAndLogError(err)
|
||||
return
|
||||
}
|
||||
|
||||
f.restarter.SetToRestart()
|
||||
f.Stop()
|
||||
f.Println("TLS certificate exported to", location)
|
||||
}
|
||||
}
|
||||
|
||||
func (f *frontendCLI) isPortFree(port string) bool {
|
||||
port = strings.ReplaceAll(port, ":", "")
|
||||
if port == "" || port == currentPort {
|
||||
if port == "" {
|
||||
return true
|
||||
}
|
||||
number, err := strconv.Atoi(port)
|
||||
|
||||
@ -18,36 +18,16 @@
|
||||
package cli
|
||||
|
||||
import (
|
||||
"strings"
|
||||
|
||||
"github.com/ProtonMail/proton-bridge/v2/internal/bridge"
|
||||
"github.com/ProtonMail/proton-bridge/v2/internal/config/settings"
|
||||
"github.com/ProtonMail/proton-bridge/v2/internal/updater"
|
||||
"github.com/abiosoft/ishell"
|
||||
)
|
||||
|
||||
func (f *frontendCLI) checkUpdates(c *ishell.Context) {
|
||||
version, err := f.updater.Check()
|
||||
if err != nil {
|
||||
f.Println("An error occurred while checking for updates.")
|
||||
return
|
||||
}
|
||||
|
||||
if f.updater.IsUpdateApplicable(version) {
|
||||
f.Println("An update is available.")
|
||||
} else {
|
||||
f.Println("Your version is up to date.")
|
||||
}
|
||||
}
|
||||
|
||||
func (f *frontendCLI) printCredits(c *ishell.Context) {
|
||||
for _, pkg := range strings.Split(bridge.Credits, ";") {
|
||||
f.Println(pkg)
|
||||
}
|
||||
f.bridge.CheckForUpdates()
|
||||
}
|
||||
|
||||
func (f *frontendCLI) enableAutoUpdates(c *ishell.Context) {
|
||||
if f.bridge.GetBool(settings.AutoUpdateKey) {
|
||||
if f.bridge.GetAutoUpdate() {
|
||||
f.Println("Bridge is already set to automatically install updates.")
|
||||
return
|
||||
}
|
||||
@ -55,12 +35,15 @@ func (f *frontendCLI) enableAutoUpdates(c *ishell.Context) {
|
||||
f.Println("Bridge is currently set to NOT automatically install updates.")
|
||||
|
||||
if f.yesNoQuestion("Are you sure you want to allow bridge to do this") {
|
||||
f.bridge.SetBool(settings.AutoUpdateKey, true)
|
||||
if err := f.bridge.SetAutoUpdate(true); err != nil {
|
||||
f.printAndLogError(err)
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (f *frontendCLI) disableAutoUpdates(c *ishell.Context) {
|
||||
if !f.bridge.GetBool(settings.AutoUpdateKey) {
|
||||
if !f.bridge.GetAutoUpdate() {
|
||||
f.Println("Bridge is already set to NOT automatically install updates.")
|
||||
return
|
||||
}
|
||||
@ -68,7 +51,10 @@ func (f *frontendCLI) disableAutoUpdates(c *ishell.Context) {
|
||||
f.Println("Bridge is currently set to automatically install updates.")
|
||||
|
||||
if f.yesNoQuestion("Are you sure you want to stop bridge from doing this") {
|
||||
f.bridge.SetBool(settings.AutoUpdateKey, false)
|
||||
if err := f.bridge.SetAutoUpdate(false); err != nil {
|
||||
f.printAndLogError(err)
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -81,7 +67,10 @@ func (f *frontendCLI) selectEarlyChannel(c *ishell.Context) {
|
||||
f.Println("Bridge is currently on the stable update channel.")
|
||||
|
||||
if f.yesNoQuestion("Are you sure you want to switch to the early-access update channel") {
|
||||
f.bridge.SetUpdateChannel(updater.EarlyChannel)
|
||||
if err := f.bridge.SetUpdateChannel(updater.EarlyChannel); err != nil {
|
||||
f.printAndLogError(err)
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -95,6 +84,9 @@ func (f *frontendCLI) selectStableChannel(c *ishell.Context) {
|
||||
f.Println("Switching to the stable channel may reset all data!")
|
||||
|
||||
if f.yesNoQuestion("Are you sure you want to switch to the stable update channel") {
|
||||
f.bridge.SetUpdateChannel(updater.StableChannel)
|
||||
if err := f.bridge.SetUpdateChannel(updater.StableChannel); err != nil {
|
||||
f.printAndLogError(err)
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -20,7 +20,6 @@ package cli
|
||||
import (
|
||||
"strings"
|
||||
|
||||
pmapi "github.com/ProtonMail/proton-bridge/v2/pkg/pmapi"
|
||||
"github.com/fatih/color"
|
||||
)
|
||||
|
||||
@ -67,15 +66,7 @@ func (f *frontendCLI) printAndLogError(args ...interface{}) {
|
||||
}
|
||||
|
||||
func (f *frontendCLI) processAPIError(err error) {
|
||||
log.Warn("API error: ", err)
|
||||
switch err {
|
||||
case pmapi.ErrNoConnection:
|
||||
f.notifyInternetOff()
|
||||
case pmapi.ErrUpgradeApplication:
|
||||
f.notifyNeedUpgrade()
|
||||
default:
|
||||
f.Println("Server error:", err.Error())
|
||||
}
|
||||
f.printAndLogError(err)
|
||||
}
|
||||
|
||||
func (f *frontendCLI) notifyInternetOff() {
|
||||
@ -91,12 +82,7 @@ func (f *frontendCLI) notifyLogout(address string) {
|
||||
}
|
||||
|
||||
func (f *frontendCLI) notifyNeedUpgrade() {
|
||||
version, err := f.updater.Check()
|
||||
if err != nil {
|
||||
log.WithError(err).Error("Failed to notify need upgrade")
|
||||
return
|
||||
}
|
||||
f.Println("Please download and install the newest version of application from", version.LandingPage)
|
||||
f.Println("Please download and install the newest version of the application.")
|
||||
}
|
||||
|
||||
func (f *frontendCLI) notifyCredentialsError() {
|
||||
|
||||
Reference in New Issue
Block a user