From 7fd4bac834f7a97d6b1f3d79887d03b6b2636582 Mon Sep 17 00:00:00 2001 From: Arnold Bechtoldt Date: Sat, 21 Mar 2020 17:31:27 +0100 Subject: [PATCH 1/5] Add logging when reconnecting to server Signed-off-by: Arnold Bechtoldt --- cmd/postisto/main.go | 13 ++++++++++++- pkg/server/imap.go | 12 +++++++++++- 2 files changed, 23 insertions(+), 2 deletions(-) diff --git a/cmd/postisto/main.go b/cmd/postisto/main.go index cce869a..7fc98ac 100644 --- a/cmd/postisto/main.go +++ b/cmd/postisto/main.go @@ -119,10 +119,21 @@ func runApp(configPath string, logLevel string, logJSON bool, pollInterval time. } acc := cfg.Accounts[name] + + if err := acc.Connection.Connect(); err != nil { + return fmt.Errorf("failed to initially connect to server %q with username %q", acc.Connection.Server, acc.Connection.Username) + } + accs = append(accs, &accInfo{name: name, acc: &acc, filters: filters}) } - log.Info("Entering continuously running mail search & filter loop. Waiting for mails...") + if onetime { + log.Info("Entering mail search & filter loop once and exit then immediately") + } else { + log.Info("Entering continuously running mail search & filter loop. Waiting for mails...") + + } + for { for _, accInfo := range accs { if err := filter.EvaluateFilterSetsOnMsgs(&accInfo.acc.Connection, *accInfo.acc.InputMailbox, []string{server.SeenFlag, server.FlaggedFlag}, *accInfo.acc.FallbackMailbox, accInfo.filters); err != nil { diff --git a/pkg/server/imap.go b/pkg/server/imap.go index 4e3327c..7708554 100644 --- a/pkg/server/imap.go +++ b/pkg/server/imap.go @@ -37,9 +37,9 @@ func (conn *Connection) Upload(file string, mailbox string, flags []string) erro return err } - //if conn.imapClient == nil || (conn.imapClient.State() != imapUtil.AuthenticatedState && conn.imapClient.State() != imapUtil.SelectedState) { // Re-login if necessary if conn.imapClient == nil || (conn.imapClient.State() != imapUtil.AuthenticatedState && conn.imapClient.State() != imapUtil.SelectedState) { + log.Infow("Server connection lost, trying to reconnect...", "server", conn.Server, "username", conn.Username) if err := conn.Connect(); err != nil { return err } @@ -63,6 +63,7 @@ func (conn *Connection) Upload(file string, mailbox string, flags []string) erro func (conn *Connection) Search(mailbox string, withFlags []string, withoutFlags []string) ([]uint32, error) { // Re-login if necessary if conn.imapClient == nil || (conn.imapClient.State() != imapUtil.AuthenticatedState && conn.imapClient.State() != imapUtil.SelectedState) { + log.Infow("Server connection lost, trying to reconnect...", "server", conn.Server, "username", conn.Username) if err := conn.Connect(); err != nil { return nil, err } @@ -90,6 +91,7 @@ func (conn *Connection) Search(mailbox string, withFlags []string, withoutFlags func (conn *Connection) Fetch(mailbox string, uids []uint32) ([]*Message, error) { // Re-login if necessary if conn.imapClient == nil || (conn.imapClient.State() != imapUtil.AuthenticatedState && conn.imapClient.State() != imapUtil.SelectedState) { + log.Infow("Server connection lost, trying to reconnect...", "server", conn.Server, "username", conn.Username) if err := conn.Connect(); err != nil { return nil, err } @@ -138,6 +140,7 @@ func (conn *Connection) Fetch(mailbox string, uids []uint32) ([]*Message, error) func (conn *Connection) SearchAndFetch(mailbox string, withFlags []string, withoutFlags []string) ([]*Message, error) { // Re-login if necessary if conn.imapClient == nil || (conn.imapClient.State() != imapUtil.AuthenticatedState && conn.imapClient.State() != imapUtil.SelectedState) { + log.Infow("Server connection lost, trying to reconnect...", "server", conn.Server, "username", conn.Username) if err := conn.Connect(); err != nil { return nil, err } @@ -159,6 +162,7 @@ func (conn *Connection) DeleteMsgs(mailbox string, uids []uint32, expunge bool) func (conn *Connection) SetFlags(mailbox string, uids []uint32, flagOp string, flags []interface{}, expunge bool) error { // Re-login if necessary if conn.imapClient == nil || (conn.imapClient.State() != imapUtil.AuthenticatedState && conn.imapClient.State() != imapUtil.SelectedState) { + log.Infow("Server connection lost, trying to reconnect...", "server", conn.Server, "username", conn.Username) if err := conn.Connect(); err != nil { return err } @@ -202,6 +206,7 @@ func (conn *Connection) GetFlags(mailbox string, uid uint32) ([]string, error) { // Re-login if necessary if conn.imapClient == nil || (conn.imapClient.State() != imapUtil.AuthenticatedState && conn.imapClient.State() != imapUtil.SelectedState) { + log.Infow("Server connection lost, trying to reconnect...", "server", conn.Server, "username", conn.Username) if err := conn.Connect(); err != nil { return nil, err } @@ -241,6 +246,7 @@ func (conn *Connection) CreateMailbox(name string) error { // Re-login if necessary if conn.imapClient == nil || (conn.imapClient.State() != imapUtil.AuthenticatedState && conn.imapClient.State() != imapUtil.SelectedState) { + log.Infow("Server connection lost, trying to reconnect...", "server", conn.Server, "username", conn.Username) if err := conn.Connect(); err != nil { return err } @@ -259,6 +265,7 @@ func (conn *Connection) DeleteMailbox(name string) error { // Re-login if necessary if conn.imapClient == nil || (conn.imapClient.State() != imapUtil.AuthenticatedState && conn.imapClient.State() != imapUtil.SelectedState) { + log.Infow("Server connection lost, trying to reconnect...", "server", conn.Server, "username", conn.Username) if err := conn.Connect(); err != nil { return err } @@ -276,6 +283,7 @@ func (conn *Connection) DeleteMailbox(name string) error { func (conn *Connection) List() (map[string]imapUtil.MailboxInfo, error) { // Re-login if necessary if conn.imapClient == nil || (conn.imapClient.State() != imapUtil.AuthenticatedState && conn.imapClient.State() != imapUtil.SelectedState) { + log.Infow("Server connection lost, trying to reconnect...", "server", conn.Server, "username", conn.Username) if err := conn.Connect(); err != nil { return nil, err } @@ -340,6 +348,7 @@ func (conn *Connection) List() (map[string]imapUtil.MailboxInfo, error) { func (conn *Connection) Move(uids []uint32, from string, to string) error { // Re-login if necessary if conn.imapClient == nil || (conn.imapClient.State() != imapUtil.AuthenticatedState && conn.imapClient.State() != imapUtil.SelectedState) { + log.Infow("Server connection lost, trying to reconnect...", "server", conn.Server, "username", conn.Username) if err := conn.Connect(); err != nil { return err } @@ -393,6 +402,7 @@ func (conn *Connection) Move(uids []uint32, from string, to string) error { func (conn *Connection) Select(mailbox string, readOnly bool, autoCreate bool) (*imapUtil.MailboxStatus, error) { // Re-login if necessary if conn.imapClient == nil || (conn.imapClient.State() != imapUtil.AuthenticatedState && conn.imapClient.State() != imapUtil.SelectedState) { + log.Infow("Server connection lost, trying to reconnect...", "server", conn.Server, "username", conn.Username) if err := conn.Connect(); err != nil { return nil, err } From c625fddb6680e224e4e4992b572ddc7fcbc0def1 Mon Sep 17 00:00:00 2001 From: Arnold Bechtoldt Date: Sat, 21 Mar 2020 18:00:49 +0100 Subject: [PATCH 2/5] Add SELECT logging Signed-off-by: Arnold Bechtoldt --- pkg/server/imap.go | 1 + 1 file changed, 1 insertion(+) diff --git a/pkg/server/imap.go b/pkg/server/imap.go index 7708554..38a8bf4 100644 --- a/pkg/server/imap.go +++ b/pkg/server/imap.go @@ -408,6 +408,7 @@ func (conn *Connection) Select(mailbox string, readOnly bool, autoCreate bool) ( } } + log.Debugw("Selecting mailbox", "mailbox", mailbox) status, err := conn.imapClient.Select(mailbox, readOnly) if err == nil { From fd5759bb5b6c32b3f741ec0c20a08f58fa2bc207 Mon Sep 17 00:00:00 2001 From: Arnold Bechtoldt Date: Fri, 27 Mar 2020 21:35:37 +0100 Subject: [PATCH 3/5] Reduce code redundancy in imap reconnect logic Thanks again @hikhvar for golang-mentoring :) Signed-off-by: Arnold Bechtoldt --- pkg/server/imap.go | 92 +++++++++++++++++++--------------------------- 1 file changed, 37 insertions(+), 55 deletions(-) diff --git a/pkg/server/imap.go b/pkg/server/imap.go index 38a8bf4..b5d2a8b 100644 --- a/pkg/server/imap.go +++ b/pkg/server/imap.go @@ -21,6 +21,21 @@ const ( RecentFlag = "\\Recent" ) +func (conn *Connection) requiresReconnect() bool { + return conn.imapClient == nil || (conn.imapClient.State() != imapUtil.AuthenticatedState && conn.imapClient.State() != imapUtil.SelectedState) +} + +func (conn *Connection) ensureConnected() error { + if conn.requiresReconnect() { + log.Infow("Server connection lost, trying to reconnect...", "server", conn.Server, "username", conn.Username) + if err := conn.Connect(); err != nil { + return err + } + } + + return nil +} + func (conn *Connection) Upload(file string, mailbox string, flags []string) error { data, err := os.Open(file) defer data.Close() @@ -38,11 +53,8 @@ func (conn *Connection) Upload(file string, mailbox string, flags []string) erro } // Re-login if necessary - if conn.imapClient == nil || (conn.imapClient.State() != imapUtil.AuthenticatedState && conn.imapClient.State() != imapUtil.SelectedState) { - log.Infow("Server connection lost, trying to reconnect...", "server", conn.Server, "username", conn.Username) - if err := conn.Connect(); err != nil { - return err - } + if err := conn.ensureConnected(); err != nil { + return err } // Select mailbox @@ -62,11 +74,8 @@ func (conn *Connection) Upload(file string, mailbox string, flags []string) erro func (conn *Connection) Search(mailbox string, withFlags []string, withoutFlags []string) ([]uint32, error) { // Re-login if necessary - if conn.imapClient == nil || (conn.imapClient.State() != imapUtil.AuthenticatedState && conn.imapClient.State() != imapUtil.SelectedState) { - log.Infow("Server connection lost, trying to reconnect...", "server", conn.Server, "username", conn.Username) - if err := conn.Connect(); err != nil { - return nil, err - } + if err := conn.ensureConnected(); err != nil { + return nil, err } // Select mailbox @@ -90,11 +99,8 @@ func (conn *Connection) Search(mailbox string, withFlags []string, withoutFlags func (conn *Connection) Fetch(mailbox string, uids []uint32) ([]*Message, error) { // Re-login if necessary - if conn.imapClient == nil || (conn.imapClient.State() != imapUtil.AuthenticatedState && conn.imapClient.State() != imapUtil.SelectedState) { - log.Infow("Server connection lost, trying to reconnect...", "server", conn.Server, "username", conn.Username) - if err := conn.Connect(); err != nil { - return nil, err - } + if err := conn.ensureConnected(); err != nil { + return nil, err } // Select mailbox @@ -139,11 +145,8 @@ func (conn *Connection) Fetch(mailbox string, uids []uint32) ([]*Message, error) func (conn *Connection) SearchAndFetch(mailbox string, withFlags []string, withoutFlags []string) ([]*Message, error) { // Re-login if necessary - if conn.imapClient == nil || (conn.imapClient.State() != imapUtil.AuthenticatedState && conn.imapClient.State() != imapUtil.SelectedState) { - log.Infow("Server connection lost, trying to reconnect...", "server", conn.Server, "username", conn.Username) - if err := conn.Connect(); err != nil { - return nil, err - } + if err := conn.ensureConnected(); err != nil { + return nil, err } uids, err := conn.Search(mailbox, withFlags, withoutFlags) @@ -161,11 +164,8 @@ func (conn *Connection) DeleteMsgs(mailbox string, uids []uint32, expunge bool) func (conn *Connection) SetFlags(mailbox string, uids []uint32, flagOp string, flags []interface{}, expunge bool) error { // Re-login if necessary - if conn.imapClient == nil || (conn.imapClient.State() != imapUtil.AuthenticatedState && conn.imapClient.State() != imapUtil.SelectedState) { - log.Infow("Server connection lost, trying to reconnect...", "server", conn.Server, "username", conn.Username) - if err := conn.Connect(); err != nil { - return err - } + if err := conn.ensureConnected(); err != nil { + return err } log.Debugw("Starting to set flags on mails", "mailbox", mailbox, "uids", uids, "flagOp", flagOp, "flags", flags, "expunge", expunge) @@ -205,11 +205,8 @@ func (conn *Connection) GetFlags(mailbox string, uid uint32) ([]string, error) { log.Debugw("Starting to get flags from a mail", "mailbox", mailbox, "uid", uid) // Re-login if necessary - if conn.imapClient == nil || (conn.imapClient.State() != imapUtil.AuthenticatedState && conn.imapClient.State() != imapUtil.SelectedState) { - log.Infow("Server connection lost, trying to reconnect...", "server", conn.Server, "username", conn.Username) - if err := conn.Connect(); err != nil { - return nil, err - } + if err := conn.ensureConnected(); err != nil { + return nil, err } // Select mailbox @@ -245,11 +242,8 @@ func (conn *Connection) CreateMailbox(name string) error { log.Infow("Creating new mailbox", "mailbox", name) // Re-login if necessary - if conn.imapClient == nil || (conn.imapClient.State() != imapUtil.AuthenticatedState && conn.imapClient.State() != imapUtil.SelectedState) { - log.Infow("Server connection lost, trying to reconnect...", "server", conn.Server, "username", conn.Username) - if err := conn.Connect(); err != nil { - return err - } + if err := conn.ensureConnected(); err != nil { + return err } if err := conn.imapClient.Create(name); err != nil { @@ -264,11 +258,8 @@ func (conn *Connection) DeleteMailbox(name string) error { log.Infow("Deleting mailbox", "mailbox", name) // Re-login if necessary - if conn.imapClient == nil || (conn.imapClient.State() != imapUtil.AuthenticatedState && conn.imapClient.State() != imapUtil.SelectedState) { - log.Infow("Server connection lost, trying to reconnect...", "server", conn.Server, "username", conn.Username) - if err := conn.Connect(); err != nil { - return err - } + if err := conn.ensureConnected(); err != nil { + return err } if err := conn.imapClient.Delete(name); err != nil { @@ -282,11 +273,8 @@ func (conn *Connection) DeleteMailbox(name string) error { // List mailboxes func (conn *Connection) List() (map[string]imapUtil.MailboxInfo, error) { // Re-login if necessary - if conn.imapClient == nil || (conn.imapClient.State() != imapUtil.AuthenticatedState && conn.imapClient.State() != imapUtil.SelectedState) { - log.Infow("Server connection lost, trying to reconnect...", "server", conn.Server, "username", conn.Username) - if err := conn.Connect(); err != nil { - return nil, err - } + if err := conn.ensureConnected(); err != nil { + return nil, err } mailboxesChan := make(chan *imapUtil.MailboxInfo) @@ -347,11 +335,8 @@ func (conn *Connection) List() (map[string]imapUtil.MailboxInfo, error) { func (conn *Connection) Move(uids []uint32, from string, to string) error { // Re-login if necessary - if conn.imapClient == nil || (conn.imapClient.State() != imapUtil.AuthenticatedState && conn.imapClient.State() != imapUtil.SelectedState) { - log.Infow("Server connection lost, trying to reconnect...", "server", conn.Server, "username", conn.Username) - if err := conn.Connect(); err != nil { - return err - } + if err := conn.ensureConnected(); err != nil { + return err } var err error @@ -401,11 +386,8 @@ func (conn *Connection) Move(uids []uint32, from string, to string) error { func (conn *Connection) Select(mailbox string, readOnly bool, autoCreate bool) (*imapUtil.MailboxStatus, error) { // Re-login if necessary - if conn.imapClient == nil || (conn.imapClient.State() != imapUtil.AuthenticatedState && conn.imapClient.State() != imapUtil.SelectedState) { - log.Infow("Server connection lost, trying to reconnect...", "server", conn.Server, "username", conn.Username) - if err := conn.Connect(); err != nil { - return nil, err - } + if err := conn.ensureConnected(); err != nil { + return nil, err } log.Debugw("Selecting mailbox", "mailbox", mailbox) From 1bd39ccd5b760c6224ee89952fc1954681bb0be1 Mon Sep 17 00:00:00 2001 From: Arnold Bechtoldt Date: Fri, 27 Mar 2020 21:56:57 +0100 Subject: [PATCH 4/5] Catch imap disconnect by server Signed-off-by: Arnold Bechtoldt --- cmd/postisto/main.go | 12 +++++++++++- pkg/server/imap.go | 7 +++++++ 2 files changed, 18 insertions(+), 1 deletion(-) diff --git a/cmd/postisto/main.go b/cmd/postisto/main.go index 7fc98ac..dc5a6ef 100644 --- a/cmd/postisto/main.go +++ b/cmd/postisto/main.go @@ -131,12 +131,22 @@ func runApp(configPath string, logLevel string, logJSON bool, pollInterval time. log.Info("Entering mail search & filter loop once and exit then immediately") } else { log.Info("Entering continuously running mail search & filter loop. Waiting for mails...") - } for { for _, accInfo := range accs { if err := filter.EvaluateFilterSetsOnMsgs(&accInfo.acc.Connection, *accInfo.acc.InputMailbox, []string{server.SeenFlag, server.FlaggedFlag}, *accInfo.acc.FallbackMailbox, accInfo.filters); err != nil { + if server.IsDisconnected(err) { + // this can happen, so let's just reconnect + //TODO implement exponential backoff to avoid too much noise? + + if err := accInfo.acc.Connection.Connect(); err != nil { + return fmt.Errorf("failed to reconnect to server %q with username %q", accInfo.acc.Connection.Server, accInfo.acc.Connection.Username) + } + + continue + } + return fmt.Errorf("failed to run filter engine: %v", err) } } diff --git a/pkg/server/imap.go b/pkg/server/imap.go index b5d2a8b..e64fa04 100644 --- a/pkg/server/imap.go +++ b/pkg/server/imap.go @@ -2,6 +2,7 @@ package server import ( "bytes" + "errors" "fmt" "github.com/arnisoph/postisto/pkg/log" imapUtil "github.com/emersion/go-imap" @@ -21,6 +22,12 @@ const ( RecentFlag = "\\Recent" ) +var ErrConnectionClosed = errors.New("imap: connection closed") + +func IsDisconnected(err error) bool { //TODO https://github.com/emersion/go-imap/issues/348 + return err.Error() == ErrConnectionClosed.Error() +} + func (conn *Connection) requiresReconnect() bool { return conn.imapClient == nil || (conn.imapClient.State() != imapUtil.AuthenticatedState && conn.imapClient.State() != imapUtil.SelectedState) } From f7ae225b0041ac1be3884a05ea672b48ffef2df6 Mon Sep 17 00:00:00 2001 From: Arnold Bechtoldt Date: Fri, 27 Mar 2020 22:01:20 +0100 Subject: [PATCH 5/5] Don't be silly, at least wait a bit before reconnect Signed-off-by: Arnold Bechtoldt --- cmd/postisto/main.go | 1 + 1 file changed, 1 insertion(+) diff --git a/cmd/postisto/main.go b/cmd/postisto/main.go index dc5a6ef..16dd72c 100644 --- a/cmd/postisto/main.go +++ b/cmd/postisto/main.go @@ -140,6 +140,7 @@ func runApp(configPath string, logLevel string, logJSON bool, pollInterval time. // this can happen, so let's just reconnect //TODO implement exponential backoff to avoid too much noise? + time.Sleep(time.Second * 3) if err := accInfo.acc.Connection.Connect(); err != nil { return fmt.Errorf("failed to reconnect to server %q with username %q", accInfo.acc.Connection.Server, accInfo.acc.Connection.Username) }