mirror of
https://github.com/ProtonMail/proton-bridge.git
synced 2025-12-10 04:36:43 +00:00
Compare commits
3 Commits
8b891fb3e7
...
dbef40cfc5
| Author | SHA1 | Date | |
|---|---|---|---|
| dbef40cfc5 | |||
| e9ea976773 | |||
| a00af3a398 |
@ -3,6 +3,12 @@
|
||||
Changelog [format](http://keepachangelog.com/en/1.0.0/)
|
||||
|
||||
|
||||
## Kanmon Bridge 3.21.1
|
||||
|
||||
### Changed
|
||||
* BRIDGE-383: Extended internal mailbox conflict resolution logic and minor changes to the mailbox conflict pre-checker.
|
||||
|
||||
|
||||
## Kanmon Bridge 3.21.0
|
||||
|
||||
### Added
|
||||
|
||||
2
Makefile
2
Makefile
@ -12,7 +12,7 @@ ROOT_DIR:=$(realpath .)
|
||||
.PHONY: build build-gui build-nogui build-launcher versioner hasher
|
||||
|
||||
# Keep version hardcoded so app build works also without Git repository.
|
||||
BRIDGE_APP_VERSION?=3.21.0+git
|
||||
BRIDGE_APP_VERSION?=3.21.1+git
|
||||
APP_VERSION:=${BRIDGE_APP_VERSION}
|
||||
APP_FULL_NAME:=Proton Mail Bridge
|
||||
APP_VENDOR:=Proton AG
|
||||
|
||||
2
go.mod
2
go.mod
@ -7,7 +7,7 @@ toolchain go1.24.2
|
||||
require (
|
||||
github.com/0xAX/notificator v0.0.0-20220220101646-ee9b8921e557
|
||||
github.com/Masterminds/semver/v3 v3.2.0
|
||||
github.com/ProtonMail/gluon v0.17.1-0.20250604083016-c6e17f8461b1
|
||||
github.com/ProtonMail/gluon v0.17.1-0.20250611120816-05167d499f8d
|
||||
github.com/ProtonMail/go-autostart v0.0.0-20210130080809-00ed301c8e9a
|
||||
github.com/ProtonMail/go-proton-api v0.4.1-0.20250417134000-e624a080f7ba
|
||||
github.com/ProtonMail/gopenpgp/v2 v2.8.2-proton
|
||||
|
||||
4
go.sum
4
go.sum
@ -36,8 +36,8 @@ github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAE
|
||||
github.com/ProtonMail/bcrypt v0.0.0-20210511135022-227b4adcab57/go.mod h1:HecWFHognK8GfRDGnFQbW/LiV7A3MX3gZVs45vk5h8I=
|
||||
github.com/ProtonMail/bcrypt v0.0.0-20211005172633-e235017c1baf h1:yc9daCCYUefEs69zUkSzubzjBbL+cmOXgnmt9Fyd9ug=
|
||||
github.com/ProtonMail/bcrypt v0.0.0-20211005172633-e235017c1baf/go.mod h1:o0ESU9p83twszAU8LBeJKFAAMX14tISa0yk4Oo5TOqo=
|
||||
github.com/ProtonMail/gluon v0.17.1-0.20250604083016-c6e17f8461b1 h1:FvkPBZF/M5GpZTy+hzhaheyi+Z5XWeZOL5GKVKqj85Y=
|
||||
github.com/ProtonMail/gluon v0.17.1-0.20250604083016-c6e17f8461b1/go.mod h1:0/c03TzZPNiSgY5UDJK1iRDkjlDPwWugxTT6et2qDu8=
|
||||
github.com/ProtonMail/gluon v0.17.1-0.20250611120816-05167d499f8d h1:45W7G+X0w7nzLzeB0eiFkGho5DTK1jNmmNbt3IhN524=
|
||||
github.com/ProtonMail/gluon v0.17.1-0.20250611120816-05167d499f8d/go.mod h1:0/c03TzZPNiSgY5UDJK1iRDkjlDPwWugxTT6et2qDu8=
|
||||
github.com/ProtonMail/go-crypto v0.0.0-20230321155629-9a39f2531310/go.mod h1:8TI4H3IbrackdNgv+92dI+rhpCaLqM0IfpgCgenFvRE=
|
||||
github.com/ProtonMail/go-crypto v1.1.4-proton h1:KIo9uNlk3vzlwI7o5VjhiEjI4Ld1TDixOMnoNZyfpFE=
|
||||
github.com/ProtonMail/go-crypto v1.1.4-proton/go.mod h1:zNoyBJW3p/yVWiHNZgfTF9VsjwqYof5YY0M9kt2QaX0=
|
||||
|
||||
@ -88,3 +88,18 @@ func (mr *MockReporterMockRecorder) ReportMessageWithContext(arg0, arg1 interfac
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ReportMessageWithContext", reflect.TypeOf((*MockReporter)(nil).ReportMessageWithContext), arg0, arg1)
|
||||
}
|
||||
|
||||
|
||||
// ReportWarningWithContext mocks base method.
|
||||
func (m *MockReporter) ReportWarningWithContext(arg0 string, arg1 map[string]interface{}) error {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "ReportWarningWithContext", arg0, arg1)
|
||||
ret0, _ := ret[0].(error)
|
||||
return ret0
|
||||
}
|
||||
|
||||
// ReportWarningWithContext indicates an expected call of ReportWarningWithContext.
|
||||
func (mr *MockReporterMockRecorder) ReportWarningWithContext(arg0, arg1 interface{}) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ReportWarningWithContext", reflect.TypeOf((*MockReporter)(nil).ReportMessageWithContext), arg0, arg1)
|
||||
}
|
||||
@ -157,7 +157,7 @@ func (r *Reporter) ReportExceptionWithContext(i interface{}, context map[string]
|
||||
SkipDuringUnwind()
|
||||
|
||||
err := fmt.Errorf("recover: %v", i)
|
||||
return r.scopedReport(context, func() {
|
||||
return r.scopedReport(context, func(_ *sentry.Scope) {
|
||||
SkipDuringUnwind()
|
||||
if eventID := sentry.CaptureException(err); eventID != nil {
|
||||
logrus.WithError(err).
|
||||
@ -169,7 +169,20 @@ func (r *Reporter) ReportExceptionWithContext(i interface{}, context map[string]
|
||||
|
||||
func (r *Reporter) ReportMessageWithContext(msg string, context map[string]interface{}) error {
|
||||
SkipDuringUnwind()
|
||||
return r.scopedReport(context, func() {
|
||||
return r.scopedReport(context, func(_ *sentry.Scope) {
|
||||
SkipDuringUnwind()
|
||||
if eventID := sentry.CaptureMessage(msg); eventID != nil {
|
||||
logrus.WithField("message", msg).
|
||||
WithField("reportID", *eventID).
|
||||
Warn("Captured message")
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func (r *Reporter) ReportWarningWithContext(msg string, context map[string]interface{}) error {
|
||||
SkipDuringUnwind()
|
||||
return r.scopedReport(context, func(scope *sentry.Scope) {
|
||||
scope.SetLevel(sentry.LevelWarning)
|
||||
SkipDuringUnwind()
|
||||
if eventID := sentry.CaptureMessage(msg); eventID != nil {
|
||||
logrus.WithField("message", msg).
|
||||
@ -180,7 +193,7 @@ func (r *Reporter) ReportMessageWithContext(msg string, context map[string]inter
|
||||
}
|
||||
|
||||
// Report reports a sentry crash with stacktrace from all goroutines.
|
||||
func (r *Reporter) scopedReport(context map[string]interface{}, doReport func()) error {
|
||||
func (r *Reporter) scopedReport(context map[string]interface{}, doReport func(scope *sentry.Scope)) error {
|
||||
SkipDuringUnwind()
|
||||
|
||||
if os.Getenv("PROTONMAIL_ENV") == "dev" {
|
||||
@ -206,7 +219,7 @@ func (r *Reporter) scopedReport(context map[string]interface{}, doReport func())
|
||||
map[string]sentry.Context{"bridge": contextToString(context)},
|
||||
)
|
||||
}
|
||||
doReport()
|
||||
doReport(scope)
|
||||
})
|
||||
|
||||
if !sentry.Flush(time.Second * 10) {
|
||||
@ -302,6 +315,10 @@ func (n NullSentryReporter) ReportMessageWithContext(string, reporter.Context) e
|
||||
return nil
|
||||
}
|
||||
|
||||
func (n NullSentryReporter) ReportWarningWithContext(string, reporter.Context) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (n NullSentryReporter) ReportExceptionWithContext(any, reporter.Context) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
@ -42,6 +42,7 @@ type gluonIDProvider interface {
|
||||
|
||||
type sentryReporter interface {
|
||||
ReportMessageWithContext(string, reporter.Context) error
|
||||
ReportWarningWithContext(string, reporter.Context) error
|
||||
}
|
||||
|
||||
type apiClient interface {
|
||||
@ -50,6 +51,8 @@ type apiClient interface {
|
||||
|
||||
type mailboxFetcherFn func(ctx context.Context, label proton.Label) (imap.MailboxData, error)
|
||||
|
||||
type mailboxMessageCountFetcherFn func(ctx context.Context, internalMailboxID imap.InternalMailboxID) (int, error)
|
||||
|
||||
type LabelConflictManager struct {
|
||||
gluonLabelNameProvider GluonLabelNameProvider
|
||||
gluonIDProvider gluonIDProvider
|
||||
@ -86,47 +89,62 @@ func (m *LabelConflictManager) generateMailboxFetcher(connectors []*Connector) m
|
||||
}
|
||||
}
|
||||
|
||||
type UserLabelConflictResolver interface {
|
||||
func (m *LabelConflictManager) generateMailboxMessageCountFetcher(connectors []*Connector) mailboxMessageCountFetcherFn {
|
||||
return func(ctx context.Context, id imap.InternalMailboxID) (int, error) {
|
||||
var countSum int
|
||||
var errs []error
|
||||
for _, conn := range connectors {
|
||||
count, err := conn.GetMailboxMessageCount(ctx, id)
|
||||
countSum += count
|
||||
errs = append(errs, err)
|
||||
}
|
||||
|
||||
return countSum, errors.Join(errs...)
|
||||
}
|
||||
}
|
||||
|
||||
type LabelConflictResolver interface {
|
||||
ResolveConflict(ctx context.Context, label proton.Label, visited map[string]bool) (func() []imap.Update, error)
|
||||
}
|
||||
type userLabelConflictResolverImpl struct {
|
||||
type labelConflictResolverImpl struct {
|
||||
mailboxFetch mailboxFetcherFn
|
||||
client apiClient
|
||||
reporter sentryReporter
|
||||
log *logrus.Entry
|
||||
}
|
||||
|
||||
type nullUserLabelConflictResolverImpl struct {
|
||||
type nullLabelConflictResolverImpl struct {
|
||||
}
|
||||
|
||||
func (r *nullUserLabelConflictResolverImpl) ResolveConflict(_ context.Context, _ proton.Label, _ map[string]bool) (func() []imap.Update, error) {
|
||||
func (r *nullLabelConflictResolverImpl) ResolveConflict(_ context.Context, _ proton.Label, _ map[string]bool) (func() []imap.Update, error) {
|
||||
return func() []imap.Update {
|
||||
return []imap.Update{}
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (m *LabelConflictManager) NewUserConflictResolver(connectors []*Connector) UserLabelConflictResolver {
|
||||
func (m *LabelConflictManager) NewConflictResolver(connectors []*Connector) LabelConflictResolver {
|
||||
if m.featureFlagProvider.GetFlagValue(unleash.LabelConflictResolverDisabled) {
|
||||
return &nullUserLabelConflictResolverImpl{}
|
||||
return &nullLabelConflictResolverImpl{}
|
||||
}
|
||||
|
||||
return &userLabelConflictResolverImpl{
|
||||
return &labelConflictResolverImpl{
|
||||
mailboxFetch: m.generateMailboxFetcher(connectors),
|
||||
client: m.client,
|
||||
reporter: m.reporter,
|
||||
log: logrus.WithFields(logrus.Fields{
|
||||
"pkg": "imapservice/userLabelConflictResolver",
|
||||
"pkg": "imapservice/labelConflictResolver",
|
||||
"numberOfConnectors": len(connectors),
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
||||
func (r *userLabelConflictResolverImpl) ResolveConflict(ctx context.Context, label proton.Label, visited map[string]bool) (func() []imap.Update, error) {
|
||||
func (r *labelConflictResolverImpl) ResolveConflict(ctx context.Context, label proton.Label, visited map[string]bool) (func() []imap.Update, error) {
|
||||
logger := r.log.WithFields(logrus.Fields{
|
||||
"labelID": label.ID,
|
||||
"labelPath": hashLabelPaths(GetMailboxName(label)),
|
||||
})
|
||||
|
||||
// For system type labels we shouldn't care.
|
||||
var updateFns []func() []imap.Update
|
||||
|
||||
// There's a cycle, such as in a label swap operation, we'll need to temporarily rename the label.
|
||||
@ -170,14 +188,14 @@ func (r *userLabelConflictResolverImpl) ResolveConflict(ctx context.Context, lab
|
||||
logger.Info("Label conflict found")
|
||||
|
||||
// If the label name belongs to some other label ID. Fetch it's state from the remote.
|
||||
conflictingLabel, err := r.client.GetLabel(ctx, mailboxData.RemoteID, proton.LabelTypeFolder, proton.LabelTypeLabel)
|
||||
conflictingLabel, err := r.client.GetLabel(ctx, mailboxData.RemoteID, proton.LabelTypeFolder, proton.LabelTypeLabel, proton.LabelTypeSystem)
|
||||
if err != nil {
|
||||
// If it's not present on the remote we should delete it. And create the new label.
|
||||
if errors.Is(err, proton.ErrNoSuchLabel) {
|
||||
logger.Info("Conflicting label does not exist on remote. Deleting.")
|
||||
fn := func() []imap.Update {
|
||||
return []imap.Update{
|
||||
imap.NewMailboxDeleted(imap.MailboxID(mailboxData.RemoteID)),
|
||||
imap.NewMailboxDeleted(imap.MailboxID(mailboxData.RemoteID)), // Should this be with remote ID
|
||||
newMailboxUpdatedOrCreated(imap.MailboxID(label.ID), GetMailboxName(label)),
|
||||
}
|
||||
}
|
||||
@ -240,19 +258,22 @@ func hashLabelPaths(path []string) string {
|
||||
}
|
||||
|
||||
type InternalLabelConflictResolver interface {
|
||||
ResolveConflict(ctx context.Context) (func() []imap.Update, error)
|
||||
ResolveConflict(ctx context.Context, apiLabels map[string]proton.Label) (func() []imap.Update, error)
|
||||
}
|
||||
|
||||
type internalLabelConflictResolverImpl struct {
|
||||
mailboxFetch mailboxFetcherFn
|
||||
client apiClient
|
||||
reporter sentryReporter
|
||||
log *logrus.Entry
|
||||
mailboxFetch mailboxFetcherFn
|
||||
mailboxMessageCountFetch mailboxMessageCountFetcherFn
|
||||
userLabelConflictResolver LabelConflictResolver
|
||||
allowNonEmptyMailboxDeletion bool
|
||||
client apiClient
|
||||
reporter sentryReporter
|
||||
log *logrus.Entry
|
||||
}
|
||||
|
||||
type nullInternalLabelConflictResolver struct{}
|
||||
|
||||
func (r *nullInternalLabelConflictResolver) ResolveConflict(_ context.Context) (func() []imap.Update, error) {
|
||||
func (r *nullInternalLabelConflictResolver) ResolveConflict(_ context.Context, _ map[string]proton.Label) (func() []imap.Update, error) {
|
||||
return func() []imap.Update { return []imap.Update{} }, nil
|
||||
}
|
||||
|
||||
@ -262,9 +283,12 @@ func (m *LabelConflictManager) NewInternalLabelConflictResolver(connectors []*Co
|
||||
}
|
||||
|
||||
return &internalLabelConflictResolverImpl{
|
||||
mailboxFetch: m.generateMailboxFetcher(connectors),
|
||||
client: m.client,
|
||||
reporter: m.reporter,
|
||||
mailboxFetch: m.generateMailboxFetcher(connectors),
|
||||
mailboxMessageCountFetch: m.generateMailboxMessageCountFetcher(connectors),
|
||||
userLabelConflictResolver: m.NewConflictResolver(connectors),
|
||||
allowNonEmptyMailboxDeletion: m.featureFlagProvider.GetFlagValue(unleash.ItnternalLabelConflictNonEmptyMailboxDeletion),
|
||||
client: m.client,
|
||||
reporter: m.reporter,
|
||||
log: logrus.WithFields(logrus.Fields{
|
||||
"pkg": "imapservice/internalLabelConflictResolver",
|
||||
"numberOfConnectors": len(connectors),
|
||||
@ -272,17 +296,17 @@ func (m *LabelConflictManager) NewInternalLabelConflictResolver(connectors []*Co
|
||||
}
|
||||
}
|
||||
|
||||
func (r *internalLabelConflictResolverImpl) ResolveConflict(ctx context.Context) (func() []imap.Update, error) {
|
||||
var updateFns []func() []imap.Update
|
||||
func (r *internalLabelConflictResolverImpl) ResolveConflict(ctx context.Context, apiLabels map[string]proton.Label) (func() []imap.Update, error) {
|
||||
updateFns := []func() []imap.Update{}
|
||||
|
||||
for _, prefix := range []string{folderPrefix, labelPrefix} {
|
||||
label := proton.Label{
|
||||
internalLabel := proton.Label{
|
||||
Path: []string{prefix},
|
||||
ID: prefix,
|
||||
Name: prefix,
|
||||
}
|
||||
|
||||
mbox, err := r.mailboxFetch(ctx, label)
|
||||
mbox, err := r.mailboxFetch(ctx, internalLabel)
|
||||
if err != nil {
|
||||
if db.IsErrNotFound(err) {
|
||||
continue
|
||||
@ -290,11 +314,75 @@ func (r *internalLabelConflictResolverImpl) ResolveConflict(ctx context.Context)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if mbox.RemoteID != label.ID {
|
||||
// If the ID's don't match we should delete these.
|
||||
fn := func() []imap.Update { return []imap.Update{imap.NewMailboxDeleted(imap.MailboxID(prefix))} }
|
||||
updateFns = append(updateFns, fn)
|
||||
// If the ID's match then we don't have a discrepancy.
|
||||
if mbox.RemoteID == internalLabel.ID {
|
||||
continue
|
||||
}
|
||||
|
||||
logFields := logrus.Fields{
|
||||
"internalLabelID": internalLabel.ID,
|
||||
"internalLabelName": internalLabel.Name,
|
||||
"conflictingLabelID": mbox.RemoteID,
|
||||
"conflictingLabelName": strings.Join(mbox.BridgeName, "/"),
|
||||
}
|
||||
reporterContext := reporter.Context(logFields)
|
||||
logger := r.log.WithFields(logFields)
|
||||
logger.Info("Encountered conflict, resolving.")
|
||||
|
||||
// There is a discrepancy, let's see if it comes from API.
|
||||
apiLabel, ok := apiLabels[mbox.RemoteID]
|
||||
if !ok {
|
||||
// Label does not come from API, we should delete it.
|
||||
// Due diligence, check if there are any messages associated with the mailbox.
|
||||
msgCount, _ := r.mailboxMessageCountFetch(ctx, mbox.InternalID)
|
||||
if msgCount != 0 {
|
||||
logger.WithField("conflictingLabelMessageCount", msgCount).Info("Non-API conflicting label has associated messages")
|
||||
|
||||
reporterContext["conflictingLabelMessageCount"] = msgCount
|
||||
if rerr := r.reporter.ReportWarningWithContext("Internal mailbox name conflict. Conflicting non-API label has messages.",
|
||||
reporterContext); rerr != nil {
|
||||
logger.WithError(rerr).Error("Failed to send report to sentry")
|
||||
}
|
||||
|
||||
if !r.allowNonEmptyMailboxDeletion {
|
||||
return combineIMAPUpdateFns(updateFns), fmt.Errorf("internal mailbox conflicting non-api label has associated messages")
|
||||
}
|
||||
}
|
||||
|
||||
fn := func() []imap.Update {
|
||||
return []imap.Update{imap.NewMailboxDeletedSilent(imap.MailboxID(mbox.RemoteID))}
|
||||
}
|
||||
updateFns = append(updateFns, fn)
|
||||
continue
|
||||
}
|
||||
|
||||
reporterContext["conflictingLabelType"] = apiLabel.Type
|
||||
|
||||
// Label is indeed from API let's see if it's name has changed.
|
||||
if compareLabelNames(GetMailboxName(apiLabel), internalLabel.Path) {
|
||||
logger.Error("Conflict, same-name mailbox is returned by API")
|
||||
|
||||
if err := r.reporter.ReportMessageWithContext("Internal mailbox name conflict. Same-name mailbox is returned by API", reporterContext); err != nil {
|
||||
logger.WithError(err).Error("Could not send report to sentry")
|
||||
}
|
||||
|
||||
return combineIMAPUpdateFns(updateFns), fmt.Errorf("API label %s conflicts with internal label %s",
|
||||
GetMailboxName(apiLabel),
|
||||
strings.Join(mbox.BridgeName, "/"),
|
||||
)
|
||||
}
|
||||
|
||||
// If it's name has changed then we ought to rename it while still taking care of potential conflicts.
|
||||
labelRenameUpdates, err := r.userLabelConflictResolver.ResolveConflict(ctx, apiLabel, make(map[string]bool))
|
||||
if err != nil {
|
||||
reporterContext["err"] = err.Error()
|
||||
if rerr := r.reporter.ReportMessageWithContext("Failed to resolve internal mailbox conflict", reporterContext); rerr != nil {
|
||||
logger.WithError(rerr).Error("Could not send report to sentry")
|
||||
}
|
||||
return combineIMAPUpdateFns(updateFns),
|
||||
fmt.Errorf("failed to resolve user label conflict for '%s': %w", apiLabel.Name, err)
|
||||
}
|
||||
updateFns = append(updateFns, labelRenameUpdates)
|
||||
}
|
||||
return combineIMAPUpdateFns(updateFns), nil
|
||||
}
|
||||
|
||||
@ -88,6 +88,11 @@ func (m *mockReporter) ReportMessageWithContext(msg string, ctx reporter.Context
|
||||
return args.Error(0)
|
||||
}
|
||||
|
||||
func (m *mockReporter) ReportWarningWithContext(msg string, ctx reporter.Context) error {
|
||||
args := m.Called(msg, ctx)
|
||||
return args.Error(0)
|
||||
}
|
||||
|
||||
func TestResolveConflict_UnexpectedLabelConflict(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
label := proton.Label{
|
||||
@ -121,7 +126,7 @@ func TestResolveConflict_UnexpectedLabelConflict(t *testing.T) {
|
||||
connector := &imapservice.Connector{}
|
||||
connector.SetAddrIDTest("addr-1")
|
||||
resolver := imapservice.NewLabelConflictManager(mockLabelProvider, mockIDProvider, mockClient, mockReporter, ffProviderFalse{}).
|
||||
NewUserConflictResolver([]*imapservice.Connector{connector})
|
||||
NewConflictResolver([]*imapservice.Connector{connector})
|
||||
|
||||
visited := make(map[string]bool)
|
||||
_, err := resolver.ResolveConflict(ctx, label, visited)
|
||||
@ -152,7 +157,7 @@ func TestResolveDiscrepancy_LabelDoesNotExist(t *testing.T) {
|
||||
connectors := []*imapservice.Connector{connector}
|
||||
|
||||
manager := imapservice.NewLabelConflictManager(mockLabelProvider, mockIDProvider, mockClient, mockReporter, ffProviderFalse{})
|
||||
resolver := manager.NewUserConflictResolver(connectors)
|
||||
resolver := manager.NewConflictResolver(connectors)
|
||||
|
||||
visited := make(map[string]bool)
|
||||
fn, err := resolver.ResolveConflict(ctx, label, visited)
|
||||
@ -185,7 +190,7 @@ func TestResolveConflict_MailboxFetchError(t *testing.T) {
|
||||
connector := &imapservice.Connector{}
|
||||
connector.SetAddrIDTest("addr-1")
|
||||
resolver := imapservice.NewLabelConflictManager(mockLabelProvider, mockIDProvider, mockClient, mockReporter, ffProviderFalse{}).
|
||||
NewUserConflictResolver([]*imapservice.Connector{connector})
|
||||
NewConflictResolver([]*imapservice.Connector{connector})
|
||||
|
||||
visited := make(map[string]bool)
|
||||
_, err := resolver.ResolveConflict(ctx, label, visited)
|
||||
@ -223,7 +228,7 @@ func TestResolveDiscrepancy_ConflictingLabelDeletedRemotely(t *testing.T) {
|
||||
connectors := []*imapservice.Connector{connector}
|
||||
|
||||
manager := imapservice.NewLabelConflictManager(mockLabelProvider, mockIDProvider, mockClient, mockReporter, ffProviderFalse{})
|
||||
resolver := manager.NewUserConflictResolver(connectors)
|
||||
resolver := manager.NewConflictResolver(connectors)
|
||||
|
||||
visited := make(map[string]bool)
|
||||
fn, err := resolver.ResolveConflict(ctx, label, visited)
|
||||
@ -266,7 +271,7 @@ func TestResolveDiscrepancy_LabelAlreadyCorrect(t *testing.T) {
|
||||
connectors := []*imapservice.Connector{connector}
|
||||
|
||||
manager := imapservice.NewLabelConflictManager(mockLabelProvider, mockIDProvider, mockClient, mockReporter, ffProviderFalse{})
|
||||
resolver := manager.NewUserConflictResolver(connectors)
|
||||
resolver := manager.NewConflictResolver(connectors)
|
||||
|
||||
visited := make(map[string]bool)
|
||||
fn, err := resolver.ResolveConflict(ctx, label, visited)
|
||||
@ -295,7 +300,7 @@ func TestResolveConflict_DeepNestedPath(t *testing.T) {
|
||||
connector := &imapservice.Connector{}
|
||||
connector.SetAddrIDTest("addr-1")
|
||||
resolver := imapservice.NewLabelConflictManager(mockLabelProvider, mockIDProvider, mockClient, mockReporter, ffProviderFalse{}).
|
||||
NewUserConflictResolver([]*imapservice.Connector{connector})
|
||||
NewConflictResolver([]*imapservice.Connector{connector})
|
||||
|
||||
visited := make(map[string]bool)
|
||||
fn, err := resolver.ResolveConflict(ctx, label, visited)
|
||||
@ -351,7 +356,7 @@ func TestResolveLabelDiscrepancy_LabelSwap(t *testing.T) {
|
||||
|
||||
for _, label := range apiLabels {
|
||||
mockClient.
|
||||
On("GetLabel", mock.Anything, label.ID, []proton.LabelType{proton.LabelTypeFolder, proton.LabelTypeLabel}).
|
||||
On("GetLabel", mock.Anything, label.ID, []proton.LabelType{proton.LabelTypeFolder, proton.LabelTypeLabel, proton.LabelTypeSystem}).
|
||||
Return(label, nil)
|
||||
}
|
||||
|
||||
@ -360,7 +365,7 @@ func TestResolveLabelDiscrepancy_LabelSwap(t *testing.T) {
|
||||
connectors := []*imapservice.Connector{connector}
|
||||
|
||||
manager := imapservice.NewLabelConflictManager(mockLabelProvider, mockIDProvider, mockClient, mockReporter, ffProviderFalse{})
|
||||
resolver := manager.NewUserConflictResolver(connectors)
|
||||
resolver := manager.NewConflictResolver(connectors)
|
||||
|
||||
visited := make(map[string]bool)
|
||||
fn, err := resolver.ResolveConflict(context.Background(), apiLabels[0], visited)
|
||||
@ -444,7 +449,7 @@ func TestResolveLabelDiscrepancy_LabelSwapExtended(t *testing.T) {
|
||||
|
||||
for _, label := range apiLabels {
|
||||
mockClient.
|
||||
On("GetLabel", mock.Anything, label.ID, []proton.LabelType{proton.LabelTypeFolder, proton.LabelTypeLabel}).
|
||||
On("GetLabel", mock.Anything, label.ID, []proton.LabelType{proton.LabelTypeFolder, proton.LabelTypeLabel, proton.LabelTypeSystem}).
|
||||
Return(label, nil)
|
||||
}
|
||||
|
||||
@ -453,7 +458,7 @@ func TestResolveLabelDiscrepancy_LabelSwapExtended(t *testing.T) {
|
||||
connectors := []*imapservice.Connector{connector}
|
||||
|
||||
manager := imapservice.NewLabelConflictManager(mockLabelProvider, mockIDProvider, mockClient, mockReporter, ffProviderFalse{})
|
||||
resolver := manager.NewUserConflictResolver(connectors)
|
||||
resolver := manager.NewConflictResolver(connectors)
|
||||
|
||||
fn, err := resolver.ResolveConflict(context.Background(), apiLabels[0], make(map[string]bool))
|
||||
require.NoError(t, err)
|
||||
@ -529,7 +534,7 @@ func TestResolveLabelDiscrepancy_LabelSwapCyclic(t *testing.T) {
|
||||
|
||||
for _, label := range apiLabels {
|
||||
mockClient.
|
||||
On("GetLabel", mock.Anything, label.ID, []proton.LabelType{proton.LabelTypeFolder, proton.LabelTypeLabel}).
|
||||
On("GetLabel", mock.Anything, label.ID, []proton.LabelType{proton.LabelTypeFolder, proton.LabelTypeLabel, proton.LabelTypeSystem}).
|
||||
Return(label, nil)
|
||||
}
|
||||
|
||||
@ -538,7 +543,7 @@ func TestResolveLabelDiscrepancy_LabelSwapCyclic(t *testing.T) {
|
||||
connectors := []*imapservice.Connector{connector}
|
||||
|
||||
manager := imapservice.NewLabelConflictManager(mockLabelProvider, mockIDProvider, mockClient, mockReporter, ffProviderFalse{})
|
||||
resolver := manager.NewUserConflictResolver(connectors)
|
||||
resolver := manager.NewConflictResolver(connectors)
|
||||
|
||||
fn, err := resolver.ResolveConflict(context.Background(), apiLabels[0], make(map[string]bool))
|
||||
require.NoError(t, err)
|
||||
@ -602,17 +607,17 @@ func TestResolveLabelDiscrepancy_LabelSwapCyclicWithDeletedLabel(t *testing.T) {
|
||||
|
||||
for _, label := range apiLabels {
|
||||
mockClient.
|
||||
On("GetLabel", mock.Anything, label.ID, []proton.LabelType{proton.LabelTypeFolder, proton.LabelTypeLabel}).
|
||||
On("GetLabel", mock.Anything, label.ID, []proton.LabelType{proton.LabelTypeFolder, proton.LabelTypeLabel, proton.LabelTypeSystem}).
|
||||
Return(label, nil)
|
||||
}
|
||||
mockClient.On("GetLabel", mock.Anything, "222", []proton.LabelType{proton.LabelTypeFolder, proton.LabelTypeLabel}).Return(proton.Label{}, proton.ErrNoSuchLabel)
|
||||
mockClient.On("GetLabel", mock.Anything, "222", []proton.LabelType{proton.LabelTypeFolder, proton.LabelTypeLabel, proton.LabelTypeSystem}).Return(proton.Label{}, proton.ErrNoSuchLabel)
|
||||
|
||||
connector := &imapservice.Connector{}
|
||||
connector.SetAddrIDTest("addr-1")
|
||||
connectors := []*imapservice.Connector{connector}
|
||||
|
||||
manager := imapservice.NewLabelConflictManager(mockLabelProvider, mockIDProvider, mockClient, mockReporter, ffProviderFalse{})
|
||||
resolver := manager.NewUserConflictResolver(connectors)
|
||||
resolver := manager.NewConflictResolver(connectors)
|
||||
|
||||
fn, err := resolver.ResolveConflict(context.Background(), apiLabels[2], make(map[string]bool))
|
||||
require.NoError(t, err)
|
||||
@ -665,17 +670,17 @@ func TestResolveLabelDiscrepancy_LabelSwapCyclicWithDeletedLabel_KillSwitchEnabl
|
||||
|
||||
for _, label := range apiLabels {
|
||||
mockClient.
|
||||
On("GetLabel", mock.Anything, label.ID, []proton.LabelType{proton.LabelTypeFolder, proton.LabelTypeLabel}).
|
||||
On("GetLabel", mock.Anything, label.ID, []proton.LabelType{proton.LabelTypeFolder, proton.LabelTypeLabel, proton.LabelTypeSystem}).
|
||||
Return(label, nil)
|
||||
}
|
||||
mockClient.On("GetLabel", mock.Anything, "222", []proton.LabelType{proton.LabelTypeFolder, proton.LabelTypeLabel}).Return(proton.Label{}, proton.ErrNoSuchLabel)
|
||||
mockClient.On("GetLabel", mock.Anything, "222", []proton.LabelType{proton.LabelTypeFolder, proton.LabelTypeLabel, proton.LabelTypeSystem}).Return(proton.Label{}, proton.ErrNoSuchLabel)
|
||||
|
||||
connector := &imapservice.Connector{}
|
||||
connector.SetAddrIDTest("addr-1")
|
||||
connectors := []*imapservice.Connector{connector}
|
||||
|
||||
manager := imapservice.NewLabelConflictManager(mockLabelProvider, mockIDProvider, mockClient, mockReporter, ffProviderTrue{})
|
||||
resolver := manager.NewUserConflictResolver(connectors)
|
||||
resolver := manager.NewConflictResolver(connectors)
|
||||
|
||||
fn, err := resolver.ResolveConflict(context.Background(), apiLabels[2], make(map[string]bool))
|
||||
require.NoError(t, err)
|
||||
@ -706,7 +711,8 @@ func TestInternalLabelConflictResolver_NoConflicts(t *testing.T) {
|
||||
manager := imapservice.NewLabelConflictManager(mockLabelProvider, mockIDProvider, mockClient, mockReporter, ffProviderFalse{})
|
||||
resolver := manager.NewInternalLabelConflictResolver(connectors)
|
||||
|
||||
fn, err := resolver.ResolveConflict(ctx)
|
||||
apiLabels := make(map[string]proton.Label)
|
||||
fn, err := resolver.ResolveConflict(ctx, apiLabels)
|
||||
assert.NoError(t, err)
|
||||
|
||||
updates := fn()
|
||||
@ -735,81 +741,159 @@ func TestInternalLabelConflictResolver_CorrectIDs(t *testing.T) {
|
||||
manager := imapservice.NewLabelConflictManager(mockLabelProvider, mockIDProvider, mockClient, mockReporter, ffProviderFalse{})
|
||||
resolver := manager.NewInternalLabelConflictResolver(connectors)
|
||||
|
||||
fn, err := resolver.ResolveConflict(ctx)
|
||||
apiLabels := make(map[string]proton.Label)
|
||||
fn, err := resolver.ResolveConflict(ctx, apiLabels)
|
||||
assert.NoError(t, err)
|
||||
|
||||
updates := fn()
|
||||
assert.Empty(t, updates)
|
||||
}
|
||||
|
||||
func TestInternalLabelConflictResolver_ConflictingFoldersID(t *testing.T) {
|
||||
type mockMailboxCountProvider struct {
|
||||
mock.Mock
|
||||
}
|
||||
|
||||
func (m *mockMailboxCountProvider) GetUserMailboxCountByInternalID(ctx context.Context, addrID string, internalID imap.InternalMailboxID) (int, error) {
|
||||
args := m.Called(ctx, addrID, internalID)
|
||||
return args.Int(0), args.Error(1)
|
||||
}
|
||||
|
||||
func TestInternalLabelConflictResolver_ConflictingNonAPILabel_ZeroCount(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
|
||||
mockLabelProvider := new(mockLabelNameProvider)
|
||||
mockClient := new(mockAPIClient)
|
||||
mockIDProvider := new(mockIDProvider)
|
||||
mockReporter := new(mockReporter)
|
||||
mockCountProvider := new(mockMailboxCountProvider)
|
||||
|
||||
mockIDProvider.On("GetGluonID", "addr-1").Return("gluon-id-1", true)
|
||||
|
||||
// Mock mailbox fetch to return conflicting mailbox
|
||||
mockLabelProvider.On("GetUserMailboxByName", mock.Anything, "gluon-id-1", []string{"Folders"}).
|
||||
Return(imap.MailboxData{RemoteID: "wrong-id", BridgeName: []string{"Folders"}}, nil)
|
||||
Return(imap.MailboxData{RemoteID: "wrong-id", BridgeName: []string{"Folders"}, InternalID: imap.InternalMailboxID(123)}, nil)
|
||||
mockLabelProvider.On("GetUserMailboxByName", mock.Anything, "gluon-id-1", []string{"Labels"}).
|
||||
Return(imap.MailboxData{}, db.ErrNotFound)
|
||||
|
||||
// Mock message count fetch to return 0 messages.
|
||||
mockLabelProvider.On("GetMailboxMessageCount", mock.Anything, "gluon-id-1", imap.InternalMailboxID(123)).
|
||||
Return(0, nil)
|
||||
|
||||
connector := &imapservice.Connector{}
|
||||
connector.SetAddrIDTest("addr-1")
|
||||
mockCountProvider.On("GetUserMailboxCountByInternalID",
|
||||
mock.Anything,
|
||||
"addr-1",
|
||||
imap.InternalMailboxID(123)).
|
||||
Return(0, nil)
|
||||
|
||||
connector.SetMailboxCountProviderTest(mockCountProvider)
|
||||
connectors := []*imapservice.Connector{connector}
|
||||
|
||||
manager := imapservice.NewLabelConflictManager(mockLabelProvider, mockIDProvider, mockClient, mockReporter, ffProviderFalse{})
|
||||
resolver := manager.NewInternalLabelConflictResolver(connectors)
|
||||
|
||||
fn, err := resolver.ResolveConflict(ctx)
|
||||
// API labels don't contain the conflicting label ID
|
||||
apiLabels := make(map[string]proton.Label)
|
||||
fn, err := resolver.ResolveConflict(ctx, apiLabels)
|
||||
assert.NoError(t, err)
|
||||
|
||||
updates := fn()
|
||||
assert.Len(t, updates, 1)
|
||||
|
||||
deleted, ok := updates[0].(*imap.MailboxDeleted)
|
||||
deleted, ok := updates[0].(*imap.MailboxDeletedSilent)
|
||||
assert.True(t, ok)
|
||||
assert.Equal(t, imap.MailboxID("Folders"), deleted.MailboxID)
|
||||
assert.Equal(t, imap.MailboxID("wrong-id"), deleted.MailboxID)
|
||||
}
|
||||
|
||||
func TestInternalLabelConflictResolver_BothConflicting(t *testing.T) {
|
||||
func TestInternalLabelConflictResolver_ConflictingNonAPILabel_PositiveCount(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
|
||||
mockLabelProvider := new(mockLabelNameProvider)
|
||||
mockClient := new(mockAPIClient)
|
||||
mockIDProvider := new(mockIDProvider)
|
||||
mockReporter := new(mockReporter)
|
||||
mockCountProvider := new(mockMailboxCountProvider)
|
||||
|
||||
mockIDProvider.On("GetGluonID", "addr-1").Return("gluon-id-1", true)
|
||||
|
||||
mockReporter.On("ReportWarningWithContext", mock.Anything, mock.Anything).
|
||||
Return(nil)
|
||||
|
||||
// Mock mailbox fetch to return conflicting mailbox
|
||||
mockLabelProvider.On("GetUserMailboxByName", mock.Anything, "gluon-id-1", []string{"Folders"}).
|
||||
Return(imap.MailboxData{RemoteID: "wrong-folders-id", BridgeName: []string{"Folders"}}, nil)
|
||||
Return(imap.MailboxData{RemoteID: "wrong-id", BridgeName: []string{"Folders"}, InternalID: imap.InternalMailboxID(123)}, nil)
|
||||
mockLabelProvider.On("GetUserMailboxByName", mock.Anything, "gluon-id-1", []string{"Labels"}).
|
||||
Return(imap.MailboxData{RemoteID: "wrong-labels-id", BridgeName: []string{"Labels"}}, nil)
|
||||
Return(imap.MailboxData{}, db.ErrNotFound)
|
||||
|
||||
// Mock message count fetch to return 0 messages.
|
||||
mockLabelProvider.On("GetMailboxMessageCount", mock.Anything, "gluon-id-1", imap.InternalMailboxID(123)).
|
||||
Return(0, nil)
|
||||
|
||||
connector := &imapservice.Connector{}
|
||||
connector.SetAddrIDTest("addr-1")
|
||||
mockCountProvider.On("GetUserMailboxCountByInternalID",
|
||||
mock.Anything,
|
||||
"addr-1",
|
||||
imap.InternalMailboxID(123)).
|
||||
Return(10, nil)
|
||||
|
||||
connector.SetMailboxCountProviderTest(mockCountProvider)
|
||||
connectors := []*imapservice.Connector{connector}
|
||||
|
||||
manager := imapservice.NewLabelConflictManager(mockLabelProvider, mockIDProvider, mockClient, mockReporter, ffProviderFalse{})
|
||||
resolver := manager.NewInternalLabelConflictResolver(connectors)
|
||||
|
||||
fn, err := resolver.ResolveConflict(ctx)
|
||||
assert.NoError(t, err)
|
||||
// API labels don't contain the conflicting label ID
|
||||
apiLabels := make(map[string]proton.Label)
|
||||
fn, err := resolver.ResolveConflict(ctx, apiLabels)
|
||||
assert.EqualError(t, err, "internal mailbox conflicting non-api label has associated messages")
|
||||
|
||||
updates := fn()
|
||||
assert.Len(t, updates, 2)
|
||||
assert.Empty(t, updates, 0)
|
||||
}
|
||||
|
||||
updateOne, ok := updates[0].(*imap.MailboxDeleted)
|
||||
assert.True(t, ok)
|
||||
assert.Equal(t, imap.MailboxID("Folders"), updateOne.MailboxID)
|
||||
func TestInternalLabelConflictResolver_ConflictingAPILabelSameName(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
|
||||
updateTwo, ok := updates[1].(*imap.MailboxDeleted)
|
||||
assert.True(t, ok)
|
||||
assert.Equal(t, imap.MailboxID("Labels"), updateTwo.MailboxID)
|
||||
mockLabelProvider := new(mockLabelNameProvider)
|
||||
mockClient := new(mockAPIClient)
|
||||
mockIDProvider := new(mockIDProvider)
|
||||
mockReporter := new(mockReporter)
|
||||
mockCountProvider := new(mockMailboxCountProvider)
|
||||
|
||||
mockIDProvider.On("GetGluonID", "addr-1").Return("gluon-id-1", true)
|
||||
|
||||
mockLabelProvider.On("GetUserMailboxByName", mock.Anything, "gluon-id-1", []string{"Folders"}).
|
||||
Return(imap.MailboxData{RemoteID: "api-label-id", BridgeName: []string{"Folders"}}, nil)
|
||||
mockLabelProvider.On("GetUserMailboxByName", mock.Anything, "gluon-id-1", []string{"Labels"}).
|
||||
Return(imap.MailboxData{}, db.ErrNotFound)
|
||||
|
||||
mockReporter.On("ReportMessageWithContext", "Internal mailbox name conflict. Same-name mailbox is returned by API", mock.Anything).
|
||||
Return(nil)
|
||||
|
||||
connector := &imapservice.Connector{}
|
||||
connector.SetAddrIDTest("addr-1")
|
||||
connector.SetMailboxCountProviderTest(mockCountProvider)
|
||||
connectors := []*imapservice.Connector{connector}
|
||||
|
||||
manager := imapservice.NewLabelConflictManager(mockLabelProvider, mockIDProvider, mockClient, mockReporter, ffProviderFalse{})
|
||||
resolver := manager.NewInternalLabelConflictResolver(connectors)
|
||||
|
||||
// API user label with empty path.
|
||||
apiLabels := map[string]proton.Label{
|
||||
"api-label-id": {
|
||||
ID: "api-label-id",
|
||||
Name: "Folders",
|
||||
Path: []string{""},
|
||||
Type: proton.LabelTypeFolder,
|
||||
},
|
||||
}
|
||||
|
||||
_, err := resolver.ResolveConflict(ctx, apiLabels)
|
||||
assert.Error(t, err)
|
||||
assert.Contains(t, err.Error(), "API label")
|
||||
assert.Contains(t, err.Error(), "conflicts with internal label")
|
||||
}
|
||||
|
||||
func TestInternalLabelConflictResolver_MailboxFetchError(t *testing.T) {
|
||||
@ -832,7 +916,8 @@ func TestInternalLabelConflictResolver_MailboxFetchError(t *testing.T) {
|
||||
manager := imapservice.NewLabelConflictManager(mockLabelProvider, mockIDProvider, mockClient, mockReporter, ffProviderFalse{})
|
||||
resolver := manager.NewInternalLabelConflictResolver(connectors)
|
||||
|
||||
_, err := resolver.ResolveConflict(ctx)
|
||||
apiLabels := make(map[string]proton.Label)
|
||||
_, err := resolver.ResolveConflict(ctx, apiLabels)
|
||||
assert.Error(t, err)
|
||||
assert.Contains(t, err.Error(), "database connection error")
|
||||
}
|
||||
@ -859,7 +944,16 @@ func TestNewInternalLabelConflictResolver_KillSwitchEnabled(t *testing.T) {
|
||||
manager := imapservice.NewLabelConflictManager(mockLabelProvider, mockIDProvider, mockClient, mockReporter, ffProviderTrue{})
|
||||
resolver := manager.NewInternalLabelConflictResolver(connectors)
|
||||
|
||||
fn, err := resolver.ResolveConflict(ctx)
|
||||
apiLabels := map[string]proton.Label{
|
||||
"some-api-label": {
|
||||
ID: "some-api-label",
|
||||
Name: "SomeLabel",
|
||||
Path: []string{"SomeLabel"},
|
||||
Type: proton.LabelTypeLabel,
|
||||
},
|
||||
}
|
||||
|
||||
fn, err := resolver.ResolveConflict(ctx, apiLabels)
|
||||
assert.NoError(t, err)
|
||||
|
||||
updates := fn()
|
||||
|
||||
@ -45,6 +45,10 @@ import (
|
||||
"golang.org/x/exp/slices"
|
||||
)
|
||||
|
||||
type mailboxCountProvider interface {
|
||||
GetUserMailboxCountByInternalID(ctx context.Context, addrID string, internalID imap.InternalMailboxID) (int, error)
|
||||
}
|
||||
|
||||
// Connector contains all IMAP state required to satisfy sync and or imap queries.
|
||||
type Connector struct {
|
||||
addrID string
|
||||
@ -67,6 +71,8 @@ type Connector struct {
|
||||
|
||||
sharedCache *SharedCache
|
||||
syncState *SyncState
|
||||
|
||||
mailboxCountProvider mailboxCountProvider
|
||||
}
|
||||
|
||||
var errNoSenderAddressMatch = errors.New("no matching sender found in address list")
|
||||
@ -82,6 +88,7 @@ func NewConnector(
|
||||
reporter reporter.Reporter,
|
||||
showAllMail bool,
|
||||
syncState *SyncState,
|
||||
mailboxCountProvider mailboxCountProvider,
|
||||
) *Connector {
|
||||
userID := identityState.UserID()
|
||||
|
||||
@ -115,6 +122,8 @@ func NewConnector(
|
||||
|
||||
sharedCache: NewSharedCached(),
|
||||
syncState: syncState,
|
||||
|
||||
mailboxCountProvider: mailboxCountProvider,
|
||||
}
|
||||
}
|
||||
|
||||
@ -909,3 +918,12 @@ func (s *Connector) getSenderProtonAddress(p *parser.Parser) (proton.Address, er
|
||||
func (s *Connector) SetAddrIDTest(addrID string) {
|
||||
s.addrID = addrID
|
||||
}
|
||||
|
||||
func (s *Connector) GetMailboxMessageCount(ctx context.Context, mailboxInternalID imap.InternalMailboxID) (int, error) {
|
||||
return s.mailboxCountProvider.GetUserMailboxCountByInternalID(ctx, s.addrID, mailboxInternalID)
|
||||
}
|
||||
|
||||
// SetMailboxCountProviderTest - sets the relevant provider. Should only be used for testing.
|
||||
func (s *Connector) SetMailboxCountProviderTest(provider mailboxCountProvider) {
|
||||
s.mailboxCountProvider = provider
|
||||
}
|
||||
|
||||
@ -55,6 +55,7 @@ func (t labelDiscrepancyType) String() string {
|
||||
type labelDiscrepancy struct {
|
||||
labelName string
|
||||
labelPath string
|
||||
labelPathParsed string
|
||||
labelID string
|
||||
conflictingLabelName string
|
||||
conflictingLabelID string
|
||||
@ -76,10 +77,12 @@ func newLabelDiscrepancy(label proton.Label, mbox imap.MailboxData, dType labelD
|
||||
if dType == discrepancyUser {
|
||||
discrepancy.labelName = algo.HashBase64SHA256(label.Name)
|
||||
discrepancy.labelPath = algo.HashBase64SHA256(joinStrings(label.Path))
|
||||
discrepancy.labelPathParsed = algo.HashBase64SHA256(joinStrings(GetMailboxName(label)))
|
||||
discrepancy.conflictingLabelName = algo.HashBase64SHA256(joinStrings(mbox.BridgeName))
|
||||
} else {
|
||||
discrepancy.labelName = label.Name
|
||||
discrepancy.labelPath = joinStrings(label.Path)
|
||||
discrepancy.labelPathParsed = joinStrings(GetMailboxName(label))
|
||||
discrepancy.conflictingLabelName = joinStrings(mbox.BridgeName)
|
||||
}
|
||||
|
||||
@ -93,9 +96,10 @@ func discrepanciesToContext(discrepancies []labelDiscrepancy) reporter.Context {
|
||||
prefix := fmt.Sprintf("discrepancy_%d_", i)
|
||||
|
||||
ctx[prefix+"type"] = d.Type.String()
|
||||
ctx[prefix+"label_id"] = d.labelID
|
||||
ctx[prefix+"label_name"] = d.labelName
|
||||
ctx[prefix+"label_path"] = d.labelPath
|
||||
ctx[prefix+"label_id"] = d.labelID
|
||||
ctx[prefix+"label_path_parsed"] = d.labelPathParsed
|
||||
ctx[prefix+"conflicting_label_name"] = d.conflictingLabelName
|
||||
ctx[prefix+"conflicting_label_id"] = d.conflictingLabelID
|
||||
}
|
||||
|
||||
@ -40,7 +40,10 @@ type IMAPServerManager interface {
|
||||
|
||||
GetUserMailboxByName(ctx context.Context, addrID string, mailboxName []string) (imap.MailboxData, error)
|
||||
|
||||
GetUserMailboxCountByInternalID(ctx context.Context, addrID string, internalID imap.InternalMailboxID) (int, error)
|
||||
|
||||
GetOpenIMAPSessionCount() int
|
||||
|
||||
GetRollingIMAPConnectionCount() int
|
||||
}
|
||||
|
||||
@ -77,6 +80,10 @@ func (n NullIMAPServerManager) GetUserMailboxByName(_ context.Context, _ string,
|
||||
return imap.MailboxData{}, nil
|
||||
}
|
||||
|
||||
func (n NullIMAPServerManager) GetUserMailboxCountByInternalID(_ context.Context, _ string, _ imap.InternalMailboxID) (int, error) {
|
||||
return 0, nil
|
||||
}
|
||||
|
||||
func (n NullIMAPServerManager) GetOpenIMAPSessionCount() int {
|
||||
return 0
|
||||
}
|
||||
|
||||
@ -540,6 +540,7 @@ func (s *Service) buildConnectors() (map[string]*Connector, error) {
|
||||
s.reporter,
|
||||
s.showAllMail,
|
||||
s.syncStateProvider,
|
||||
s.serverManager,
|
||||
)
|
||||
|
||||
return connectors, nil
|
||||
@ -557,6 +558,7 @@ func (s *Service) buildConnectors() (map[string]*Connector, error) {
|
||||
s.reporter,
|
||||
s.showAllMail,
|
||||
s.syncStateProvider,
|
||||
s.serverManager,
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@ -157,6 +157,7 @@ func addNewAddressSplitMode(ctx context.Context, s *Service, addrID string) erro
|
||||
s.reporter,
|
||||
s.showAllMail,
|
||||
s.syncStateProvider,
|
||||
s.serverManager,
|
||||
)
|
||||
|
||||
if err := s.serverManager.AddIMAPUser(ctx, connector, connector.addrID, s.gluonIDProvider, s.syncStateProvider); err != nil {
|
||||
|
||||
@ -90,7 +90,7 @@ func onLabelCreated(ctx context.Context, s *Service, event proton.LabelEvent) ([
|
||||
|
||||
wr.SetLabel(event.Label.ID, event.Label, "onLabelCreated")
|
||||
|
||||
labelConflictResolver := s.labelConflictManager.NewUserConflictResolver(maps.Values(s.connectors))
|
||||
labelConflictResolver := s.labelConflictManager.NewConflictResolver(maps.Values(s.connectors))
|
||||
conflictUpdatesGenerator, err := labelConflictResolver.ResolveConflict(ctx, event.Label, make(map[string]bool))
|
||||
if err != nil {
|
||||
return updates, err
|
||||
@ -150,7 +150,7 @@ func onLabelUpdated(ctx context.Context, s *Service, event proton.LabelEvent) ([
|
||||
wr.SetLabel(apiLabel.ID, apiLabel, "onLabelUpdatedApiID")
|
||||
|
||||
// Resolve potential conflicts
|
||||
labelConflictResolver := s.labelConflictManager.NewUserConflictResolver(maps.Values(s.connectors))
|
||||
labelConflictResolver := s.labelConflictManager.NewConflictResolver(maps.Values(s.connectors))
|
||||
conflictUpdatesGenerator, err := labelConflictResolver.ResolveConflict(ctx, apiLabel, make(map[string]bool))
|
||||
if err != nil {
|
||||
return updates, err
|
||||
|
||||
@ -133,24 +133,31 @@ func (s *SyncUpdateApplier) SyncLabels(ctx context.Context, labels map[string]pr
|
||||
func syncLabels(ctx context.Context, labels map[string]proton.Label, connectors []*Connector, labelConflictManager *LabelConflictManager) ([]imap.Update, error) {
|
||||
var updates []imap.Update
|
||||
|
||||
userLabelConflictResolver := labelConflictManager.NewUserConflictResolver(connectors)
|
||||
userLabelConflictResolver := labelConflictManager.NewConflictResolver(connectors)
|
||||
internalLabelConflictResolver := labelConflictManager.NewInternalLabelConflictResolver(connectors)
|
||||
|
||||
conflictUpdateGenerator, err := internalLabelConflictResolver.ResolveConflict(ctx, labels)
|
||||
if err != nil {
|
||||
return updates, err
|
||||
}
|
||||
|
||||
for _, updateCh := range connectors {
|
||||
conflictUpdates := conflictUpdateGenerator()
|
||||
updateCh.publishUpdate(ctx, conflictUpdates...)
|
||||
updates = append(updates, conflictUpdates...)
|
||||
}
|
||||
|
||||
// Create placeholder Folders/Labels mailboxes with the \Noselect attribute.
|
||||
for _, prefix := range []string{folderPrefix, labelPrefix} {
|
||||
conflictUpdateGenerator, err := internalLabelConflictResolver.ResolveConflict(ctx)
|
||||
if err != nil {
|
||||
return updates, err
|
||||
}
|
||||
|
||||
for _, updateCh := range connectors {
|
||||
conflictUpdates := conflictUpdateGenerator()
|
||||
updateCh.publishUpdate(ctx, conflictUpdates...)
|
||||
updates = append(updates, conflictUpdates...)
|
||||
|
||||
update := newPlaceHolderMailboxCreatedUpdate(prefix)
|
||||
updateCh.publishUpdate(ctx, update)
|
||||
updates = append(updates, update)
|
||||
|
||||
// Ensure we perform a rename operation as well. The created event won't update the name if the ID exists.
|
||||
renameUpdate := imap.NewMailboxUpdated(imap.MailboxID(prefix), []string{prefix})
|
||||
updateCh.publishUpdate(ctx, renameUpdate)
|
||||
updates = append(updates, renameUpdate)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -208,6 +208,10 @@ func (sm *Service) GetUserMailboxByName(ctx context.Context, addrID string, mail
|
||||
return sm.imapServer.GetUserMailboxByName(ctx, addrID, mailboxName)
|
||||
}
|
||||
|
||||
func (sm *Service) GetUserMailboxCountByInternalID(ctx context.Context, addrID string, internalID imap.InternalMailboxID) (int, error) {
|
||||
return sm.imapServer.GetUserMailboxCountByInternalID(ctx, addrID, internalID)
|
||||
}
|
||||
|
||||
func (sm *Service) GetOpenIMAPSessionCount() int {
|
||||
return sm.imapServer.GetOpenSessionCount()
|
||||
}
|
||||
|
||||
@ -37,13 +37,14 @@ var pollJitter = 2 * time.Minute //nolint:gochecknoglobals
|
||||
const filename = "unleash_flags"
|
||||
|
||||
const (
|
||||
EventLoopNotificationDisabled = "InboxBridgeEventLoopNotificationDisabled"
|
||||
IMAPAuthenticateCommandDisabled = "InboxBridgeImapAuthenticateCommandDisabled"
|
||||
UserRemovalGluonDataCleanupDisabled = "InboxBridgeUserRemovalGluonDataCleanupDisabled"
|
||||
UpdateUseNewVersionFileStructureDisabled = "InboxBridgeUpdateWithOsFilterDisabled"
|
||||
LabelConflictResolverDisabled = "InboxBridgeLabelConflictResolverDisabled"
|
||||
SMTPSubmissionRequestSentryReportDisabled = "InboxBridgeSmtpSubmissionRequestSentryReportDisabled"
|
||||
InternalLabelConflictResolverDisabled = "InboxBridgeUnexpectedFoldersLabelsStartupFixupDisabled"
|
||||
EventLoopNotificationDisabled = "InboxBridgeEventLoopNotificationDisabled"
|
||||
IMAPAuthenticateCommandDisabled = "InboxBridgeImapAuthenticateCommandDisabled"
|
||||
UserRemovalGluonDataCleanupDisabled = "InboxBridgeUserRemovalGluonDataCleanupDisabled"
|
||||
UpdateUseNewVersionFileStructureDisabled = "InboxBridgeUpdateWithOsFilterDisabled"
|
||||
LabelConflictResolverDisabled = "InboxBridgeLabelConflictResolverDisabled"
|
||||
SMTPSubmissionRequestSentryReportDisabled = "InboxBridgeSmtpSubmissionRequestSentryReportDisabled"
|
||||
InternalLabelConflictResolverDisabled = "InboxBridgeUnexpectedFoldersLabelsStartupFixupDisabled"
|
||||
ItnternalLabelConflictNonEmptyMailboxDeletion = "InboxBridgeUnknownNonEmptyMailboxDeletion"
|
||||
)
|
||||
|
||||
type FeatureFlagValueProvider interface {
|
||||
|
||||
@ -146,6 +146,11 @@ func (r *reportRecorder) ReportMessageWithContext(message string, context report
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *reportRecorder) ReportWarningWithContext(message string, context reporter.Context) error {
|
||||
r.add(false, message, context)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *reportRecorder) ReportExceptionWithContext(data any, context reporter.Context) error {
|
||||
if context == nil {
|
||||
context = reporter.Context{}
|
||||
|
||||
Reference in New Issue
Block a user