Skip to content

Commit 2ecbdd2

Browse files
feat(GODT-2576): Support for Forward flags
Necessary changes required to support forward flags. These are non-standard IMAP flags that can be assigned to mailboxes. Thunderbird uses `$Forwarded` and Apple Mail issues both `$Forwarded` and `Forwarded`. Unfortunately, Outlook (office 365) does not support any non-standard IMAP flags. This patch extends the connector with the `MarkMessageForwarded` as the forwarded setting is applied after a message is sent. Every time we apply a forward flag, we ensure that all known forwarding flags are set so that it works with all known variations.
1 parent 6d69277 commit 2ecbdd2

File tree

14 files changed

+368
-98
lines changed

14 files changed

+368
-98
lines changed

benchmarks/gluon_bench/gluon_benchmarks/sync.go

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -156,6 +156,14 @@ func init() {
156156

157157
type nullIMAPStateWriter struct{}
158158

159+
func (n nullIMAPStateWriter) AddFlagsToAllMailboxes(ctx context.Context, flags ...string) error {
160+
panic("implement me")
161+
}
162+
163+
func (n nullIMAPStateWriter) AddPermFlagsToAllMailboxes(ctx context.Context, flags ...string) error {
164+
panic("implement me")
165+
}
166+
159167
func (n nullIMAPStateWriter) GetSettings(ctx context.Context) (string, bool, error) {
160168
return "", false, nil
161169
}

connector/connector.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,9 @@ type Connector interface {
5454
// MarkMessagesFlagged sets the flagged value of the given messages.
5555
MarkMessagesFlagged(ctx context.Context, cache IMAPStateWrite, messageIDs []imap.MessageID, flagged bool) error
5656

57+
// MarkMessagesForwarded sets the forwarded value of the give messages.
58+
MarkMessagesForwarded(ctx context.Context, cache IMAPStateWrite, messageIDs []imap.MessageID, forwarded bool) error
59+
5760
// GetUpdates returns a stream of updates that the gluon server should apply.
5861
GetUpdates() <-chan imap.Update
5962

connector/dummy.go

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -263,6 +263,19 @@ func (conn *Dummy) MarkMessagesFlagged(_ context.Context, _ IMAPStateWrite, mess
263263
return nil
264264
}
265265

266+
func (conn *Dummy) MarkMessagesForwarded(ctx context.Context, cache IMAPStateWrite, messageIDs []imap.MessageID, forwarded bool) error {
267+
for _, messageID := range messageIDs {
268+
conn.state.setForwarded(messageID, forwarded)
269+
270+
conn.pushUpdate(imap.NewMessageFlagsUpdated(
271+
messageID,
272+
conn.state.getMessageFlags(messageID),
273+
))
274+
}
275+
276+
return nil
277+
}
278+
266279
func (conn *Dummy) Sync(ctx context.Context) error {
267280
for _, mailbox := range conn.state.getMailboxes() {
268281
update := imap.NewMailboxCreated(mailbox)

connector/dummy_state.go

Lines changed: 19 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -27,12 +27,13 @@ type dummyMailbox struct {
2727
}
2828

2929
type dummyMessage struct {
30-
literal []byte
31-
parsed *imap.ParsedMessage
32-
seen bool
33-
flagged bool
34-
date time.Time
35-
flags imap.FlagSet
30+
literal []byte
31+
parsed *imap.ParsedMessage
32+
seen bool
33+
flagged bool
34+
forwarded bool
35+
date time.Time
36+
flags imap.FlagSet
3637

3738
mboxIDs map[imap.MailboxID]struct{}
3839
}
@@ -256,6 +257,13 @@ func (state *dummyState) setFlagged(messageID imap.MessageID, flagged bool) {
256257
state.messages[messageID].flagged = flagged
257258
}
258259

260+
func (state *dummyState) setForwarded(messageID imap.MessageID, forwarded bool) {
261+
state.lock.Lock()
262+
defer state.lock.Unlock()
263+
264+
state.messages[messageID].forwarded = forwarded
265+
}
266+
259267
func (state *dummyState) isSeen(messageID imap.MessageID) bool {
260268
state.lock.Lock()
261269
defer state.lock.Unlock()
@@ -294,6 +302,11 @@ func (state *dummyState) getMessageFlags(messageID imap.MessageID) imap.FlagSet
294302
flags.AddToSelf(imap.FlagFlagged)
295303
}
296304

305+
if msg.forwarded {
306+
flags.AddToSelf(imap.XFlagForwarded)
307+
flags.AddToSelf(imap.XFlagDollarForwarded)
308+
}
309+
297310
return flags
298311
}
299312

connector/state.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,4 +58,8 @@ type IMAPStateWrite interface {
5858
// transformation necessary to ensure that new parent or child mailboxes are created as expected by a regular
5959
// IMAP rename operation.
6060
PatchMailboxHierarchyWithoutTransforms(ctx context.Context, id imap.MailboxID, newName []string) error
61+
62+
AddFlagsToAllMailboxes(ctx context.Context, flags ...string) error
63+
64+
AddPermFlagsToAllMailboxes(ctx context.Context, flags ...string) error
6165
}

db/ops_mailbox.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -105,6 +105,10 @@ type MailboxWriteOps interface {
105105
UpdateRemoteMailboxID(ctx context.Context, mobxID imap.InternalMailboxID, remoteID imap.MailboxID) error
106106

107107
SetMailboxUIDValidity(ctx context.Context, mboxID imap.InternalMailboxID, uidValidity imap.UID) error
108+
109+
AddFlagsToAllMailboxes(ctx context.Context, flags ...string) error
110+
111+
AddPermFlagsToAllMailboxes(ctx context.Context, flags ...string) error
108112
}
109113

110114
type SnapshotMessageResult struct {

imap/flags.go

Lines changed: 34 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -9,23 +9,37 @@ import (
99
)
1010

1111
const (
12-
FlagSeen = `\Seen`
13-
FlagAnswered = `\Answered`
14-
FlagFlagged = `\Flagged`
15-
FlagDeleted = `\Deleted`
16-
FlagDraft = `\Draft`
17-
FlagRecent = `\Recent` // Read-only!.
12+
FlagSeen = `\Seen`
13+
FlagAnswered = `\Answered`
14+
FlagFlagged = `\Flagged`
15+
FlagDeleted = `\Deleted`
16+
FlagDraft = `\Draft`
17+
FlagRecent = `\Recent` // Read-only!.
18+
XFlagDollarForwarded = "$Forwarded" // Non-Standard flag
19+
XFlagForwarded = "Forwarded" // Non-Standard flag
1820
)
1921

2022
const (
21-
FlagSeenLowerCase = `\seen`
22-
FlagAnsweredLowerCase = `\answered`
23-
FlagFlaggedLowerCase = `\flagged`
24-
FlagDeletedLowerCase = `\deleted`
25-
FlagDraftLowerCase = `\draft`
26-
FlagRecentLowerCase = `\recent` // Read-only!.
23+
FlagSeenLowerCase = `\seen`
24+
FlagAnsweredLowerCase = `\answered`
25+
FlagFlaggedLowerCase = `\flagged`
26+
FlagDeletedLowerCase = `\deleted`
27+
FlagDraftLowerCase = `\draft`
28+
FlagRecentLowerCase = `\recent` // Read-only!.
29+
XFlagDollarForwardedLowerCase = "$forwarded"
30+
XFlagForwardedLowerCase = "forwarded"
2731
)
2832

33+
var ForwardFlagList = []string{
34+
XFlagDollarForwarded,
35+
XFlagForwarded,
36+
}
37+
38+
var ForwardFlagListLowerCase = []string{
39+
XFlagDollarForwardedLowerCase,
40+
XFlagForwardedLowerCase,
41+
}
42+
2943
// FlagSet represents a set of IMAP flags. Flags are case-insensitive and no duplicates are allowed.
3044
type FlagSet map[string]string
3145

@@ -89,6 +103,14 @@ func (fs FlagSet) ContainsAny(flags ...string) bool {
89103
}) >= 0
90104
}
91105

106+
// ContainsAnyUnchecked returns true if and only if any of the flags are in the set. The flag list is not converted to
107+
// lower case.
108+
func (fs FlagSet) ContainsAnyUnchecked(flags ...string) bool {
109+
return xslices.IndexFunc(flags, func(f string) bool {
110+
return fs.ContainsUnchecked(f)
111+
}) >= 0
112+
}
113+
92114
// ContainsAll returns true if and only if all of the flags are in the set.
93115
func (fs FlagSet) ContainsAll(flags ...string) bool {
94116
return xslices.IndexFunc(flags, func(f string) bool {

internal/backend/connector_state_write.go

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,14 @@ func (d *DBIMAPStateWrite) PatchMailboxHierarchyWithoutTransforms(ctx context.Co
9090
return d.tx.RenameMailboxWithRemoteID(ctx, id, combined)
9191
}
9292

93+
func (d *DBIMAPStateWrite) AddFlagsToAllMailboxes(ctx context.Context, flags ...string) error {
94+
return d.tx.AddFlagsToAllMailboxes(ctx, flags...)
95+
}
96+
97+
func (d *DBIMAPStateWrite) AddPermFlagsToAllMailboxes(ctx context.Context, flags ...string) error {
98+
return d.tx.AddPermFlagsToAllMailboxes(ctx, flags...)
99+
}
100+
93101
func (d *DBIMAPStateWrite) wrapStateUpdates(ctx context.Context, f func(ctx context.Context, tx db.Transaction) ([]state.Update, error)) error {
94102
updates, err := f(ctx, d.tx)
95103
if err == nil {

internal/backend/state_connector_impl.go

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -191,6 +191,23 @@ func (sc *stateConnectorImpl) GetMailboxVisibility(ctx context.Context,
191191
return sc.connector.GetMailboxVisibility(ctx, id)
192192
}
193193

194+
func (sc *stateConnectorImpl) SetMessagesForwarded(
195+
ctx context.Context,
196+
tx db.Transaction,
197+
messageIDs []imap.MessageID,
198+
forwarded bool,
199+
) ([]state.Update, error) {
200+
ctx = sc.newContextWithMetadata(ctx)
201+
202+
cache := sc.newDBIMAPWrite(tx)
203+
204+
if err := sc.connector.MarkMessagesForwarded(ctx, &cache, messageIDs, forwarded); err != nil {
205+
return nil, err
206+
}
207+
208+
return cache.stateUpdates, nil
209+
}
210+
194211
func (sc *stateConnectorImpl) getMetadataValue(key string) any {
195212
v, ok := sc.metadata[key]
196213
if !ok {

internal/db_impl/sqlite3/utils/tracer.go

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -460,3 +460,15 @@ func (w WriteTracer) StoreConnectorSettings(ctx context.Context, settings string
460460

461461
return w.TX.StoreConnectorSettings(ctx, settings)
462462
}
463+
464+
func (w WriteTracer) AddFlagsToAllMailboxes(ctx context.Context, flags ...string) error {
465+
w.Entry.Tracef("AddFlagsToAllMailboxes")
466+
467+
return w.TX.AddFlagsToAllMailboxes(ctx, flags...)
468+
}
469+
470+
func (w WriteTracer) AddPermFlagsToAllMailboxes(ctx context.Context, flags ...string) error {
471+
w.Entry.Tracef("AddPermFlagsToAllMailboxes")
472+
473+
return w.TX.AddPermFlagsToAllMailboxes(ctx, flags...)
474+
}

internal/db_impl/sqlite3/write_ops.go

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -669,3 +669,43 @@ func (w writeOps) StoreConnectorSettings(ctx context.Context, settings string) e
669669

670670
return err
671671
}
672+
673+
func (w writeOps) AddFlagsToAllMailboxes(ctx context.Context, flags ...string) error {
674+
flagsJoined := strings.Join(xslices.Map(flags, func(s string) string {
675+
return "('" + s + "')"
676+
}), ",")
677+
678+
queryInsert := fmt.Sprintf(
679+
"INSERT OR IGNORE INTO %v (`%v`, `%v`) SELECT `%v`,`value` FROM %v CROSS JOIN (WITH T(value) AS (VALUES %v) SELECT * FROM T)",
680+
v1.MailboxFlagsTableName,
681+
v1.MailboxFlagsFieldMailboxID,
682+
v1.MailboxFlagsFieldValue,
683+
v1.MailboxesFieldID,
684+
v1.MailboxesTableName,
685+
flagsJoined,
686+
)
687+
688+
_, err := utils.ExecQuery(ctx, w.qw, queryInsert)
689+
690+
return err
691+
}
692+
693+
func (w writeOps) AddPermFlagsToAllMailboxes(ctx context.Context, flags ...string) error {
694+
flagsJoined := strings.Join(xslices.Map(flags, func(s string) string {
695+
return "('" + s + "')"
696+
}), ",")
697+
698+
queryInsert := fmt.Sprintf(
699+
"INSERT OR IGNORE INTO %v (`%v`, `%v`) SELECT `%v`,`value` FROM %v CROSS JOIN (WITH T(value) AS (VALUES %v) SELECT * FROM T)",
700+
v1.MailboxPermFlagsTableName,
701+
v1.MailboxPermFlagsFieldMailboxID,
702+
v1.MailboxPermFlagsFieldValue,
703+
v1.MailboxesFieldID,
704+
v1.MailboxesTableName,
705+
flagsJoined,
706+
)
707+
708+
_, err := utils.ExecQuery(ctx, w.qw, queryInsert)
709+
710+
return err
711+
}

internal/state/connector.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -80,4 +80,7 @@ type Connector interface {
8080

8181
// GetMailboxVisibility retrieves the visibility status of a mailbox for a client.
8282
GetMailboxVisibility(ctx context.Context, id imap.MailboxID) imap.MailboxVisibility
83+
84+
// SetMessagesForwarded marks the message with the given ID as forwarded.
85+
SetMessagesForwarded(ctx context.Context, tx db.Transaction, messageIDs []imap.MessageID, forwarded bool) ([]Update, error)
8386
}

0 commit comments

Comments
 (0)