From ba65494fcee0ff349d6233f56f95cf35b80e85c6 Mon Sep 17 00:00:00 2001 From: Michal Horejsek Date: Fri, 6 Nov 2020 14:23:38 +0100 Subject: [PATCH] Try load messages one-by-one --- Changelog.md | 2 + Makefile | 2 +- internal/transfer/mocks/mocks.go | 171 +++++++++++++++++++++- internal/transfer/provider_imap.go | 25 +++- internal/transfer/provider_imap_source.go | 69 ++++----- internal/transfer/provider_imap_test.go | 100 +++++++++++++ internal/transfer/provider_imap_utils.go | 89 ++++++----- internal/transfer/transfer_test.go | 24 +-- 8 files changed, 393 insertions(+), 89 deletions(-) create mode 100644 internal/transfer/provider_imap_test.go diff --git a/Changelog.md b/Changelog.md index 977cd280..fc384055 100644 --- a/Changelog.md +++ b/Changelog.md @@ -16,6 +16,8 @@ Changelog [format](http://keepachangelog.com/en/1.0.0/) ## [Bridge 1.5.0] Golden Gate +* GODT-701 Try load messages one-by-one if IMAP server errors with batch load and not interrupt the transfer + ### Changed * Updated go-mbox dependency back to upstream. diff --git a/Makefile b/Makefile index b6c1c654..cd0e7411 100644 --- a/Makefile +++ b/Makefile @@ -196,7 +196,7 @@ coverage: test mocks: mockgen --package mocks github.com/ProtonMail/proton-bridge/internal/users Configer,PanicHandler,ClientManager,CredentialsStorer,StoreMaker > internal/users/mocks/mocks.go - mockgen --package mocks github.com/ProtonMail/proton-bridge/internal/transfer PanicHandler,ClientManager > internal/transfer/mocks/mocks.go + mockgen --package mocks github.com/ProtonMail/proton-bridge/internal/transfer PanicHandler,ClientManager,IMAPClientProvider > internal/transfer/mocks/mocks.go mockgen --package mocks github.com/ProtonMail/proton-bridge/internal/store PanicHandler,ClientManager,BridgeUser > internal/store/mocks/mocks.go mockgen --package mocks github.com/ProtonMail/proton-bridge/pkg/listener Listener > internal/store/mocks/utils_mocks.go mockgen --package mocks github.com/ProtonMail/proton-bridge/pkg/pmapi Client > pkg/pmapi/mocks/mocks.go diff --git a/internal/transfer/mocks/mocks.go b/internal/transfer/mocks/mocks.go index 560303bf..8c78a87a 100644 --- a/internal/transfer/mocks/mocks.go +++ b/internal/transfer/mocks/mocks.go @@ -1,5 +1,5 @@ // Code generated by MockGen. DO NOT EDIT. -// Source: github.com/ProtonMail/proton-bridge/internal/transfer (interfaces: PanicHandler,ClientManager) +// Source: github.com/ProtonMail/proton-bridge/internal/transfer (interfaces: PanicHandler,ClientManager,IMAPClientProvider) // Package mocks is a generated GoMock package. package mocks @@ -8,6 +8,8 @@ import ( reflect "reflect" pmapi "github.com/ProtonMail/proton-bridge/pkg/pmapi" + imap "github.com/emersion/go-imap" + sasl "github.com/emersion/go-sasl" gomock "github.com/golang/mock/gomock" ) @@ -96,3 +98,170 @@ func (mr *MockClientManagerMockRecorder) GetClient(arg0 interface{}) *gomock.Cal mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetClient", reflect.TypeOf((*MockClientManager)(nil).GetClient), arg0) } + +// MockIMAPClientProvider is a mock of IMAPClientProvider interface +type MockIMAPClientProvider struct { + ctrl *gomock.Controller + recorder *MockIMAPClientProviderMockRecorder +} + +// MockIMAPClientProviderMockRecorder is the mock recorder for MockIMAPClientProvider +type MockIMAPClientProviderMockRecorder struct { + mock *MockIMAPClientProvider +} + +// NewMockIMAPClientProvider creates a new mock instance +func NewMockIMAPClientProvider(ctrl *gomock.Controller) *MockIMAPClientProvider { + mock := &MockIMAPClientProvider{ctrl: ctrl} + mock.recorder = &MockIMAPClientProviderMockRecorder{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use +func (m *MockIMAPClientProvider) EXPECT() *MockIMAPClientProviderMockRecorder { + return m.recorder +} + +// Authenticate mocks base method +func (m *MockIMAPClientProvider) Authenticate(arg0 sasl.Client) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "Authenticate", arg0) + ret0, _ := ret[0].(error) + return ret0 +} + +// Authenticate indicates an expected call of Authenticate +func (mr *MockIMAPClientProviderMockRecorder) Authenticate(arg0 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Authenticate", reflect.TypeOf((*MockIMAPClientProvider)(nil).Authenticate), arg0) +} + +// Capability mocks base method +func (m *MockIMAPClientProvider) Capability() (map[string]bool, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "Capability") + ret0, _ := ret[0].(map[string]bool) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// Capability indicates an expected call of Capability +func (mr *MockIMAPClientProviderMockRecorder) Capability() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Capability", reflect.TypeOf((*MockIMAPClientProvider)(nil).Capability)) +} + +// Fetch mocks base method +func (m *MockIMAPClientProvider) Fetch(arg0 *imap.SeqSet, arg1 []imap.FetchItem, arg2 chan *imap.Message) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "Fetch", arg0, arg1, arg2) + ret0, _ := ret[0].(error) + return ret0 +} + +// Fetch indicates an expected call of Fetch +func (mr *MockIMAPClientProviderMockRecorder) Fetch(arg0, arg1, arg2 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Fetch", reflect.TypeOf((*MockIMAPClientProvider)(nil).Fetch), arg0, arg1, arg2) +} + +// List mocks base method +func (m *MockIMAPClientProvider) List(arg0, arg1 string, arg2 chan *imap.MailboxInfo) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "List", arg0, arg1, arg2) + ret0, _ := ret[0].(error) + return ret0 +} + +// List indicates an expected call of List +func (mr *MockIMAPClientProviderMockRecorder) List(arg0, arg1, arg2 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "List", reflect.TypeOf((*MockIMAPClientProvider)(nil).List), arg0, arg1, arg2) +} + +// Login mocks base method +func (m *MockIMAPClientProvider) Login(arg0, arg1 string) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "Login", arg0, arg1) + ret0, _ := ret[0].(error) + return ret0 +} + +// Login indicates an expected call of Login +func (mr *MockIMAPClientProviderMockRecorder) Login(arg0, arg1 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Login", reflect.TypeOf((*MockIMAPClientProvider)(nil).Login), arg0, arg1) +} + +// Select mocks base method +func (m *MockIMAPClientProvider) Select(arg0 string, arg1 bool) (*imap.MailboxStatus, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "Select", arg0, arg1) + ret0, _ := ret[0].(*imap.MailboxStatus) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// Select indicates an expected call of Select +func (mr *MockIMAPClientProviderMockRecorder) Select(arg0, arg1 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Select", reflect.TypeOf((*MockIMAPClientProvider)(nil).Select), arg0, arg1) +} + +// State mocks base method +func (m *MockIMAPClientProvider) State() imap.ConnState { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "State") + ret0, _ := ret[0].(imap.ConnState) + return ret0 +} + +// State indicates an expected call of State +func (mr *MockIMAPClientProviderMockRecorder) State() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "State", reflect.TypeOf((*MockIMAPClientProvider)(nil).State)) +} + +// Support mocks base method +func (m *MockIMAPClientProvider) Support(arg0 string) (bool, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "Support", arg0) + ret0, _ := ret[0].(bool) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// Support indicates an expected call of Support +func (mr *MockIMAPClientProviderMockRecorder) Support(arg0 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Support", reflect.TypeOf((*MockIMAPClientProvider)(nil).Support), arg0) +} + +// SupportAuth mocks base method +func (m *MockIMAPClientProvider) SupportAuth(arg0 string) (bool, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "SupportAuth", arg0) + ret0, _ := ret[0].(bool) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// SupportAuth indicates an expected call of SupportAuth +func (mr *MockIMAPClientProviderMockRecorder) SupportAuth(arg0 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SupportAuth", reflect.TypeOf((*MockIMAPClientProvider)(nil).SupportAuth), arg0) +} + +// UidFetch mocks base method +func (m *MockIMAPClientProvider) UidFetch(arg0 *imap.SeqSet, arg1 []imap.FetchItem, arg2 chan *imap.Message) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "UidFetch", arg0, arg1, arg2) + ret0, _ := ret[0].(error) + return ret0 +} + +// UidFetch indicates an expected call of UidFetch +func (mr *MockIMAPClientProviderMockRecorder) UidFetch(arg0, arg1, arg2 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "UidFetch", reflect.TypeOf((*MockIMAPClientProvider)(nil).UidFetch), arg0, arg1, arg2) +} diff --git a/internal/transfer/provider_imap.go b/internal/transfer/provider_imap.go index c2669c49..19cc5214 100644 --- a/internal/transfer/provider_imap.go +++ b/internal/transfer/provider_imap.go @@ -21,28 +21,49 @@ import ( "net" "strings" - imapClient "github.com/emersion/go-imap/client" + "github.com/emersion/go-imap" + "github.com/emersion/go-sasl" ) +type IMAPClientProvider interface { + Capability() (map[string]bool, error) + Support(cap string) (bool, error) + State() imap.ConnState + SupportAuth(mech string) (bool, error) + Authenticate(auth sasl.Client) error + Login(username, password string) error + List(ref, name string, ch chan *imap.MailboxInfo) error + Select(name string, readOnly bool) (*imap.MailboxStatus, error) + Fetch(seqset *imap.SeqSet, items []imap.FetchItem, ch chan *imap.Message) error + UidFetch(seqset *imap.SeqSet, items []imap.FetchItem, ch chan *imap.Message) error +} + // IMAPProvider implements export from IMAP server. type IMAPProvider struct { username string password string addr string - client *imapClient.Client + clientDialer func(addr string) (IMAPClientProvider, error) + client IMAPClientProvider timeIt *timeIt } // NewIMAPProvider returns new IMAPProvider. func NewIMAPProvider(username, password, host, port string) (*IMAPProvider, error) { + return newIMAPProvider(imapClientDial, username, password, host, port) +} + +func newIMAPProvider(clientDialer func(string) (IMAPClientProvider, error), username, password, host, port string) (*IMAPProvider, error) { p := &IMAPProvider{ username: username, password: password, addr: net.JoinHostPort(host, port), timeIt: newTimeIt("imap"), + + clientDialer: clientDialer, } if err := p.auth(); err != nil { diff --git a/internal/transfer/provider_imap_source.go b/internal/transfer/provider_imap_source.go index 9f669a0c..04b68179 100644 --- a/internal/transfer/provider_imap_source.go +++ b/internal/transfer/provider_imap_source.go @@ -84,12 +84,37 @@ func (p *IMAPProvider) loadMessagesInfo(rule *Rule, progress *Progress, uidValid p.timeIt.start("load", rule.SourceMailbox.Name) defer p.timeIt.stop("load", rule.SourceMailbox.Name) + log := log.WithField("mailbox", rule.SourceMailbox.Name) messagesInfo := map[string]imapMessageInfo{} + fetchItems := []imap.FetchItem{imap.FetchUid, imap.FetchRFC822Size} + if rule.HasTimeLimit() { + fetchItems = append(fetchItems, imap.FetchEnvelope) + } + + processMessageCallback := func(imapMessage *imap.Message) { + if rule.HasTimeLimit() { + t := imapMessage.Envelope.Date.Unix() + if t != 0 && !rule.isTimeInRange(t) { + log.WithField("uid", imapMessage.Uid).Debug("Message skipped due to time") + return + } + } + id := getUniqueMessageID(rule.SourceMailbox.Name, uidValidity, imapMessage.Uid) + // We use ID as key to ensure we have every unique message only once. + // Some IMAP servers responded twice the same message... + messagesInfo[id] = imapMessageInfo{ + id: id, + uid: imapMessage.Uid, + size: imapMessage.Size, + } + progress.addMessage(id, []string{rule.SourceMailbox.Name}, rule.TargetMailboxNames()) + } + pageStart := uint32(1) pageEnd := imapPageSize for { - if progress.shouldStop() { + if progress.shouldStop() || pageStart > count { break } @@ -100,45 +125,21 @@ func (p *IMAPProvider) loadMessagesInfo(rule *Rule, progress *Progress, uidValid seqSet := &imap.SeqSet{} seqSet.AddRange(pageStart, pageEnd) - - items := []imap.FetchItem{imap.FetchUid, imap.FetchRFC822Size} - if rule.HasTimeLimit() { - items = append(items, imap.FetchEnvelope) - } - - pageMsgCount := uint32(0) - processMessageCallback := func(imapMessage *imap.Message) { - pageMsgCount++ - if rule.HasTimeLimit() { - t := imapMessage.Envelope.Date.Unix() - if t != 0 && !rule.isTimeInRange(t) { - log.WithField("uid", imapMessage.Uid).Debug("Message skipped due to time") - return + err := p.fetch(rule.SourceMailbox.Name, seqSet, fetchItems, processMessageCallback) + if err != nil { + log.WithError(err).WithField("idx", seqSet).Warning("Load batch fetch failed, trying one by one") + for ; pageStart <= pageEnd; pageStart++ { + seqSet := &imap.SeqSet{} + seqSet.AddNum(pageStart) + if err := p.fetch(rule.SourceMailbox.Name, seqSet, fetchItems, processMessageCallback); err != nil { + log.WithError(err).WithField("idx", seqSet).Warning("Load fetch failed, skipping the message") } } - id := getUniqueMessageID(rule.SourceMailbox.Name, uidValidity, imapMessage.Uid) - // We use ID as key to ensure we have every unique message only once. - // Some IMAP servers responded twice the same message... - messagesInfo[id] = imapMessageInfo{ - id: id, - uid: imapMessage.Uid, - size: imapMessage.Size, - } - progress.addMessage(id, []string{rule.SourceMailbox.Name}, rule.TargetMailboxNames()) } - progress.callWrap(func() error { - return p.fetch(rule.SourceMailbox.Name, seqSet, items, processMessageCallback) - }) - - if pageMsgCount < imapPageSize { - break - } - - pageStart = pageEnd + pageStart = pageEnd + 1 pageEnd += imapPageSize } - return messagesInfo } diff --git a/internal/transfer/provider_imap_test.go b/internal/transfer/provider_imap_test.go new file mode 100644 index 00000000..55443d4c --- /dev/null +++ b/internal/transfer/provider_imap_test.go @@ -0,0 +1,100 @@ +// Copyright (c) 2020 Proton Technologies AG +// +// This file is part of ProtonMail Bridge. +// +// ProtonMail Bridge is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// ProtonMail Bridge is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with ProtonMail Bridge. If not, see . + +package transfer + +import ( + "fmt" + "testing" + + "github.com/emersion/go-imap" + gomock "github.com/golang/mock/gomock" + "github.com/pkg/errors" + r "github.com/stretchr/testify/require" +) + +func newTestIMAPProvider(t *testing.T, m mocks) *IMAPProvider { + m.imapClientProvider.EXPECT().State().Return(imap.ConnectedState).AnyTimes() + m.imapClientProvider.EXPECT().Capability().Return(map[string]bool{ + "AUTH": true, + }, nil).AnyTimes() + + dialer := func(string) (IMAPClientProvider, error) { + return m.imapClientProvider, nil + } + provider, err := newIMAPProvider(dialer, "user", "pass", "host", "42") + r.NoError(t, err) + return provider +} + +func TestProviderIMAPLoadMessagesInfo(t *testing.T) { + m := initMocks(t) + defer m.ctrl.Finish() + + provider := newTestIMAPProvider(t, m) + + progress := newProgress(log, nil) + drainProgressUpdateChannel(&progress) + + rule := &Rule{SourceMailbox: Mailbox{Name: "Mailbox"}} + uidValidity := 1 + count := 2200 + failingIndex := 2100 + + m.imapClientProvider.EXPECT().Select(rule.SourceMailbox.Name, gomock.Any()).Return(&imap.MailboxStatus{}, nil).AnyTimes() + m.imapClientProvider.EXPECT(). + Fetch(gomock.Any(), gomock.Any(), gomock.Any()). + DoAndReturn(func(seqSet *imap.SeqSet, items []imap.FetchItem, ch chan *imap.Message) error { + defer close(ch) + for _, seq := range seqSet.Set { + for i := seq.Start; i <= seq.Stop; i++ { + if int(i) == failingIndex { + return errors.New("internal server error") + } + ch <- &imap.Message{ + SeqNum: i, + Uid: i * 10, + Size: i * 100, + } + } + } + return nil + }). + // 2200 messages is split into two batches (2000 and 200), + // the second one fails and makes 200 calls (one-by-one). + // Plus two failed requests are repeated `imapRetries` times. + Times(2 + 200 + (2 * (imapRetries - 1))) + + messageInfo := provider.loadMessagesInfo(rule, &progress, uint32(uidValidity), uint32(count)) + + r.Equal(t, count-1, len(messageInfo)) // One message produces internal server error. + for index := 1; index <= count; index++ { + uid := index * 10 + key := fmt.Sprintf("%s_%d:%d", rule.SourceMailbox.Name, uidValidity, uid) + + if index == failingIndex { + r.Empty(t, messageInfo[key]) + continue + } + + r.Equal(t, imapMessageInfo{ + id: key, + uid: uint32(uid), + size: uint32(index * 100), + }, messageInfo[key]) + } +} diff --git a/internal/transfer/provider_imap_utils.go b/internal/transfer/provider_imap_utils.go index 16f2166e..4dbda047 100644 --- a/internal/transfer/provider_imap_utils.go +++ b/internal/transfer/provider_imap_utils.go @@ -24,10 +24,11 @@ import ( "time" imapID "github.com/ProtonMail/go-imap-id" + "github.com/ProtonMail/proton-bridge/pkg/constants" "github.com/ProtonMail/proton-bridge/pkg/pmapi" "github.com/emersion/go-imap" imapClient "github.com/emersion/go-imap/client" - sasl "github.com/emersion/go-sasl" + "github.com/emersion/go-sasl" "github.com/pkg/errors" "github.com/sirupsen/logrus" ) @@ -51,6 +52,43 @@ func (l *imapErrorLogger) Println(v ...interface{}) { l.log.Errorln(v...) } +func imapClientDial(addr string) (IMAPClientProvider, error) { + if _, err := net.DialTimeout("tcp", addr, imapDialTimeout); err != nil { + return nil, errors.Wrap(err, "failed to dial server") + } + + client, err := imapClientDialHelper(addr) + if err == nil { + client.ErrorLog = &imapErrorLogger{logrus.WithField("pkg", "imap-client")} + // Logrus `WriterLevel` fails for big messages because of bufio.MaxScanTokenSize limit. + // Also, this spams a lot, uncomment once needed during development. + //client.SetDebug(imap.NewDebugWriter( + // logrus.WithField("pkg", "imap/client").WriterLevel(logrus.TraceLevel), + // logrus.WithField("pkg", "imap/server").WriterLevel(logrus.TraceLevel), + //)) + } + return client, err +} + +func imapClientDialHelper(addr string) (*imapClient.Client, error) { + host, _, _ := net.SplitHostPort(addr) + if host == "127.0.0.1" { + return imapClient.Dial(addr) + } + + // IMAP mail.yahoo.com has problem with golang TLS 1.3 implementation + // with weird behaviour, i.e., Yahoo does not return error during dial + // or handshake but server does logs out right after successful login + // leaving no time to perform any action. + // Limiting TLS to version 1.2 is working just fine. + var tlsConf *tls.Config + if strings.Contains(strings.ToLower(host), "yahoo") { + log.Warning("Yahoo server detected: limiting maximal TLS version to 1.2.") + tlsConf = &tls.Config{MaxVersion: tls.VersionTLS12} + } + return imapClient.DialTLS(addr, tlsConf) +} + func (p *IMAPProvider) ensureConnection(callback func() error) error { return p.ensureConnectionAndSelection(callback, "") } @@ -138,41 +176,10 @@ func (p *IMAPProvider) auth() error { //nolint[funlen] log.Info("Connecting to server") - if _, err := net.DialTimeout("tcp", p.addr, imapDialTimeout); err != nil { - return ErrIMAPConnection{imapError{Err: err, Message: "failed to dial server"}} - } - - var client *imapClient.Client - var err error - host, _, _ := net.SplitHostPort(p.addr) - if host == "127.0.0.1" { - client, err = imapClient.Dial(p.addr) - } else { - // IMAP.mail.yahoo.com have problem with golang TLS1.3 - // implementation with weird behaviour i.e. Yahoo - // no error during dial or handshake but server logs out right - // after successful login leaving no time to perform any - // action. It was discovered that limiting to maximum TLS - // version 1.2 for yahoo servers is working solution. - - var tlsConf *tls.Config - if strings.Contains(strings.ToLower(host), "yahoo") { - log.Warning("Yahoo server detected: limiting maximal TLS version to 1.2.") - tlsConf = &tls.Config{MaxVersion: tls.VersionTLS12} - } - client, err = imapClient.DialTLS(p.addr, tlsConf) - } + client, err := p.clientDialer(p.addr) if err != nil { return ErrIMAPConnection{imapError{Err: err, Message: "failed to connect to server"}} } - - client.ErrorLog = &imapErrorLogger{logrus.WithField("pkg", "imap-client")} - // Logrus `WriterLevel` fails for big messages because of bufio.MaxScanTokenSize limit. - // Also, this spams a lot, uncomment once needed during development. - //client.SetDebug(imap.NewDebugWriter( - // logrus.WithField("pkg", "imap/client").WriterLevel(logrus.TraceLevel), - // logrus.WithField("pkg", "imap/server").WriterLevel(logrus.TraceLevel), - //)) p.client = client log.Info("Connected") @@ -210,13 +217,15 @@ func (p *IMAPProvider) auth() error { //nolint[funlen] log.Info("Logged in") - idClient := imapID.NewClient(p.client) - if ok, err := idClient.SupportID(); err == nil && ok { - serverID, err := idClient.ID(imapID.ID{ - imapID.FieldName: "ImportExport", - imapID.FieldVersion: "beta", - }) - log.WithField("ID", serverID).WithError(err).Debug("Server info") + if c, ok := p.client.(*imapClient.Client); ok { + idClient := imapID.NewClient(c) + if ok, err := idClient.SupportID(); err == nil && ok { + serverID, err := idClient.ID(imapID.ID{ + imapID.FieldName: "ImportExport", + imapID.FieldVersion: constants.Version, + }) + log.WithField("ID", serverID).WithError(err).Debug("Server info") + } } return err diff --git a/internal/transfer/transfer_test.go b/internal/transfer/transfer_test.go index 653407f9..f71e6d9c 100644 --- a/internal/transfer/transfer_test.go +++ b/internal/transfer/transfer_test.go @@ -31,11 +31,12 @@ import ( type mocks struct { t *testing.T - ctrl *gomock.Controller - panicHandler *transfermocks.MockPanicHandler - clientManager *transfermocks.MockClientManager - pmapiClient *pmapimocks.MockClient - pmapiConfig *pmapi.ClientConfig + ctrl *gomock.Controller + panicHandler *transfermocks.MockPanicHandler + clientManager *transfermocks.MockClientManager + imapClientProvider *transfermocks.MockIMAPClientProvider + pmapiClient *pmapimocks.MockClient + pmapiConfig *pmapi.ClientConfig keyring *crypto.KeyRing } @@ -46,12 +47,13 @@ func initMocks(t *testing.T) mocks { m := mocks{ t: t, - ctrl: mockCtrl, - panicHandler: transfermocks.NewMockPanicHandler(mockCtrl), - clientManager: transfermocks.NewMockClientManager(mockCtrl), - pmapiClient: pmapimocks.NewMockClient(mockCtrl), - pmapiConfig: &pmapi.ClientConfig{}, - keyring: newTestKeyring(), + ctrl: mockCtrl, + panicHandler: transfermocks.NewMockPanicHandler(mockCtrl), + clientManager: transfermocks.NewMockClientManager(mockCtrl), + imapClientProvider: transfermocks.NewMockIMAPClientProvider(mockCtrl), + pmapiClient: pmapimocks.NewMockClient(mockCtrl), + pmapiConfig: &pmapi.ClientConfig{}, + keyring: newTestKeyring(), } m.clientManager.EXPECT().GetClient("user").Return(m.pmapiClient).AnyTimes()