mirror of
https://github.com/ProtonMail/proton-bridge.git
synced 2025-12-10 04:36:43 +00:00
feat(BRIDGE-383): Internal mailbox conflict resolution extended; Minor alterations to mailbox conflict pre-checker
This commit is contained in:
2
go.mod
2
go.mod
@ -7,7 +7,7 @@ toolchain go1.24.2
|
|||||||
require (
|
require (
|
||||||
github.com/0xAX/notificator v0.0.0-20220220101646-ee9b8921e557
|
github.com/0xAX/notificator v0.0.0-20220220101646-ee9b8921e557
|
||||||
github.com/Masterminds/semver/v3 v3.2.0
|
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-autostart v0.0.0-20210130080809-00ed301c8e9a
|
||||||
github.com/ProtonMail/go-proton-api v0.4.1-0.20250417134000-e624a080f7ba
|
github.com/ProtonMail/go-proton-api v0.4.1-0.20250417134000-e624a080f7ba
|
||||||
github.com/ProtonMail/gopenpgp/v2 v2.8.2-proton
|
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-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 h1:yc9daCCYUefEs69zUkSzubzjBbL+cmOXgnmt9Fyd9ug=
|
||||||
github.com/ProtonMail/bcrypt v0.0.0-20211005172633-e235017c1baf/go.mod h1:o0ESU9p83twszAU8LBeJKFAAMX14tISa0yk4Oo5TOqo=
|
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.20250611120816-05167d499f8d h1:45W7G+X0w7nzLzeB0eiFkGho5DTK1jNmmNbt3IhN524=
|
||||||
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/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 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 h1:KIo9uNlk3vzlwI7o5VjhiEjI4Ld1TDixOMnoNZyfpFE=
|
||||||
github.com/ProtonMail/go-crypto v1.1.4-proton/go.mod h1:zNoyBJW3p/yVWiHNZgfTF9VsjwqYof5YY0M9kt2QaX0=
|
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()
|
mr.mock.ctrl.T.Helper()
|
||||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ReportMessageWithContext", reflect.TypeOf((*MockReporter)(nil).ReportMessageWithContext), arg0, arg1)
|
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()
|
SkipDuringUnwind()
|
||||||
|
|
||||||
err := fmt.Errorf("recover: %v", i)
|
err := fmt.Errorf("recover: %v", i)
|
||||||
return r.scopedReport(context, func() {
|
return r.scopedReport(context, func(_ *sentry.Scope) {
|
||||||
SkipDuringUnwind()
|
SkipDuringUnwind()
|
||||||
if eventID := sentry.CaptureException(err); eventID != nil {
|
if eventID := sentry.CaptureException(err); eventID != nil {
|
||||||
logrus.WithError(err).
|
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 {
|
func (r *Reporter) ReportMessageWithContext(msg string, context map[string]interface{}) error {
|
||||||
SkipDuringUnwind()
|
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()
|
SkipDuringUnwind()
|
||||||
if eventID := sentry.CaptureMessage(msg); eventID != nil {
|
if eventID := sentry.CaptureMessage(msg); eventID != nil {
|
||||||
logrus.WithField("message", msg).
|
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.
|
// 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()
|
SkipDuringUnwind()
|
||||||
|
|
||||||
if os.Getenv("PROTONMAIL_ENV") == "dev" {
|
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)},
|
map[string]sentry.Context{"bridge": contextToString(context)},
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
doReport()
|
doReport(scope)
|
||||||
})
|
})
|
||||||
|
|
||||||
if !sentry.Flush(time.Second * 10) {
|
if !sentry.Flush(time.Second * 10) {
|
||||||
@ -302,6 +315,10 @@ func (n NullSentryReporter) ReportMessageWithContext(string, reporter.Context) e
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (n NullSentryReporter) ReportWarningWithContext(string, reporter.Context) error {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
func (n NullSentryReporter) ReportExceptionWithContext(any, reporter.Context) error {
|
func (n NullSentryReporter) ReportExceptionWithContext(any, reporter.Context) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|||||||
@ -42,6 +42,7 @@ type gluonIDProvider interface {
|
|||||||
|
|
||||||
type sentryReporter interface {
|
type sentryReporter interface {
|
||||||
ReportMessageWithContext(string, reporter.Context) error
|
ReportMessageWithContext(string, reporter.Context) error
|
||||||
|
ReportWarningWithContext(string, reporter.Context) error
|
||||||
}
|
}
|
||||||
|
|
||||||
type apiClient interface {
|
type apiClient interface {
|
||||||
@ -50,6 +51,8 @@ type apiClient interface {
|
|||||||
|
|
||||||
type mailboxFetcherFn func(ctx context.Context, label proton.Label) (imap.MailboxData, error)
|
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 {
|
type LabelConflictManager struct {
|
||||||
gluonLabelNameProvider GluonLabelNameProvider
|
gluonLabelNameProvider GluonLabelNameProvider
|
||||||
gluonIDProvider gluonIDProvider
|
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)
|
ResolveConflict(ctx context.Context, label proton.Label, visited map[string]bool) (func() []imap.Update, error)
|
||||||
}
|
}
|
||||||
type userLabelConflictResolverImpl struct {
|
type labelConflictResolverImpl struct {
|
||||||
mailboxFetch mailboxFetcherFn
|
mailboxFetch mailboxFetcherFn
|
||||||
client apiClient
|
client apiClient
|
||||||
reporter sentryReporter
|
reporter sentryReporter
|
||||||
log *logrus.Entry
|
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 func() []imap.Update {
|
||||||
return []imap.Update{}
|
return []imap.Update{}
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *LabelConflictManager) NewUserConflictResolver(connectors []*Connector) UserLabelConflictResolver {
|
func (m *LabelConflictManager) NewConflictResolver(connectors []*Connector) LabelConflictResolver {
|
||||||
if m.featureFlagProvider.GetFlagValue(unleash.LabelConflictResolverDisabled) {
|
if m.featureFlagProvider.GetFlagValue(unleash.LabelConflictResolverDisabled) {
|
||||||
return &nullUserLabelConflictResolverImpl{}
|
return &nullLabelConflictResolverImpl{}
|
||||||
}
|
}
|
||||||
|
|
||||||
return &userLabelConflictResolverImpl{
|
return &labelConflictResolverImpl{
|
||||||
mailboxFetch: m.generateMailboxFetcher(connectors),
|
mailboxFetch: m.generateMailboxFetcher(connectors),
|
||||||
client: m.client,
|
client: m.client,
|
||||||
reporter: m.reporter,
|
reporter: m.reporter,
|
||||||
log: logrus.WithFields(logrus.Fields{
|
log: logrus.WithFields(logrus.Fields{
|
||||||
"pkg": "imapservice/userLabelConflictResolver",
|
"pkg": "imapservice/labelConflictResolver",
|
||||||
"numberOfConnectors": len(connectors),
|
"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{
|
logger := r.log.WithFields(logrus.Fields{
|
||||||
"labelID": label.ID,
|
"labelID": label.ID,
|
||||||
"labelPath": hashLabelPaths(GetMailboxName(label)),
|
"labelPath": hashLabelPaths(GetMailboxName(label)),
|
||||||
})
|
})
|
||||||
|
|
||||||
|
// For system type labels we shouldn't care.
|
||||||
var updateFns []func() []imap.Update
|
var updateFns []func() []imap.Update
|
||||||
|
|
||||||
// There's a cycle, such as in a label swap operation, we'll need to temporarily rename the label.
|
// 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")
|
logger.Info("Label conflict found")
|
||||||
|
|
||||||
// If the label name belongs to some other label ID. Fetch it's state from the remote.
|
// 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 err != nil {
|
||||||
// If it's not present on the remote we should delete it. And create the new label.
|
// If it's not present on the remote we should delete it. And create the new label.
|
||||||
if errors.Is(err, proton.ErrNoSuchLabel) {
|
if errors.Is(err, proton.ErrNoSuchLabel) {
|
||||||
logger.Info("Conflicting label does not exist on remote. Deleting.")
|
logger.Info("Conflicting label does not exist on remote. Deleting.")
|
||||||
fn := func() []imap.Update {
|
fn := func() []imap.Update {
|
||||||
return []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)),
|
newMailboxUpdatedOrCreated(imap.MailboxID(label.ID), GetMailboxName(label)),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -240,11 +258,14 @@ func hashLabelPaths(path []string) string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type InternalLabelConflictResolver interface {
|
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 {
|
type internalLabelConflictResolverImpl struct {
|
||||||
mailboxFetch mailboxFetcherFn
|
mailboxFetch mailboxFetcherFn
|
||||||
|
mailboxMessageCountFetch mailboxMessageCountFetcherFn
|
||||||
|
userLabelConflictResolver LabelConflictResolver
|
||||||
|
allowNonEmptyMailboxDeletion bool
|
||||||
client apiClient
|
client apiClient
|
||||||
reporter sentryReporter
|
reporter sentryReporter
|
||||||
log *logrus.Entry
|
log *logrus.Entry
|
||||||
@ -252,7 +273,7 @@ type internalLabelConflictResolverImpl struct {
|
|||||||
|
|
||||||
type nullInternalLabelConflictResolver struct{}
|
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
|
return func() []imap.Update { return []imap.Update{} }, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -263,6 +284,9 @@ func (m *LabelConflictManager) NewInternalLabelConflictResolver(connectors []*Co
|
|||||||
|
|
||||||
return &internalLabelConflictResolverImpl{
|
return &internalLabelConflictResolverImpl{
|
||||||
mailboxFetch: m.generateMailboxFetcher(connectors),
|
mailboxFetch: m.generateMailboxFetcher(connectors),
|
||||||
|
mailboxMessageCountFetch: m.generateMailboxMessageCountFetcher(connectors),
|
||||||
|
userLabelConflictResolver: m.NewConflictResolver(connectors),
|
||||||
|
allowNonEmptyMailboxDeletion: m.featureFlagProvider.GetFlagValue(unleash.ItnternalLabelConflictNonEmptyMailboxDeletion),
|
||||||
client: m.client,
|
client: m.client,
|
||||||
reporter: m.reporter,
|
reporter: m.reporter,
|
||||||
log: logrus.WithFields(logrus.Fields{
|
log: logrus.WithFields(logrus.Fields{
|
||||||
@ -272,17 +296,17 @@ func (m *LabelConflictManager) NewInternalLabelConflictResolver(connectors []*Co
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *internalLabelConflictResolverImpl) ResolveConflict(ctx context.Context) (func() []imap.Update, error) {
|
func (r *internalLabelConflictResolverImpl) ResolveConflict(ctx context.Context, apiLabels map[string]proton.Label) (func() []imap.Update, error) {
|
||||||
var updateFns []func() []imap.Update
|
updateFns := []func() []imap.Update{}
|
||||||
|
|
||||||
for _, prefix := range []string{folderPrefix, labelPrefix} {
|
for _, prefix := range []string{folderPrefix, labelPrefix} {
|
||||||
label := proton.Label{
|
internalLabel := proton.Label{
|
||||||
Path: []string{prefix},
|
Path: []string{prefix},
|
||||||
ID: prefix,
|
ID: prefix,
|
||||||
Name: prefix,
|
Name: prefix,
|
||||||
}
|
}
|
||||||
|
|
||||||
mbox, err := r.mailboxFetch(ctx, label)
|
mbox, err := r.mailboxFetch(ctx, internalLabel)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if db.IsErrNotFound(err) {
|
if db.IsErrNotFound(err) {
|
||||||
continue
|
continue
|
||||||
@ -290,11 +314,75 @@ func (r *internalLabelConflictResolverImpl) ResolveConflict(ctx context.Context)
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
if mbox.RemoteID != label.ID {
|
// If the ID's match then we don't have a discrepancy.
|
||||||
// If the ID's don't match we should delete these.
|
if mbox.RemoteID == internalLabel.ID {
|
||||||
fn := func() []imap.Update { return []imap.Update{imap.NewMailboxDeleted(imap.MailboxID(prefix))} }
|
continue
|
||||||
updateFns = append(updateFns, fn)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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
|
return combineIMAPUpdateFns(updateFns), nil
|
||||||
}
|
}
|
||||||
|
|||||||
@ -88,6 +88,11 @@ func (m *mockReporter) ReportMessageWithContext(msg string, ctx reporter.Context
|
|||||||
return args.Error(0)
|
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) {
|
func TestResolveConflict_UnexpectedLabelConflict(t *testing.T) {
|
||||||
ctx := context.Background()
|
ctx := context.Background()
|
||||||
label := proton.Label{
|
label := proton.Label{
|
||||||
@ -121,7 +126,7 @@ func TestResolveConflict_UnexpectedLabelConflict(t *testing.T) {
|
|||||||
connector := &imapservice.Connector{}
|
connector := &imapservice.Connector{}
|
||||||
connector.SetAddrIDTest("addr-1")
|
connector.SetAddrIDTest("addr-1")
|
||||||
resolver := imapservice.NewLabelConflictManager(mockLabelProvider, mockIDProvider, mockClient, mockReporter, ffProviderFalse{}).
|
resolver := imapservice.NewLabelConflictManager(mockLabelProvider, mockIDProvider, mockClient, mockReporter, ffProviderFalse{}).
|
||||||
NewUserConflictResolver([]*imapservice.Connector{connector})
|
NewConflictResolver([]*imapservice.Connector{connector})
|
||||||
|
|
||||||
visited := make(map[string]bool)
|
visited := make(map[string]bool)
|
||||||
_, err := resolver.ResolveConflict(ctx, label, visited)
|
_, err := resolver.ResolveConflict(ctx, label, visited)
|
||||||
@ -152,7 +157,7 @@ func TestResolveDiscrepancy_LabelDoesNotExist(t *testing.T) {
|
|||||||
connectors := []*imapservice.Connector{connector}
|
connectors := []*imapservice.Connector{connector}
|
||||||
|
|
||||||
manager := imapservice.NewLabelConflictManager(mockLabelProvider, mockIDProvider, mockClient, mockReporter, ffProviderFalse{})
|
manager := imapservice.NewLabelConflictManager(mockLabelProvider, mockIDProvider, mockClient, mockReporter, ffProviderFalse{})
|
||||||
resolver := manager.NewUserConflictResolver(connectors)
|
resolver := manager.NewConflictResolver(connectors)
|
||||||
|
|
||||||
visited := make(map[string]bool)
|
visited := make(map[string]bool)
|
||||||
fn, err := resolver.ResolveConflict(ctx, label, visited)
|
fn, err := resolver.ResolveConflict(ctx, label, visited)
|
||||||
@ -185,7 +190,7 @@ func TestResolveConflict_MailboxFetchError(t *testing.T) {
|
|||||||
connector := &imapservice.Connector{}
|
connector := &imapservice.Connector{}
|
||||||
connector.SetAddrIDTest("addr-1")
|
connector.SetAddrIDTest("addr-1")
|
||||||
resolver := imapservice.NewLabelConflictManager(mockLabelProvider, mockIDProvider, mockClient, mockReporter, ffProviderFalse{}).
|
resolver := imapservice.NewLabelConflictManager(mockLabelProvider, mockIDProvider, mockClient, mockReporter, ffProviderFalse{}).
|
||||||
NewUserConflictResolver([]*imapservice.Connector{connector})
|
NewConflictResolver([]*imapservice.Connector{connector})
|
||||||
|
|
||||||
visited := make(map[string]bool)
|
visited := make(map[string]bool)
|
||||||
_, err := resolver.ResolveConflict(ctx, label, visited)
|
_, err := resolver.ResolveConflict(ctx, label, visited)
|
||||||
@ -223,7 +228,7 @@ func TestResolveDiscrepancy_ConflictingLabelDeletedRemotely(t *testing.T) {
|
|||||||
connectors := []*imapservice.Connector{connector}
|
connectors := []*imapservice.Connector{connector}
|
||||||
|
|
||||||
manager := imapservice.NewLabelConflictManager(mockLabelProvider, mockIDProvider, mockClient, mockReporter, ffProviderFalse{})
|
manager := imapservice.NewLabelConflictManager(mockLabelProvider, mockIDProvider, mockClient, mockReporter, ffProviderFalse{})
|
||||||
resolver := manager.NewUserConflictResolver(connectors)
|
resolver := manager.NewConflictResolver(connectors)
|
||||||
|
|
||||||
visited := make(map[string]bool)
|
visited := make(map[string]bool)
|
||||||
fn, err := resolver.ResolveConflict(ctx, label, visited)
|
fn, err := resolver.ResolveConflict(ctx, label, visited)
|
||||||
@ -266,7 +271,7 @@ func TestResolveDiscrepancy_LabelAlreadyCorrect(t *testing.T) {
|
|||||||
connectors := []*imapservice.Connector{connector}
|
connectors := []*imapservice.Connector{connector}
|
||||||
|
|
||||||
manager := imapservice.NewLabelConflictManager(mockLabelProvider, mockIDProvider, mockClient, mockReporter, ffProviderFalse{})
|
manager := imapservice.NewLabelConflictManager(mockLabelProvider, mockIDProvider, mockClient, mockReporter, ffProviderFalse{})
|
||||||
resolver := manager.NewUserConflictResolver(connectors)
|
resolver := manager.NewConflictResolver(connectors)
|
||||||
|
|
||||||
visited := make(map[string]bool)
|
visited := make(map[string]bool)
|
||||||
fn, err := resolver.ResolveConflict(ctx, label, visited)
|
fn, err := resolver.ResolveConflict(ctx, label, visited)
|
||||||
@ -295,7 +300,7 @@ func TestResolveConflict_DeepNestedPath(t *testing.T) {
|
|||||||
connector := &imapservice.Connector{}
|
connector := &imapservice.Connector{}
|
||||||
connector.SetAddrIDTest("addr-1")
|
connector.SetAddrIDTest("addr-1")
|
||||||
resolver := imapservice.NewLabelConflictManager(mockLabelProvider, mockIDProvider, mockClient, mockReporter, ffProviderFalse{}).
|
resolver := imapservice.NewLabelConflictManager(mockLabelProvider, mockIDProvider, mockClient, mockReporter, ffProviderFalse{}).
|
||||||
NewUserConflictResolver([]*imapservice.Connector{connector})
|
NewConflictResolver([]*imapservice.Connector{connector})
|
||||||
|
|
||||||
visited := make(map[string]bool)
|
visited := make(map[string]bool)
|
||||||
fn, err := resolver.ResolveConflict(ctx, label, visited)
|
fn, err := resolver.ResolveConflict(ctx, label, visited)
|
||||||
@ -351,7 +356,7 @@ func TestResolveLabelDiscrepancy_LabelSwap(t *testing.T) {
|
|||||||
|
|
||||||
for _, label := range apiLabels {
|
for _, label := range apiLabels {
|
||||||
mockClient.
|
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)
|
Return(label, nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -360,7 +365,7 @@ func TestResolveLabelDiscrepancy_LabelSwap(t *testing.T) {
|
|||||||
connectors := []*imapservice.Connector{connector}
|
connectors := []*imapservice.Connector{connector}
|
||||||
|
|
||||||
manager := imapservice.NewLabelConflictManager(mockLabelProvider, mockIDProvider, mockClient, mockReporter, ffProviderFalse{})
|
manager := imapservice.NewLabelConflictManager(mockLabelProvider, mockIDProvider, mockClient, mockReporter, ffProviderFalse{})
|
||||||
resolver := manager.NewUserConflictResolver(connectors)
|
resolver := manager.NewConflictResolver(connectors)
|
||||||
|
|
||||||
visited := make(map[string]bool)
|
visited := make(map[string]bool)
|
||||||
fn, err := resolver.ResolveConflict(context.Background(), apiLabels[0], visited)
|
fn, err := resolver.ResolveConflict(context.Background(), apiLabels[0], visited)
|
||||||
@ -444,7 +449,7 @@ func TestResolveLabelDiscrepancy_LabelSwapExtended(t *testing.T) {
|
|||||||
|
|
||||||
for _, label := range apiLabels {
|
for _, label := range apiLabels {
|
||||||
mockClient.
|
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)
|
Return(label, nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -453,7 +458,7 @@ func TestResolveLabelDiscrepancy_LabelSwapExtended(t *testing.T) {
|
|||||||
connectors := []*imapservice.Connector{connector}
|
connectors := []*imapservice.Connector{connector}
|
||||||
|
|
||||||
manager := imapservice.NewLabelConflictManager(mockLabelProvider, mockIDProvider, mockClient, mockReporter, ffProviderFalse{})
|
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))
|
fn, err := resolver.ResolveConflict(context.Background(), apiLabels[0], make(map[string]bool))
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
@ -529,7 +534,7 @@ func TestResolveLabelDiscrepancy_LabelSwapCyclic(t *testing.T) {
|
|||||||
|
|
||||||
for _, label := range apiLabels {
|
for _, label := range apiLabels {
|
||||||
mockClient.
|
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)
|
Return(label, nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -538,7 +543,7 @@ func TestResolveLabelDiscrepancy_LabelSwapCyclic(t *testing.T) {
|
|||||||
connectors := []*imapservice.Connector{connector}
|
connectors := []*imapservice.Connector{connector}
|
||||||
|
|
||||||
manager := imapservice.NewLabelConflictManager(mockLabelProvider, mockIDProvider, mockClient, mockReporter, ffProviderFalse{})
|
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))
|
fn, err := resolver.ResolveConflict(context.Background(), apiLabels[0], make(map[string]bool))
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
@ -602,17 +607,17 @@ func TestResolveLabelDiscrepancy_LabelSwapCyclicWithDeletedLabel(t *testing.T) {
|
|||||||
|
|
||||||
for _, label := range apiLabels {
|
for _, label := range apiLabels {
|
||||||
mockClient.
|
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)
|
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 := &imapservice.Connector{}
|
||||||
connector.SetAddrIDTest("addr-1")
|
connector.SetAddrIDTest("addr-1")
|
||||||
connectors := []*imapservice.Connector{connector}
|
connectors := []*imapservice.Connector{connector}
|
||||||
|
|
||||||
manager := imapservice.NewLabelConflictManager(mockLabelProvider, mockIDProvider, mockClient, mockReporter, ffProviderFalse{})
|
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))
|
fn, err := resolver.ResolveConflict(context.Background(), apiLabels[2], make(map[string]bool))
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
@ -665,17 +670,17 @@ func TestResolveLabelDiscrepancy_LabelSwapCyclicWithDeletedLabel_KillSwitchEnabl
|
|||||||
|
|
||||||
for _, label := range apiLabels {
|
for _, label := range apiLabels {
|
||||||
mockClient.
|
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)
|
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 := &imapservice.Connector{}
|
||||||
connector.SetAddrIDTest("addr-1")
|
connector.SetAddrIDTest("addr-1")
|
||||||
connectors := []*imapservice.Connector{connector}
|
connectors := []*imapservice.Connector{connector}
|
||||||
|
|
||||||
manager := imapservice.NewLabelConflictManager(mockLabelProvider, mockIDProvider, mockClient, mockReporter, ffProviderTrue{})
|
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))
|
fn, err := resolver.ResolveConflict(context.Background(), apiLabels[2], make(map[string]bool))
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
@ -706,7 +711,8 @@ func TestInternalLabelConflictResolver_NoConflicts(t *testing.T) {
|
|||||||
manager := imapservice.NewLabelConflictManager(mockLabelProvider, mockIDProvider, mockClient, mockReporter, ffProviderFalse{})
|
manager := imapservice.NewLabelConflictManager(mockLabelProvider, mockIDProvider, mockClient, mockReporter, ffProviderFalse{})
|
||||||
resolver := manager.NewInternalLabelConflictResolver(connectors)
|
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)
|
assert.NoError(t, err)
|
||||||
|
|
||||||
updates := fn()
|
updates := fn()
|
||||||
@ -735,81 +741,159 @@ func TestInternalLabelConflictResolver_CorrectIDs(t *testing.T) {
|
|||||||
manager := imapservice.NewLabelConflictManager(mockLabelProvider, mockIDProvider, mockClient, mockReporter, ffProviderFalse{})
|
manager := imapservice.NewLabelConflictManager(mockLabelProvider, mockIDProvider, mockClient, mockReporter, ffProviderFalse{})
|
||||||
resolver := manager.NewInternalLabelConflictResolver(connectors)
|
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)
|
assert.NoError(t, err)
|
||||||
|
|
||||||
updates := fn()
|
updates := fn()
|
||||||
assert.Empty(t, updates)
|
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()
|
ctx := context.Background()
|
||||||
|
|
||||||
mockLabelProvider := new(mockLabelNameProvider)
|
mockLabelProvider := new(mockLabelNameProvider)
|
||||||
mockClient := new(mockAPIClient)
|
mockClient := new(mockAPIClient)
|
||||||
mockIDProvider := new(mockIDProvider)
|
mockIDProvider := new(mockIDProvider)
|
||||||
mockReporter := new(mockReporter)
|
mockReporter := new(mockReporter)
|
||||||
|
mockCountProvider := new(mockMailboxCountProvider)
|
||||||
|
|
||||||
mockIDProvider.On("GetGluonID", "addr-1").Return("gluon-id-1", true)
|
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"}).
|
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"}).
|
mockLabelProvider.On("GetUserMailboxByName", mock.Anything, "gluon-id-1", []string{"Labels"}).
|
||||||
Return(imap.MailboxData{}, db.ErrNotFound)
|
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 := &imapservice.Connector{}
|
||||||
connector.SetAddrIDTest("addr-1")
|
connector.SetAddrIDTest("addr-1")
|
||||||
|
mockCountProvider.On("GetUserMailboxCountByInternalID",
|
||||||
|
mock.Anything,
|
||||||
|
"addr-1",
|
||||||
|
imap.InternalMailboxID(123)).
|
||||||
|
Return(0, nil)
|
||||||
|
|
||||||
|
connector.SetMailboxCountProviderTest(mockCountProvider)
|
||||||
connectors := []*imapservice.Connector{connector}
|
connectors := []*imapservice.Connector{connector}
|
||||||
|
|
||||||
manager := imapservice.NewLabelConflictManager(mockLabelProvider, mockIDProvider, mockClient, mockReporter, ffProviderFalse{})
|
manager := imapservice.NewLabelConflictManager(mockLabelProvider, mockIDProvider, mockClient, mockReporter, ffProviderFalse{})
|
||||||
resolver := manager.NewInternalLabelConflictResolver(connectors)
|
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)
|
assert.NoError(t, err)
|
||||||
|
|
||||||
updates := fn()
|
updates := fn()
|
||||||
assert.Len(t, updates, 1)
|
assert.Len(t, updates, 1)
|
||||||
|
|
||||||
deleted, ok := updates[0].(*imap.MailboxDeleted)
|
deleted, ok := updates[0].(*imap.MailboxDeletedSilent)
|
||||||
assert.True(t, ok)
|
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()
|
ctx := context.Background()
|
||||||
|
|
||||||
mockLabelProvider := new(mockLabelNameProvider)
|
mockLabelProvider := new(mockLabelNameProvider)
|
||||||
mockClient := new(mockAPIClient)
|
mockClient := new(mockAPIClient)
|
||||||
mockIDProvider := new(mockIDProvider)
|
mockIDProvider := new(mockIDProvider)
|
||||||
mockReporter := new(mockReporter)
|
mockReporter := new(mockReporter)
|
||||||
|
mockCountProvider := new(mockMailboxCountProvider)
|
||||||
|
|
||||||
mockIDProvider.On("GetGluonID", "addr-1").Return("gluon-id-1", true)
|
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"}).
|
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"}).
|
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 := &imapservice.Connector{}
|
||||||
connector.SetAddrIDTest("addr-1")
|
connector.SetAddrIDTest("addr-1")
|
||||||
|
mockCountProvider.On("GetUserMailboxCountByInternalID",
|
||||||
|
mock.Anything,
|
||||||
|
"addr-1",
|
||||||
|
imap.InternalMailboxID(123)).
|
||||||
|
Return(10, nil)
|
||||||
|
|
||||||
|
connector.SetMailboxCountProviderTest(mockCountProvider)
|
||||||
connectors := []*imapservice.Connector{connector}
|
connectors := []*imapservice.Connector{connector}
|
||||||
|
|
||||||
manager := imapservice.NewLabelConflictManager(mockLabelProvider, mockIDProvider, mockClient, mockReporter, ffProviderFalse{})
|
manager := imapservice.NewLabelConflictManager(mockLabelProvider, mockIDProvider, mockClient, mockReporter, ffProviderFalse{})
|
||||||
resolver := manager.NewInternalLabelConflictResolver(connectors)
|
resolver := manager.NewInternalLabelConflictResolver(connectors)
|
||||||
|
|
||||||
fn, err := resolver.ResolveConflict(ctx)
|
// API labels don't contain the conflicting label ID
|
||||||
assert.NoError(t, err)
|
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()
|
updates := fn()
|
||||||
assert.Len(t, updates, 2)
|
assert.Empty(t, updates, 0)
|
||||||
|
}
|
||||||
|
|
||||||
updateOne, ok := updates[0].(*imap.MailboxDeleted)
|
func TestInternalLabelConflictResolver_ConflictingAPILabelSameName(t *testing.T) {
|
||||||
assert.True(t, ok)
|
ctx := context.Background()
|
||||||
assert.Equal(t, imap.MailboxID("Folders"), updateOne.MailboxID)
|
|
||||||
|
|
||||||
updateTwo, ok := updates[1].(*imap.MailboxDeleted)
|
mockLabelProvider := new(mockLabelNameProvider)
|
||||||
assert.True(t, ok)
|
mockClient := new(mockAPIClient)
|
||||||
assert.Equal(t, imap.MailboxID("Labels"), updateTwo.MailboxID)
|
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) {
|
func TestInternalLabelConflictResolver_MailboxFetchError(t *testing.T) {
|
||||||
@ -832,7 +916,8 @@ func TestInternalLabelConflictResolver_MailboxFetchError(t *testing.T) {
|
|||||||
manager := imapservice.NewLabelConflictManager(mockLabelProvider, mockIDProvider, mockClient, mockReporter, ffProviderFalse{})
|
manager := imapservice.NewLabelConflictManager(mockLabelProvider, mockIDProvider, mockClient, mockReporter, ffProviderFalse{})
|
||||||
resolver := manager.NewInternalLabelConflictResolver(connectors)
|
resolver := manager.NewInternalLabelConflictResolver(connectors)
|
||||||
|
|
||||||
_, err := resolver.ResolveConflict(ctx)
|
apiLabels := make(map[string]proton.Label)
|
||||||
|
_, err := resolver.ResolveConflict(ctx, apiLabels)
|
||||||
assert.Error(t, err)
|
assert.Error(t, err)
|
||||||
assert.Contains(t, err.Error(), "database connection error")
|
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{})
|
manager := imapservice.NewLabelConflictManager(mockLabelProvider, mockIDProvider, mockClient, mockReporter, ffProviderTrue{})
|
||||||
resolver := manager.NewInternalLabelConflictResolver(connectors)
|
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)
|
assert.NoError(t, err)
|
||||||
|
|
||||||
updates := fn()
|
updates := fn()
|
||||||
|
|||||||
@ -45,6 +45,10 @@ import (
|
|||||||
"golang.org/x/exp/slices"
|
"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.
|
// Connector contains all IMAP state required to satisfy sync and or imap queries.
|
||||||
type Connector struct {
|
type Connector struct {
|
||||||
addrID string
|
addrID string
|
||||||
@ -67,6 +71,8 @@ type Connector struct {
|
|||||||
|
|
||||||
sharedCache *SharedCache
|
sharedCache *SharedCache
|
||||||
syncState *SyncState
|
syncState *SyncState
|
||||||
|
|
||||||
|
mailboxCountProvider mailboxCountProvider
|
||||||
}
|
}
|
||||||
|
|
||||||
var errNoSenderAddressMatch = errors.New("no matching sender found in address list")
|
var errNoSenderAddressMatch = errors.New("no matching sender found in address list")
|
||||||
@ -82,6 +88,7 @@ func NewConnector(
|
|||||||
reporter reporter.Reporter,
|
reporter reporter.Reporter,
|
||||||
showAllMail bool,
|
showAllMail bool,
|
||||||
syncState *SyncState,
|
syncState *SyncState,
|
||||||
|
mailboxCountProvider mailboxCountProvider,
|
||||||
) *Connector {
|
) *Connector {
|
||||||
userID := identityState.UserID()
|
userID := identityState.UserID()
|
||||||
|
|
||||||
@ -115,6 +122,8 @@ func NewConnector(
|
|||||||
|
|
||||||
sharedCache: NewSharedCached(),
|
sharedCache: NewSharedCached(),
|
||||||
syncState: syncState,
|
syncState: syncState,
|
||||||
|
|
||||||
|
mailboxCountProvider: mailboxCountProvider,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -909,3 +918,12 @@ func (s *Connector) getSenderProtonAddress(p *parser.Parser) (proton.Address, er
|
|||||||
func (s *Connector) SetAddrIDTest(addrID string) {
|
func (s *Connector) SetAddrIDTest(addrID string) {
|
||||||
s.addrID = addrID
|
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 {
|
type labelDiscrepancy struct {
|
||||||
labelName string
|
labelName string
|
||||||
labelPath string
|
labelPath string
|
||||||
|
labelPathParsed string
|
||||||
labelID string
|
labelID string
|
||||||
conflictingLabelName string
|
conflictingLabelName string
|
||||||
conflictingLabelID string
|
conflictingLabelID string
|
||||||
@ -76,10 +77,12 @@ func newLabelDiscrepancy(label proton.Label, mbox imap.MailboxData, dType labelD
|
|||||||
if dType == discrepancyUser {
|
if dType == discrepancyUser {
|
||||||
discrepancy.labelName = algo.HashBase64SHA256(label.Name)
|
discrepancy.labelName = algo.HashBase64SHA256(label.Name)
|
||||||
discrepancy.labelPath = algo.HashBase64SHA256(joinStrings(label.Path))
|
discrepancy.labelPath = algo.HashBase64SHA256(joinStrings(label.Path))
|
||||||
|
discrepancy.labelPathParsed = algo.HashBase64SHA256(joinStrings(GetMailboxName(label)))
|
||||||
discrepancy.conflictingLabelName = algo.HashBase64SHA256(joinStrings(mbox.BridgeName))
|
discrepancy.conflictingLabelName = algo.HashBase64SHA256(joinStrings(mbox.BridgeName))
|
||||||
} else {
|
} else {
|
||||||
discrepancy.labelName = label.Name
|
discrepancy.labelName = label.Name
|
||||||
discrepancy.labelPath = joinStrings(label.Path)
|
discrepancy.labelPath = joinStrings(label.Path)
|
||||||
|
discrepancy.labelPathParsed = joinStrings(GetMailboxName(label))
|
||||||
discrepancy.conflictingLabelName = joinStrings(mbox.BridgeName)
|
discrepancy.conflictingLabelName = joinStrings(mbox.BridgeName)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -93,9 +96,10 @@ func discrepanciesToContext(discrepancies []labelDiscrepancy) reporter.Context {
|
|||||||
prefix := fmt.Sprintf("discrepancy_%d_", i)
|
prefix := fmt.Sprintf("discrepancy_%d_", i)
|
||||||
|
|
||||||
ctx[prefix+"type"] = d.Type.String()
|
ctx[prefix+"type"] = d.Type.String()
|
||||||
|
ctx[prefix+"label_id"] = d.labelID
|
||||||
ctx[prefix+"label_name"] = d.labelName
|
ctx[prefix+"label_name"] = d.labelName
|
||||||
ctx[prefix+"label_path"] = d.labelPath
|
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_name"] = d.conflictingLabelName
|
||||||
ctx[prefix+"conflicting_label_id"] = d.conflictingLabelID
|
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)
|
GetUserMailboxByName(ctx context.Context, addrID string, mailboxName []string) (imap.MailboxData, error)
|
||||||
|
|
||||||
|
GetUserMailboxCountByInternalID(ctx context.Context, addrID string, internalID imap.InternalMailboxID) (int, error)
|
||||||
|
|
||||||
GetOpenIMAPSessionCount() int
|
GetOpenIMAPSessionCount() int
|
||||||
|
|
||||||
GetRollingIMAPConnectionCount() int
|
GetRollingIMAPConnectionCount() int
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -77,6 +80,10 @@ func (n NullIMAPServerManager) GetUserMailboxByName(_ context.Context, _ string,
|
|||||||
return imap.MailboxData{}, nil
|
return imap.MailboxData{}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (n NullIMAPServerManager) GetUserMailboxCountByInternalID(_ context.Context, _ string, _ imap.InternalMailboxID) (int, error) {
|
||||||
|
return 0, nil
|
||||||
|
}
|
||||||
|
|
||||||
func (n NullIMAPServerManager) GetOpenIMAPSessionCount() int {
|
func (n NullIMAPServerManager) GetOpenIMAPSessionCount() int {
|
||||||
return 0
|
return 0
|
||||||
}
|
}
|
||||||
|
|||||||
@ -540,6 +540,7 @@ func (s *Service) buildConnectors() (map[string]*Connector, error) {
|
|||||||
s.reporter,
|
s.reporter,
|
||||||
s.showAllMail,
|
s.showAllMail,
|
||||||
s.syncStateProvider,
|
s.syncStateProvider,
|
||||||
|
s.serverManager,
|
||||||
)
|
)
|
||||||
|
|
||||||
return connectors, nil
|
return connectors, nil
|
||||||
@ -557,6 +558,7 @@ func (s *Service) buildConnectors() (map[string]*Connector, error) {
|
|||||||
s.reporter,
|
s.reporter,
|
||||||
s.showAllMail,
|
s.showAllMail,
|
||||||
s.syncStateProvider,
|
s.syncStateProvider,
|
||||||
|
s.serverManager,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -157,6 +157,7 @@ func addNewAddressSplitMode(ctx context.Context, s *Service, addrID string) erro
|
|||||||
s.reporter,
|
s.reporter,
|
||||||
s.showAllMail,
|
s.showAllMail,
|
||||||
s.syncStateProvider,
|
s.syncStateProvider,
|
||||||
|
s.serverManager,
|
||||||
)
|
)
|
||||||
|
|
||||||
if err := s.serverManager.AddIMAPUser(ctx, connector, connector.addrID, s.gluonIDProvider, s.syncStateProvider); err != nil {
|
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")
|
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))
|
conflictUpdatesGenerator, err := labelConflictResolver.ResolveConflict(ctx, event.Label, make(map[string]bool))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return updates, err
|
return updates, err
|
||||||
@ -150,7 +150,7 @@ func onLabelUpdated(ctx context.Context, s *Service, event proton.LabelEvent) ([
|
|||||||
wr.SetLabel(apiLabel.ID, apiLabel, "onLabelUpdatedApiID")
|
wr.SetLabel(apiLabel.ID, apiLabel, "onLabelUpdatedApiID")
|
||||||
|
|
||||||
// Resolve potential conflicts
|
// 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))
|
conflictUpdatesGenerator, err := labelConflictResolver.ResolveConflict(ctx, apiLabel, make(map[string]bool))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return updates, err
|
return updates, err
|
||||||
|
|||||||
@ -133,12 +133,10 @@ 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) {
|
func syncLabels(ctx context.Context, labels map[string]proton.Label, connectors []*Connector, labelConflictManager *LabelConflictManager) ([]imap.Update, error) {
|
||||||
var updates []imap.Update
|
var updates []imap.Update
|
||||||
|
|
||||||
userLabelConflictResolver := labelConflictManager.NewUserConflictResolver(connectors)
|
userLabelConflictResolver := labelConflictManager.NewConflictResolver(connectors)
|
||||||
internalLabelConflictResolver := labelConflictManager.NewInternalLabelConflictResolver(connectors)
|
internalLabelConflictResolver := labelConflictManager.NewInternalLabelConflictResolver(connectors)
|
||||||
|
|
||||||
// Create placeholder Folders/Labels mailboxes with the \Noselect attribute.
|
conflictUpdateGenerator, err := internalLabelConflictResolver.ResolveConflict(ctx, labels)
|
||||||
for _, prefix := range []string{folderPrefix, labelPrefix} {
|
|
||||||
conflictUpdateGenerator, err := internalLabelConflictResolver.ResolveConflict(ctx)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return updates, err
|
return updates, err
|
||||||
}
|
}
|
||||||
@ -147,10 +145,19 @@ func syncLabels(ctx context.Context, labels map[string]proton.Label, connectors
|
|||||||
conflictUpdates := conflictUpdateGenerator()
|
conflictUpdates := conflictUpdateGenerator()
|
||||||
updateCh.publishUpdate(ctx, conflictUpdates...)
|
updateCh.publishUpdate(ctx, conflictUpdates...)
|
||||||
updates = append(updates, conflictUpdates...)
|
updates = append(updates, conflictUpdates...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create placeholder Folders/Labels mailboxes with the \Noselect attribute.
|
||||||
|
for _, prefix := range []string{folderPrefix, labelPrefix} {
|
||||||
|
for _, updateCh := range connectors {
|
||||||
update := newPlaceHolderMailboxCreatedUpdate(prefix)
|
update := newPlaceHolderMailboxCreatedUpdate(prefix)
|
||||||
updateCh.publishUpdate(ctx, update)
|
updateCh.publishUpdate(ctx, update)
|
||||||
updates = append(updates, 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)
|
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 {
|
func (sm *Service) GetOpenIMAPSessionCount() int {
|
||||||
return sm.imapServer.GetOpenSessionCount()
|
return sm.imapServer.GetOpenSessionCount()
|
||||||
}
|
}
|
||||||
|
|||||||
@ -44,6 +44,7 @@ const (
|
|||||||
LabelConflictResolverDisabled = "InboxBridgeLabelConflictResolverDisabled"
|
LabelConflictResolverDisabled = "InboxBridgeLabelConflictResolverDisabled"
|
||||||
SMTPSubmissionRequestSentryReportDisabled = "InboxBridgeSmtpSubmissionRequestSentryReportDisabled"
|
SMTPSubmissionRequestSentryReportDisabled = "InboxBridgeSmtpSubmissionRequestSentryReportDisabled"
|
||||||
InternalLabelConflictResolverDisabled = "InboxBridgeUnexpectedFoldersLabelsStartupFixupDisabled"
|
InternalLabelConflictResolverDisabled = "InboxBridgeUnexpectedFoldersLabelsStartupFixupDisabled"
|
||||||
|
ItnternalLabelConflictNonEmptyMailboxDeletion = "InboxBridgeUnknownNonEmptyMailboxDeletion"
|
||||||
)
|
)
|
||||||
|
|
||||||
type FeatureFlagValueProvider interface {
|
type FeatureFlagValueProvider interface {
|
||||||
|
|||||||
@ -146,6 +146,11 @@ func (r *reportRecorder) ReportMessageWithContext(message string, context report
|
|||||||
return nil
|
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 {
|
func (r *reportRecorder) ReportExceptionWithContext(data any, context reporter.Context) error {
|
||||||
if context == nil {
|
if context == nil {
|
||||||
context = reporter.Context{}
|
context = reporter.Context{}
|
||||||
|
|||||||
Reference in New Issue
Block a user