diff --git a/Makefile b/Makefile
index 58a59637..1a492412 100644
--- a/Makefile
+++ b/Makefile
@@ -290,8 +290,10 @@ gofiles: ./internal/bridge/credits.go ./internal/importexport/credits.go
## Run and debug
.PHONY: run run-qt run-qt-cli run-nogui run-nogui-cli run-debug run-qml-preview run-ie-qml-preview run-ie run-ie-qt run-ie-qt-cli run-ie-nogui run-ie-nogui-cli clean-vendor clean-frontend-qt clean-frontend-qt-ie clean-frontend-qt-common clean
-VERBOSITY?=debug
-RUN_FLAGS:=-m -l=${VERBOSITY}
+LOG?=debug
+LOG_IMAP?=client # client/server/all, or empty to turn it off
+LOG_SMTP?=--log-smtp # empty to turn it off
+RUN_FLAGS?=-m -l=${LOG} --log-imap=${LOG_IMAP} ${LOG_SMTP}
run: run-nogui-cli
diff --git a/go.sum b/go.sum
index 32090e86..9aaae71f 100644
--- a/go.sum
+++ b/go.sum
@@ -274,6 +274,8 @@ github.com/ugorji/go v1.1.4/go.mod h1:uQMGLiO92mf5W77hV/PUCpI3pbzQx3CRekS0kk+RGr
github.com/ugorji/go v1.1.7/go.mod h1:kZn38zHttfInRq0xu/PH0az30d+z6vm202qpg1oXVMw=
github.com/ugorji/go/codec v0.0.0-20181204163529-d75b2dcb6bc8/go.mod h1:VFNgLljTbGfSG7qAOspJ7OScBnGdDN/yBr0sguwnwf0=
github.com/ugorji/go/codec v1.1.7/go.mod h1:Ax+UKWsSmolVDwsd+7N3ZtXu+yMGCf907BLYF3GoBXY=
+github.com/urfave/cli v1.22.4 h1:u7tSpNPPswAFymm8IehJhy4uJMlUuU/GmqSkvJ1InXA=
+github.com/urfave/cli v1.22.4/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0=
github.com/urfave/cli/v2 v2.2.0 h1:JTTnM6wKzdA0Jqodd966MVj4vWbbquZykeX1sKbe2C4=
github.com/urfave/cli/v2 v2.2.0/go.mod h1:SE9GqnLQmjVa0iPEY0f1w3ygNIYcIJ0OKPMoW2caLfQ=
github.com/urfave/negroni v1.0.0/go.mod h1:Meg73S6kFm/4PpbYdq35yYWoCZ9mS/YSx+lKnmiohz4=
diff --git a/internal/app/base/base.go b/internal/app/base/base.go
index 46232bd9..7ce454d5 100644
--- a/internal/app/base/base.go
+++ b/internal/app/base/base.go
@@ -158,13 +158,13 @@ func New( // nolint[funlen]
apiConfig := pmapi.GetAPIConfig(configName, constants.Version)
apiConfig.ConnectionOffHandler = func() {
- eventListener.Emit(events.InternetOffEvent, "")
+ listener.Emit(events.InternetOffEvent, "")
}
apiConfig.ConnectionOnHandler = func() {
- eventListener.Emit(events.InternetOnEvent, "")
+ listener.Emit(events.InternetOnEvent, "")
}
apiConfig.UpgradeApplicationHandler = func() {
- eventListener.Emit(events.UpgradeApplicationEvent, "")
+ listener.Emit(events.UpgradeApplicationEvent, "")
}
cm := pmapi.NewClientManager(apiConfig)
cm.SetRoundTripper(pmapi.GetRoundTripper(cm, listener))
diff --git a/internal/bridge/credits.go b/internal/bridge/credits.go
index 30e6ef9d..b7205f24 100644
--- a/internal/bridge/credits.go
+++ b/internal/bridge/credits.go
@@ -15,8 +15,8 @@
// You should have received a copy of the GNU General Public License
// along with ProtonMail Bridge. If not, see .
-// Code generated by ./credits.sh at Mon Jan 4 03:19:07 PM CET 2021. DO NOT EDIT.
+// Code generated by ./credits.sh at Fri Jan 22 11:28:55 CET 2021. DO NOT EDIT.
package bridge
-const Credits = "github.com/0xAX/notificator;github.com/abiosoft/ishell;github.com/abiosoft/readline;github.com/allan-simon/go-singleinstance;github.com/chzyer/logex;github.com/chzyer/test;github.com/cucumber/godog;github.com/docker/docker-credential-helpers;github.com/emersion/go-imap;github.com/emersion/go-imap-appendlimit;github.com/emersion/go-imap-idle;github.com/emersion/go-imap-move;github.com/emersion/go-imap-quota;github.com/emersion/go-imap-specialuse;github.com/emersion/go-imap-unselect;github.com/emersion/go-mbox;github.com/emersion/go-message;github.com/emersion/go-sasl;github.com/emersion/go-smtp;github.com/emersion/go-textwrapper;github.com/emersion/go-vcard;github.com/fatih/color;github.com/flynn-archive/go-shlex;github.com/getsentry/sentry-go;github.com/golang/mock;github.com/google/go-cmp;github.com/google/uuid;github.com/gopherjs/gopherjs;github.com/go-resty/resty/v2;github.com/hashicorp/go-multierror;github.com/jameskeane/bcrypt;github.com/jaytaylor/html2text;github.com/keybase/go-keychain;github.com/logrusorgru/aurora;github.com/Masterminds/semver/v3;github.com/mattn/go-runewidth;github.com/miekg/dns;github.com/myesui/uuid;github.com/nsf/jsondiff;github.com/olekukonko/tablewriter;github.com/pkg/errors;github.com/ProtonMail/bcrypt;github.com/ProtonMail/crypto;github.com/ProtonMail/docker-credential-helpers;github.com/ProtonMail/go-appdir;github.com/ProtonMail/go-apple-mobileconfig;github.com/ProtonMail/go-autostart;github.com/ProtonMail/go-imap;github.com/ProtonMail/go-imap-id;github.com/ProtonMail/gopenpgp/v2;github.com/ProtonMail/go-rfc5322;github.com/ProtonMail/go-vcard;github.com/PuerkitoBio/goquery;github.com/sirupsen/logrus;github.com/skratchdot/open-golang;github.com/ssor/bom;github.com/stretchr/testify;github.com/therecipe/qt;github.com/twinj/uuid;github.com/urfave/cli/v2;go.etcd.io/bbolt;golang.org/x/crypto;golang.org/x/net;golang.org/x/text;gopkg.in/stretchr/testify.v1;;Font Awesome 4.7.0;;Qt 5.13 by Qt group;"
+const Credits = "github.com/0xAX/notificator;github.com/Masterminds/semver/v3;github.com/ProtonMail/bcrypt;github.com/ProtonMail/crypto;github.com/ProtonMail/docker-credential-helpers;github.com/ProtonMail/go-appdir;github.com/ProtonMail/go-apple-mobileconfig;github.com/ProtonMail/go-autostart;github.com/ProtonMail/go-imap;github.com/ProtonMail/go-imap-id;github.com/ProtonMail/go-rfc5322;github.com/ProtonMail/go-vcard;github.com/ProtonMail/gopenpgp/v2;github.com/PuerkitoBio/goquery;github.com/abiosoft/ishell;github.com/abiosoft/readline;github.com/allan-simon/go-singleinstance;github.com/chzyer/logex;github.com/chzyer/test;github.com/cucumber/godog;github.com/docker/docker-credential-helpers;github.com/emersion/go-imap;github.com/emersion/go-imap-appendlimit;github.com/emersion/go-imap-idle;github.com/emersion/go-imap-move;github.com/emersion/go-imap-quota;github.com/emersion/go-imap-unselect;github.com/emersion/go-mbox;github.com/emersion/go-message;github.com/emersion/go-sasl;github.com/emersion/go-smtp;github.com/emersion/go-textwrapper;github.com/emersion/go-vcard;github.com/fatih/color;github.com/flynn-archive/go-shlex;github.com/getsentry/sentry-go;github.com/go-resty/resty/v2;github.com/golang/mock;github.com/google/go-cmp;github.com/google/uuid;github.com/gopherjs/gopherjs;github.com/hashicorp/go-multierror;github.com/jameskeane/bcrypt;github.com/jaytaylor/html2text;github.com/keybase/go-keychain;github.com/logrusorgru/aurora;github.com/mattn/go-runewidth;github.com/miekg/dns;github.com/myesui/uuid;github.com/nsf/jsondiff;github.com/olekukonko/tablewriter;github.com/pkg/errors;github.com/sirupsen/logrus;github.com/skratchdot/open-golang;github.com/ssor/bom;github.com/stretchr/testify;github.com/therecipe/qt;github.com/twinj/uuid;github.com/urfave/cli;github.com/urfave/cli/v2;github.com/vmihailenco/msgpack/v5;go.etcd.io/bbolt;golang.org/x/crypto;golang.org/x/net;golang.org/x/text;gopkg.in/stretchr/testify.v1;;Font Awesome 4.7.0;;Qt 5.13 by Qt group;"
diff --git a/internal/imap/cache/cache.go b/internal/imap/cache/cache.go
index 54ce89e3..6048f460 100644
--- a/internal/imap/cache/cache.go
+++ b/internal/imap/cache/cache.go
@@ -23,7 +23,7 @@ import (
"sync"
"time"
- backendMessage "github.com/ProtonMail/proton-bridge/pkg/message"
+ pkgMsg "github.com/ProtonMail/proton-bridge/pkg/message"
)
type key struct {
@@ -41,7 +41,7 @@ func (s oldestFirst) Less(i, j int) bool { return s[i].Timestamp < s[j].Timestam
type cachedMessage struct {
key
data []byte
- structure backendMessage.BodyStructure
+ structure pkgMsg.BodyStructure
}
//nolint[gochecknoglobals]
@@ -101,7 +101,7 @@ func BuildUnlock(messageID string) {
delete(buildLocks, messageID)
}
-func LoadMail(mID string) (reader *bytes.Reader, structure *backendMessage.BodyStructure) {
+func LoadMail(mID string) (reader *bytes.Reader, structure *pkgMsg.BodyStructure) {
reader = &bytes.Reader{}
cacheMutex.Lock()
defer cacheMutex.Unlock()
@@ -115,7 +115,7 @@ func LoadMail(mID string) (reader *bytes.Reader, structure *backendMessage.BodyS
return
}
-func SaveMail(mID string, msg []byte, structure *backendMessage.BodyStructure) {
+func SaveMail(mID string, msg []byte, structure *pkgMsg.BodyStructure) {
cacheMutex.Lock()
defer cacheMutex.Unlock()
diff --git a/internal/imap/cache/cache_test.go b/internal/imap/cache/cache_test.go
index 38e6c21a..90696239 100644
--- a/internal/imap/cache/cache_test.go
+++ b/internal/imap/cache/cache_test.go
@@ -22,11 +22,11 @@ import (
"testing"
"time"
- bckMsg "github.com/ProtonMail/proton-bridge/pkg/message"
+ pkgMsg "github.com/ProtonMail/proton-bridge/pkg/message"
"github.com/stretchr/testify/require"
)
-var bs = &bckMsg.BodyStructure{} //nolint[gochecknoglobals]
+var bs = &pkgMsg.BodyStructure{} //nolint[gochecknoglobals]
const testUID = "testmsg"
func TestSaveAndLoad(t *testing.T) {
diff --git a/internal/imap/mailbox.go b/internal/imap/mailbox.go
index 3593112b..eea2fead 100644
--- a/internal/imap/mailbox.go
+++ b/internal/imap/mailbox.go
@@ -197,8 +197,8 @@ func (im *imapMailbox) Expunge() error {
// UIDExpunge permanently removes messages that have the \Deleted flag set
// and UID passed from SeqSet from the currently selected mailbox.
func (im *imapMailbox) UIDExpunge(seqSet *imap.SeqSet) error {
- im.user.backend.setUpdatesBeBlocking(im.user.currentAddressLowercase, im.name, operationDeleteMessage)
- defer im.user.backend.unsetUpdatesBeBlocking(im.user.currentAddressLowercase, im.name, operationDeleteMessage)
+ im.user.backend.updates.block(im.user.currentAddressLowercase, im.name, operationDeleteMessage)
+ defer im.user.backend.updates.unblock(im.user.currentAddressLowercase, im.name, operationDeleteMessage)
messageIDs, err := im.apiIDsFromSeqSet(true, seqSet)
if err != nil || len(messageIDs) == 0 {
diff --git a/internal/imap/mailbox_message.go b/internal/imap/mailbox_message.go
index 1673d905..2899093a 100644
--- a/internal/imap/mailbox_message.go
+++ b/internal/imap/mailbox_message.go
@@ -328,12 +328,21 @@ func (im *imapMailbox) getBodyAndStructure(storeMessage storeMessageProvider) (
var body []byte
structure, body, err = im.buildMessage(m)
m.Size = int64(len(body))
+ // Save size and body structure even for messages unable to decrypt
+ // so the size or body structure doesn't have to be computed every time.
if err := storeMessage.SetSize(m.Size); err != nil {
im.log.WithError(err).
WithField("newSize", m.Size).
WithField("msgID", m.ID).
Warn("Cannot update size while building")
}
+ if structure != nil && !isMessageInDraftFolder(m) {
+ if err := storeMessage.SetBodyStructure(structure); err != nil {
+ im.log.WithError(err).
+ WithField("msgID", m.ID).
+ Warn("Cannot update bodystructure while building")
+ }
+ }
if err == nil && structure != nil && len(body) > 0 {
if err := storeMessage.SetContentTypeAndHeader(m.MIMEType, m.Header); err != nil {
im.log.WithError(err).
@@ -342,11 +351,6 @@ func (im *imapMailbox) getBodyAndStructure(storeMessage storeMessageProvider) (
}
// Drafts can change and we don't want to cache them.
if !isMessageInDraftFolder(m) {
- if err := storeMessage.SetBodyStructure(structure); err != nil {
- im.log.WithError(err).
- WithField("msgID", m.ID).
- Warn("Cannot update bodystructure while building")
- }
cache.SaveMail(id, body, structure)
}
bodyReader = bytes.NewReader(body)
diff --git a/internal/imap/server.go b/internal/imap/server.go
index c1ca74d0..b00b46a4 100644
--- a/internal/imap/server.go
+++ b/internal/imap/server.go
@@ -23,6 +23,7 @@ import (
"io"
"net"
"strings"
+ "sync/atomic"
"time"
imapid "github.com/ProtonMail/go-imap-id"
@@ -31,6 +32,7 @@ import (
"github.com/ProtonMail/proton-bridge/internal/imap/id"
"github.com/ProtonMail/proton-bridge/internal/imap/uidplus"
"github.com/ProtonMail/proton-bridge/pkg/listener"
+ "github.com/ProtonMail/proton-bridge/pkg/ports"
"github.com/emersion/go-imap"
imapappendlimit "github.com/emersion/go-imap-appendlimit"
imapidle "github.com/emersion/go-imap-idle"
@@ -48,6 +50,8 @@ type imapServer struct {
eventListener listener.Listener
debugClient bool
debugServer bool
+ port int
+ isRunning atomic.Value
}
// NewIMAPServer constructs a new IMAP server configured with the given options.
@@ -96,13 +100,16 @@ func NewIMAPServer(panicHandler panicHandler, debugClient, debugServer bool, por
uidplus.NewExtension(),
)
- return &imapServer{
+ server := &imapServer{
panicHandler: panicHandler,
server: s,
eventListener: eventListener,
debugClient: debugClient,
debugServer: debugServer,
+ port: port,
}
+ server.isRunning.Store(false)
+ return server
}
// Starts the server.
@@ -114,6 +121,11 @@ func (s *imapServer) ListenAndServe() {
}
func (s *imapServer) listenAndServe() {
+ if s.isRunning.Load().(bool) {
+ return
+ }
+ s.isRunning.Store(true)
+
log.Info("IMAP server listening at ", s.server.Addr)
l, err := net.Listen("tcp", s.server.Addr)
if err != nil {
@@ -126,19 +138,13 @@ func (s *imapServer) listenAndServe() {
Listener: l,
server: s,
})
- if err != nil {
- failed := true
- if netErr, ok := err.(*net.OpError); ok {
- originalErr := netErr.Unwrap()
- if originalErr != nil && originalErr.Error() == "use of closed network connection" {
- failed = false
- }
- }
- if failed {
- s.eventListener.Emit(events.ErrorEvent, "IMAP failed: "+err.Error())
- log.Error("IMAP failed: ", err)
- return
- }
+ // Serve returns error every time, even after closing the server.
+ // User shouldn't be notified about error if server shouldn't be running,
+ // but it should in case it was not closed by `s.Close()`.
+ if err != nil && s.isRunning.Load().(bool) {
+ s.eventListener.Emit(events.ErrorEvent, "IMAP failed: "+err.Error())
+ log.Error("IMAP failed: ", err)
+ return
}
defer s.server.Close() //nolint[errcheck]
@@ -147,6 +153,11 @@ func (s *imapServer) listenAndServe() {
// Stops the server.
func (s *imapServer) Close() {
+ if !s.isRunning.Load().(bool) {
+ return
+ }
+ s.isRunning.Store(false)
+
log.Info("Closing IMAP server")
if err := s.server.Close(); err != nil {
log.WithError(err).Error("Failed to close the connection")
@@ -159,29 +170,32 @@ func (s *imapServer) monitorInternetConnection() {
off := make(chan string)
s.eventListener.Add(events.InternetOffEvent, off)
- isOn := true
for {
+ var expectedIsPortFree bool
select {
case <-on:
- if isOn {
- continue
- }
- isOn = true
go func() {
defer s.panicHandler.HandlePanic()
s.listenAndServe()
}()
+ expectedIsPortFree = false
case <-off:
- if !isOn {
- continue
- }
- isOn = false
s.Close()
+ expectedIsPortFree = true
+ }
+
+ start := time.Now()
+ for {
+ if ports.IsPortFree(s.port) == expectedIsPortFree {
+ break
+ }
+ // Safety stop if something went wrong.
+ if time.Since(start) > 15*time.Second {
+ log.WithField("expectedIsPortFree", expectedIsPortFree).Warn("Server start/stop check timeouted")
+ break
+ }
+ time.Sleep(100 * time.Millisecond)
}
- // Give it some time to serve or close server before changing it again.
- // E.g., if we get quickly off-on signal, starting or closing could
- // fail because server is still running or not yet, respectively.
- time.Sleep(10 * time.Second)
}
}
diff --git a/internal/imap/server_test.go b/internal/imap/server_test.go
new file mode 100644
index 00000000..565e045e
--- /dev/null
+++ b/internal/imap/server_test.go
@@ -0,0 +1,65 @@
+// Copyright (c) 2021 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 imap
+
+import (
+ "fmt"
+ "testing"
+ "time"
+
+ "github.com/ProtonMail/proton-bridge/internal/bridge"
+ "github.com/ProtonMail/proton-bridge/internal/events"
+ "github.com/ProtonMail/proton-bridge/pkg/listener"
+ "github.com/ProtonMail/proton-bridge/pkg/ports"
+ imapserver "github.com/emersion/go-imap/server"
+
+ "github.com/stretchr/testify/require"
+)
+
+type testPanicHandler struct{}
+
+func (ph *testPanicHandler) HandlePanic() {}
+
+func TestIMAPServerTurnOffAndOnAgain(t *testing.T) {
+ panicHandler := &testPanicHandler{}
+
+ eventListener := listener.New()
+
+ port := ports.FindFreePortFrom(12345)
+ server := imapserver.New(nil)
+ server.Addr = fmt.Sprintf("%v:%v", bridge.Host, port)
+
+ s := &imapServer{
+ panicHandler: panicHandler,
+ server: server,
+ eventListener: eventListener,
+ }
+ s.isRunning.Store(false)
+
+ go s.ListenAndServe()
+ time.Sleep(5 * time.Second)
+ require.False(t, ports.IsPortFree(port))
+
+ eventListener.Emit(events.InternetOffEvent, "")
+ time.Sleep(10 * time.Second)
+ require.True(t, ports.IsPortFree(port))
+
+ eventListener.Emit(events.InternetOnEvent, "")
+ time.Sleep(10 * time.Second)
+ require.False(t, ports.IsPortFree(port))
+}
diff --git a/internal/imap/store.go b/internal/imap/store.go
index 4970ca13..005108c3 100644
--- a/internal/imap/store.go
+++ b/internal/imap/store.go
@@ -24,7 +24,7 @@ import (
"github.com/ProtonMail/gopenpgp/v2/crypto"
"github.com/ProtonMail/proton-bridge/internal/imap/uidplus"
"github.com/ProtonMail/proton-bridge/internal/store"
- backendMessage "github.com/ProtonMail/proton-bridge/pkg/message"
+ pkgMsg "github.com/ProtonMail/proton-bridge/pkg/message"
"github.com/ProtonMail/proton-bridge/pkg/pmapi"
)
@@ -100,8 +100,8 @@ type storeMessageProvider interface {
SetSize(int64) error
SetContentTypeAndHeader(string, mail.Header) error
- SetBodyStructure(*backendMessage.BodyStructure) error
- GetBodyStructure() (*backendMessage.BodyStructure, error)
+ SetBodyStructure(*pkgMsg.BodyStructure) error
+ GetBodyStructure() (*pkgMsg.BodyStructure, error)
}
type storeUserWrap struct {
diff --git a/internal/imap/updates_test.go b/internal/imap/updates_test.go
new file mode 100644
index 00000000..7390461f
--- /dev/null
+++ b/internal/imap/updates_test.go
@@ -0,0 +1,60 @@
+// Copyright (c) 2021 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 imap
+
+import (
+ "testing"
+ "time"
+
+ "github.com/stretchr/testify/require"
+)
+
+func TestUpdatesCanDelete(t *testing.T) {
+ u := newIMAPUpdates()
+
+ can, _ := u.CanDelete("mbox")
+ require.True(t, can)
+
+ u.forbidExpunge("mbox")
+ u.allowExpunge("mbox")
+
+ can, _ = u.CanDelete("mbox")
+ require.True(t, can)
+}
+
+func TestUpdatesCannotDelete(t *testing.T) {
+ u := newIMAPUpdates()
+
+ u.forbidExpunge("mbox")
+ can, wait := u.CanDelete("mbox")
+ require.False(t, can)
+
+ ch := make(chan time.Duration)
+ go func() {
+ start := time.Now()
+ wait()
+ ch <- time.Since(start)
+ close(ch)
+ }()
+
+ time.Sleep(200 * time.Millisecond)
+ u.allowExpunge("mbox")
+ duration := <-ch
+
+ require.True(t, duration > 200*time.Millisecond)
+}
diff --git a/internal/importexport/credits.go b/internal/importexport/credits.go
index 5be3cb3d..89feddd1 100644
--- a/internal/importexport/credits.go
+++ b/internal/importexport/credits.go
@@ -15,8 +15,8 @@
// You should have received a copy of the GNU General Public License
// along with ProtonMail Bridge. If not, see .
-// Code generated by ./credits.sh at Mon Jan 4 03:19:07 PM CET 2021. DO NOT EDIT.
+// Code generated by ./credits.sh at Fri Jan 22 11:28:55 CET 2021. DO NOT EDIT.
package importexport
-const Credits = "github.com/0xAX/notificator;github.com/abiosoft/ishell;github.com/abiosoft/readline;github.com/allan-simon/go-singleinstance;github.com/chzyer/logex;github.com/chzyer/test;github.com/cucumber/godog;github.com/docker/docker-credential-helpers;github.com/emersion/go-imap;github.com/emersion/go-imap-appendlimit;github.com/emersion/go-imap-idle;github.com/emersion/go-imap-move;github.com/emersion/go-imap-quota;github.com/emersion/go-imap-specialuse;github.com/emersion/go-imap-unselect;github.com/emersion/go-mbox;github.com/emersion/go-message;github.com/emersion/go-sasl;github.com/emersion/go-smtp;github.com/emersion/go-textwrapper;github.com/emersion/go-vcard;github.com/fatih/color;github.com/flynn-archive/go-shlex;github.com/getsentry/sentry-go;github.com/golang/mock;github.com/google/go-cmp;github.com/google/uuid;github.com/gopherjs/gopherjs;github.com/go-resty/resty/v2;github.com/hashicorp/go-multierror;github.com/jameskeane/bcrypt;github.com/jaytaylor/html2text;github.com/keybase/go-keychain;github.com/logrusorgru/aurora;github.com/Masterminds/semver/v3;github.com/mattn/go-runewidth;github.com/miekg/dns;github.com/myesui/uuid;github.com/nsf/jsondiff;github.com/olekukonko/tablewriter;github.com/pkg/errors;github.com/ProtonMail/bcrypt;github.com/ProtonMail/crypto;github.com/ProtonMail/docker-credential-helpers;github.com/ProtonMail/go-appdir;github.com/ProtonMail/go-apple-mobileconfig;github.com/ProtonMail/go-autostart;github.com/ProtonMail/go-imap;github.com/ProtonMail/go-imap-id;github.com/ProtonMail/gopenpgp/v2;github.com/ProtonMail/go-rfc5322;github.com/ProtonMail/go-vcard;github.com/PuerkitoBio/goquery;github.com/sirupsen/logrus;github.com/skratchdot/open-golang;github.com/ssor/bom;github.com/stretchr/testify;github.com/therecipe/qt;github.com/twinj/uuid;github.com/urfave/cli/v2;go.etcd.io/bbolt;golang.org/x/crypto;golang.org/x/net;golang.org/x/text;gopkg.in/stretchr/testify.v1;;Font Awesome 4.7.0;;Qt 5.13 by Qt group;"
+const Credits = "github.com/0xAX/notificator;github.com/Masterminds/semver/v3;github.com/ProtonMail/bcrypt;github.com/ProtonMail/crypto;github.com/ProtonMail/docker-credential-helpers;github.com/ProtonMail/go-appdir;github.com/ProtonMail/go-apple-mobileconfig;github.com/ProtonMail/go-autostart;github.com/ProtonMail/go-imap;github.com/ProtonMail/go-imap-id;github.com/ProtonMail/go-rfc5322;github.com/ProtonMail/go-vcard;github.com/ProtonMail/gopenpgp/v2;github.com/PuerkitoBio/goquery;github.com/abiosoft/ishell;github.com/abiosoft/readline;github.com/allan-simon/go-singleinstance;github.com/chzyer/logex;github.com/chzyer/test;github.com/cucumber/godog;github.com/docker/docker-credential-helpers;github.com/emersion/go-imap;github.com/emersion/go-imap-appendlimit;github.com/emersion/go-imap-idle;github.com/emersion/go-imap-move;github.com/emersion/go-imap-quota;github.com/emersion/go-imap-unselect;github.com/emersion/go-mbox;github.com/emersion/go-message;github.com/emersion/go-sasl;github.com/emersion/go-smtp;github.com/emersion/go-textwrapper;github.com/emersion/go-vcard;github.com/fatih/color;github.com/flynn-archive/go-shlex;github.com/getsentry/sentry-go;github.com/go-resty/resty/v2;github.com/golang/mock;github.com/google/go-cmp;github.com/google/uuid;github.com/gopherjs/gopherjs;github.com/hashicorp/go-multierror;github.com/jameskeane/bcrypt;github.com/jaytaylor/html2text;github.com/keybase/go-keychain;github.com/logrusorgru/aurora;github.com/mattn/go-runewidth;github.com/miekg/dns;github.com/myesui/uuid;github.com/nsf/jsondiff;github.com/olekukonko/tablewriter;github.com/pkg/errors;github.com/sirupsen/logrus;github.com/skratchdot/open-golang;github.com/ssor/bom;github.com/stretchr/testify;github.com/therecipe/qt;github.com/twinj/uuid;github.com/urfave/cli;github.com/urfave/cli/v2;github.com/vmihailenco/msgpack/v5;go.etcd.io/bbolt;golang.org/x/crypto;golang.org/x/net;golang.org/x/text;gopkg.in/stretchr/testify.v1;;Font Awesome 4.7.0;;Qt 5.13 by Qt group;"
diff --git a/internal/smtp/user.go b/internal/smtp/user.go
index fd857dee..a6dc829f 100644
--- a/internal/smtp/user.go
+++ b/internal/smtp/user.go
@@ -31,7 +31,7 @@ import (
"github.com/ProtonMail/gopenpgp/v2/crypto"
"github.com/ProtonMail/proton-bridge/internal/events"
"github.com/ProtonMail/proton-bridge/pkg/listener"
- pkgMessage "github.com/ProtonMail/proton-bridge/pkg/message"
+ pkgMsg "github.com/ProtonMail/proton-bridge/pkg/message"
"github.com/ProtonMail/proton-bridge/pkg/message/parser"
"github.com/ProtonMail/proton-bridge/pkg/pmapi"
goSMTPBackend "github.com/emersion/go-smtp"
@@ -229,7 +229,7 @@ func (su *smtpUser) Send(returnPath string, to []string, messageReader io.Reader
err = errors.Wrap(err, "failed to create new parser")
return
}
- message, plainBody, attReaders, err := pkgMessage.ParserWithParser(parser)
+ message, plainBody, attReaders, err := pkgMsg.ParserWithParser(parser)
if err != nil {
log.WithError(err).Error("Failed to parse message")
return
@@ -273,10 +273,10 @@ func (su *smtpUser) Send(returnPath string, to []string, messageReader io.Reader
}
if attachedPublicKey != "" {
- pkgMessage.AttachPublicKey(parser, attachedPublicKey, attachedPublicKeyName)
+ pkgMsg.AttachPublicKey(parser, attachedPublicKey, attachedPublicKeyName)
}
- mimeBody, err := pkgMessage.BuildMIMEBody(parser)
+ mimeBody, err := pkgMsg.BuildMIMEBody(parser)
if err != nil {
log.WithError(err).Error("Failed to build message")
return
diff --git a/internal/store/event_loop_test.go b/internal/store/event_loop_test.go
index 139c4e44..bf79bc6f 100644
--- a/internal/store/event_loop_test.go
+++ b/internal/store/event_loop_test.go
@@ -82,22 +82,92 @@ func TestEventLoopUpdateMessageFromLoop(t *testing.T) {
Subject: subject,
})
+ testEvent(t, m, &pmapi.Event{
+ EventID: "event1",
+ Messages: []*pmapi.EventMessage{{
+ EventItem: pmapi.EventItem{
+ ID: "msg1",
+ Action: pmapi.EventUpdate,
+ },
+ Updated: &pmapi.EventMessageUpdated{
+ ID: "msg1",
+ Subject: &newSubject,
+ },
+ }},
+ })
+
+ msg, err := m.store.getMessageFromDB("msg1")
+ require.NoError(t, err)
+ require.Equal(t, newSubject, msg.Subject)
+}
+
+func TestEventLoopDeletionNotPaused(t *testing.T) {
+ m, clear := initMocks(t)
+ defer clear()
+
+ m.newStoreNoEvents(true, &pmapi.Message{
+ ID: "msg1",
+ Subject: "subject",
+ LabelIDs: []string{"label"},
+ })
+
+ m.changeNotifier.EXPECT().CanDelete("label").Return(true, func() {})
+ m.store.SetChangeNotifier(m.changeNotifier)
+
+ testEvent(t, m, &pmapi.Event{
+ EventID: "event1",
+ Messages: []*pmapi.EventMessage{{
+ EventItem: pmapi.EventItem{
+ ID: "msg1",
+ Action: pmapi.EventDelete,
+ },
+ }},
+ })
+
+ _, err := m.store.getMessageFromDB("msg1")
+ require.Error(t, err)
+}
+
+func TestEventLoopDeletionPaused(t *testing.T) {
+ m, clear := initMocks(t)
+ defer clear()
+
+ m.newStoreNoEvents(true, &pmapi.Message{
+ ID: "msg1",
+ Subject: "subject",
+ LabelIDs: []string{"label"},
+ })
+
+ delay := 5 * time.Second
+
+ m.changeNotifier.EXPECT().CanDelete("label").Return(false, func() {
+ time.Sleep(delay)
+ })
+ m.changeNotifier.EXPECT().CanDelete("label").Return(true, func() {})
+ m.store.SetChangeNotifier(m.changeNotifier)
+
+ start := time.Now()
+
+ testEvent(t, m, &pmapi.Event{
+ EventID: "event1",
+ Messages: []*pmapi.EventMessage{{
+ EventItem: pmapi.EventItem{
+ ID: "msg1",
+ Action: pmapi.EventDelete,
+ },
+ }},
+ })
+
+ _, err := m.store.getMessageFromDB("msg1")
+ require.Error(t, err)
+ require.True(t, time.Since(start) > delay)
+}
+
+func testEvent(t *testing.T, m *mocksForStore, event *pmapi.Event) {
eventReceived := make(chan struct{})
m.client.EXPECT().GetEvent("latestEventID").DoAndReturn(func(eventID string) (*pmapi.Event, error) {
defer close(eventReceived)
- return &pmapi.Event{
- EventID: "event1",
- Messages: []*pmapi.EventMessage{{
- EventItem: pmapi.EventItem{
- ID: "msg1",
- Action: pmapi.EventUpdate,
- },
- Updated: &pmapi.EventMessageUpdated{
- ID: "msg1",
- Subject: &newSubject,
- },
- }},
- }, nil
+ return event, nil
})
// Event loop runs in goroutine started during store creation (newStoreNoEvents).
@@ -109,10 +179,6 @@ func TestEventLoopUpdateMessageFromLoop(t *testing.T) {
case <-time.After(5 * time.Second):
require.Fail(t, "latestEventID was not processed")
}
-
- msg, err := m.store.getMessageFromDB("msg1")
- require.NoError(t, err)
- require.Equal(t, newSubject, msg.Subject)
}
func TestEventLoopUpdateMessage(t *testing.T) {
diff --git a/internal/store/message.go b/internal/store/message.go
index 78b889b7..f74bd129 100644
--- a/internal/store/message.go
+++ b/internal/store/message.go
@@ -20,7 +20,7 @@ package store
import (
"net/mail"
- backendMessage "github.com/ProtonMail/proton-bridge/pkg/message"
+ pkgMsg "github.com/ProtonMail/proton-bridge/pkg/message"
"github.com/ProtonMail/proton-bridge/pkg/pmapi"
bolt "go.etcd.io/bbolt"
)
@@ -121,8 +121,8 @@ func (message *Message) SetContentTypeAndHeader(mimeType string, header mail.Hea
return message.store.db.Update(txUpdate)
}
-// SetBodyStructure stores serialized body structure in database
-func (message *Message) SetBodyStructure(bs *backendMessage.BodyStructure) error {
+// SetBodyStructure stores serialized body structure in database.
+func (message *Message) SetBodyStructure(bs *pkgMsg.BodyStructure) error {
txUpdate := func(tx *bolt.Tx) error {
return message.store.txPutBodyStructure(
tx.Bucket(bodystructureBucket),
@@ -132,10 +132,10 @@ func (message *Message) SetBodyStructure(bs *backendMessage.BodyStructure) error
return message.store.db.Update(txUpdate)
}
-// GetBodyStructure deserialize body structure from database. If body structure
+// GetBodyStructure deserializes body structure from database. If body structure
// is not in database it returns nil error and nil body structure. If error
// occurs it returns nil body structure.
-func (message *Message) GetBodyStructure() (bs *backendMessage.BodyStructure, err error) {
+func (message *Message) GetBodyStructure() (bs *pkgMsg.BodyStructure, err error) {
txRead := func(tx *bolt.Tx) error {
bs, err = message.store.txGetBodyStructure(
tx.Bucket(bodystructureBucket),
diff --git a/internal/store/store.go b/internal/store/store.go
index 1df2541c..cacd9201 100644
--- a/internal/store/store.go
+++ b/internal/store/store.go
@@ -51,6 +51,8 @@ var (
// Database structure:
// * metadata
// * {messageID} -> message data (subject, from, to, time, headers, body size, ...)
+ // * bodystructure
+ // * {messageID} -> message body structure
// * counts
// * {mailboxID} -> mailboxCounts: totalOnAPI, unreadOnAPI, labelName, labelColor, labelIsExclusive
// * address_info
diff --git a/internal/store/store_test.go b/internal/store/store_test.go
index 603cc81b..0f2aafe5 100644
--- a/internal/store/store_test.go
+++ b/internal/store/store_test.go
@@ -103,7 +103,7 @@ func (mocks *mocksForStore) newStoreNoEvents(combinedMode bool, msgs ...*pmapi.M
{ID: addrID1, Email: addr1, Type: pmapi.OriginalAddress, Receive: pmapi.CanReceive},
{ID: addrID2, Email: addr2, Type: pmapi.AliasAddress, Receive: pmapi.CanReceive},
})
- mocks.client.EXPECT().ListLabels()
+ mocks.client.EXPECT().ListLabels().AnyTimes()
mocks.client.EXPECT().CountMessages("")
// Call to get latest event ID and then to process first event.
diff --git a/internal/store/user_message.go b/internal/store/user_message.go
index 7798161d..9628657e 100644
--- a/internal/store/user_message.go
+++ b/internal/store/user_message.go
@@ -27,7 +27,7 @@ import (
"strings"
"github.com/ProtonMail/gopenpgp/v2/crypto"
- backendMessage "github.com/ProtonMail/proton-bridge/pkg/message"
+ pkgMsg "github.com/ProtonMail/proton-bridge/pkg/message"
"github.com/ProtonMail/proton-bridge/pkg/pmapi"
"github.com/pkg/errors"
"github.com/sirupsen/logrus"
@@ -171,7 +171,7 @@ func (store *Store) txPutMessage(metaBucket *bolt.Bucket, onlyMeta *pmapi.Messag
return nil
}
-func (store *Store) txPutBodyStructure(bsBucket *bolt.Bucket, msgID string, bs *backendMessage.BodyStructure) error {
+func (store *Store) txPutBodyStructure(bsBucket *bolt.Bucket, msgID string, bs *pkgMsg.BodyStructure) error {
raw, err := bs.Serialize()
if err != nil {
return err
@@ -183,12 +183,12 @@ func (store *Store) txPutBodyStructure(bsBucket *bolt.Bucket, msgID string, bs *
return nil
}
-func (store *Store) txGetBodyStructure(bsBucket *bolt.Bucket, msgID string) (*backendMessage.BodyStructure, error) {
+func (store *Store) txGetBodyStructure(bsBucket *bolt.Bucket, msgID string) (*pkgMsg.BodyStructure, error) {
raw := bsBucket.Get([]byte(msgID))
if len(raw) == 0 {
return nil, nil
}
- return backendMessage.DeserializeBodyStructure(raw)
+ return pkgMsg.DeserializeBodyStructure(raw)
}
// createOrUpdateMessageEvent is helper to create only one message with
diff --git a/internal/transfer/provider_pmapi_source.go b/internal/transfer/provider_pmapi_source.go
index bac7422d..b1e65cd1 100644
--- a/internal/transfer/provider_pmapi_source.go
+++ b/internal/transfer/provider_pmapi_source.go
@@ -21,7 +21,7 @@ import (
"fmt"
"sync"
- pkgMessage "github.com/ProtonMail/proton-bridge/pkg/message"
+ pkgMsg "github.com/ProtonMail/proton-bridge/pkg/message"
"github.com/ProtonMail/proton-bridge/pkg/pmapi"
"github.com/pkg/errors"
"github.com/sirupsen/logrus"
@@ -153,7 +153,7 @@ func (p *PMAPIProvider) exportMessage(rule *Rule, progress *Progress, pmapiMsgID
p.timeIt.start("build", msgID)
defer p.timeIt.stop("build", msgID)
- msgBuilder := pkgMessage.NewBuilder(p.client(), msg)
+ msgBuilder := pkgMsg.NewBuilder(p.client(), msg)
msgBuilder.EncryptedToHTML = false
_, body, err := msgBuilder.BuildMessage()
if err != nil {
diff --git a/internal/transfer/provider_pmapi_target.go b/internal/transfer/provider_pmapi_target.go
index 445bcfee..36149e75 100644
--- a/internal/transfer/provider_pmapi_target.go
+++ b/internal/transfer/provider_pmapi_target.go
@@ -24,7 +24,7 @@ import (
"io/ioutil"
"sync"
- pkgMessage "github.com/ProtonMail/proton-bridge/pkg/message"
+ pkgMsg "github.com/ProtonMail/proton-bridge/pkg/message"
"github.com/ProtonMail/proton-bridge/pkg/pmapi"
"github.com/pkg/errors"
)
@@ -246,7 +246,7 @@ func (p *PMAPIProvider) generateImportMsgReq(rules transferRules, progress *Prog
func (p *PMAPIProvider) parseMessage(msg Message) (m *pmapi.Message, r []io.Reader, err error) {
p.timeIt.start("parse", msg.ID)
defer p.timeIt.stop("parse", msg.ID)
- message, _, _, attachmentReaders, err := pkgMessage.Parse(bytes.NewBuffer(msg.Body))
+ message, _, _, attachmentReaders, err := pkgMsg.Parse(bytes.NewBuffer(msg.Body))
return message, attachmentReaders, err
}
@@ -254,7 +254,7 @@ func (p *PMAPIProvider) encryptMessage(msg *pmapi.Message, attachmentReaders []i
if msg.MIMEType == pmapi.ContentTypeMultipartEncrypted {
return []byte(msg.Body), nil
}
- return pkgMessage.BuildEncrypted(msg, attachmentReaders, p.keyRing)
+ return pkgMsg.BuildEncrypted(msg, attachmentReaders, p.keyRing)
}
func computeMessageFlags(labels []string) (flag int64) {
diff --git a/internal/users/user.go b/internal/users/user.go
index bd0783e5..a70b0745 100644
--- a/internal/users/user.go
+++ b/internal/users/user.go
@@ -152,7 +152,7 @@ func (u *User) authorizeIfNecessary(emitEvent bool) (err error) {
u.log.WithError(err).Error("Could not authorize and unlock user")
switch errors.Cause(err) {
- case pmapi.ErrUpgradeApplication, pmapi.ErrAPINotReachable:
+ case pmapi.ErrUpgradeApplication, pmapi.ErrAPINotReachable: // Ignore these errors.
default:
if errLogout := u.credStorer.Logout(u.userID); errLogout != nil {
u.log.WithField("err", errLogout).Error("Could not log user out from credentials store")
diff --git a/pkg/pmapi/clientmanager.go b/pkg/pmapi/clientmanager.go
index 0b022f67..15eafe17 100644
--- a/pkg/pmapi/clientmanager.go
+++ b/pkg/pmapi/clientmanager.go
@@ -132,7 +132,9 @@ func (cm *ClientManager) noConnection() {
}
cm.log.Warn("Connection lost")
- cm.config.ConnectionOffHandler()
+ if cm.config.ConnectionOffHandler != nil {
+ cm.config.ConnectionOffHandler()
+ }
cm.connectionOff = true
go func() {
@@ -141,7 +143,9 @@ func (cm *ClientManager) noConnection() {
if err := cm.CheckConnection(); err == nil {
cm.log.Info("Connection re-established")
- cm.config.ConnectionOnHandler()
+ if cm.config.ConnectionOnHandler != nil {
+ cm.config.ConnectionOnHandler()
+ }
cm.connectionOff = false
return
}
diff --git a/unreleased.md b/unreleased.md
index 89f38f37..88bc7308 100644
--- a/unreleased.md
+++ b/unreleased.md
@@ -32,9 +32,13 @@ Changelog [format](http://keepachangelog.com/en/1.0.0/)
* GODT-854 EXPUNGE and FETCH unilateral responses are returned before OK EXPUNGE or OK STORE, respectively.
* GODT-806 Changed GUI dialog on manual update. Added autoupdates checkbox. Simplifyed installation process GUI.
* Bump gopenpgp dependency to v2.1.3 for improved memory usage.
-* GODT-912 Changed scroll bar behaviour in settings tab
+* GODT-912 Changed scroll bar behaviour in settings tab.
* GODT-149 Send heartbeat ASAP on each new calendar day.
* GODT-792 GODT-908 Cache body structure in order to reduce network traffic.
+* GODT-792 Stop IMAP server while no internet connection.
+* GODT-792 Cache message size every time to reduce network traffic.
+* GODT-792 Cache body structure in order to reduce network traffic.
+* GODT-908 Do not unpause event loop if other mailbox is still fetching.
### Removed
* GODT-208 Remove deprecated use of BuildNameToCertificate.
@@ -47,7 +51,7 @@ Changelog [format](http://keepachangelog.com/en/1.0.0/)
* GODT-898 Only set ContentID for inline attachments.
* GODT-773 Replace `INTERNALDATE` older than birthday of RFC822 by birthday of RFC822 to not crash Apple Mail.
* GODT-927 Avoid to call API with empty label name.
-* GODT-732 Fix usage of fontawesome
+* GODT-732 Fix usage of fontawesome.
* GODT-915 Bump go-imap dependency and remove go-imap-specialuse dependency.
* GODT-831 Cancel request of uploading attachment if reading/writing it fails.