Skip to content

Commit

Permalink
feat(GODT-2576): Support for Forward flags
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
LBeernaertProton committed Nov 14, 2023
1 parent 35e3607 commit 745a654
Show file tree
Hide file tree
Showing 14 changed files with 281 additions and 97 deletions.
8 changes: 8 additions & 0 deletions benchmarks/gluon_bench/gluon_benchmarks/sync.go
Original file line number Diff line number Diff line change
Expand Up @@ -156,6 +156,14 @@ func init() {

type nullIMAPStateWriter struct{}

func (n nullIMAPStateWriter) AddFlagsToAllMailboxes(ctx context.Context, flags ...string) error {
panic("implement me")
}

func (n nullIMAPStateWriter) AddPermFlagsToAllMailboxes(ctx context.Context, flags ...string) error {
panic("implement me")
}

func (n nullIMAPStateWriter) GetSettings(ctx context.Context) (string, bool, error) {
return "", false, nil
}
Expand Down
3 changes: 3 additions & 0 deletions connector/connector.go
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,9 @@ type Connector interface {
// MarkMessagesFlagged sets the flagged value of the given messages.
MarkMessagesFlagged(ctx context.Context, cache IMAPStateWrite, messageIDs []imap.MessageID, flagged bool) error

// MarkMessagesForwarded sets the forwarded value of the give messages.
MarkMessagesForwarded(ctx context.Context, cache IMAPStateWrite, messageIDs []imap.MessageID, forwarded bool) error

// GetUpdates returns a stream of updates that the gluon server should apply.
GetUpdates() <-chan imap.Update

Expand Down
13 changes: 13 additions & 0 deletions connector/dummy.go
Original file line number Diff line number Diff line change
Expand Up @@ -263,6 +263,19 @@ func (conn *Dummy) MarkMessagesFlagged(_ context.Context, _ IMAPStateWrite, mess
return nil
}

func (conn *Dummy) MarkMessagesForwarded(ctx context.Context, cache IMAPStateWrite, messageIDs []imap.MessageID, forwarded bool) error {
for _, messageID := range messageIDs {
conn.state.setForwarded(messageID, forwarded)

conn.pushUpdate(imap.NewMessageFlagsUpdated(
messageID,
conn.state.getMessageFlags(messageID),
))
}

return nil
}

func (conn *Dummy) Sync(ctx context.Context) error {
for _, mailbox := range conn.state.getMailboxes() {
update := imap.NewMailboxCreated(mailbox)
Expand Down
25 changes: 19 additions & 6 deletions connector/dummy_state.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,12 +27,13 @@ type dummyMailbox struct {
}

type dummyMessage struct {
literal []byte
parsed *imap.ParsedMessage
seen bool
flagged bool
date time.Time
flags imap.FlagSet
literal []byte
parsed *imap.ParsedMessage
seen bool
flagged bool
forwarded bool
date time.Time
flags imap.FlagSet

mboxIDs map[imap.MailboxID]struct{}
}
Expand Down Expand Up @@ -256,6 +257,13 @@ func (state *dummyState) setFlagged(messageID imap.MessageID, flagged bool) {
state.messages[messageID].flagged = flagged
}

func (state *dummyState) setForwarded(messageID imap.MessageID, forwarded bool) {
state.lock.Lock()
defer state.lock.Unlock()

state.messages[messageID].forwarded = forwarded
}

func (state *dummyState) isSeen(messageID imap.MessageID) bool {
state.lock.Lock()
defer state.lock.Unlock()
Expand Down Expand Up @@ -294,6 +302,11 @@ func (state *dummyState) getMessageFlags(messageID imap.MessageID) imap.FlagSet
flags.AddToSelf(imap.FlagFlagged)
}

if msg.forwarded {
flags.AddToSelf(imap.XFlagForwarded)
flags.AddToSelf(imap.XFlagDollarForwarded)
}

return flags
}

Expand Down
4 changes: 4 additions & 0 deletions connector/state.go
Original file line number Diff line number Diff line change
Expand Up @@ -58,4 +58,8 @@ type IMAPStateWrite interface {
// transformation necessary to ensure that new parent or child mailboxes are created as expected by a regular
// IMAP rename operation.
PatchMailboxHierarchyWithoutTransforms(ctx context.Context, id imap.MailboxID, newName []string) error

AddFlagsToAllMailboxes(ctx context.Context, flags ...string) error

AddPermFlagsToAllMailboxes(ctx context.Context, flags ...string) error
}
4 changes: 4 additions & 0 deletions db/ops_mailbox.go
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,10 @@ type MailboxWriteOps interface {
UpdateRemoteMailboxID(ctx context.Context, mobxID imap.InternalMailboxID, remoteID imap.MailboxID) error

SetMailboxUIDValidity(ctx context.Context, mboxID imap.InternalMailboxID, uidValidity imap.UID) error

AddFlagsToAllMailboxes(ctx context.Context, flags ...string) error

AddPermFlagsToAllMailboxes(ctx context.Context, flags ...string) error
}

type SnapshotMessageResult struct {
Expand Down
38 changes: 26 additions & 12 deletions imap/flags.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,23 +9,37 @@ import (
)

const (
FlagSeen = `\Seen`
FlagAnswered = `\Answered`
FlagFlagged = `\Flagged`
FlagDeleted = `\Deleted`
FlagDraft = `\Draft`
FlagRecent = `\Recent` // Read-only!.
FlagSeen = `\Seen`
FlagAnswered = `\Answered`
FlagFlagged = `\Flagged`
FlagDeleted = `\Deleted`
FlagDraft = `\Draft`
FlagRecent = `\Recent` // Read-only!.
XFlagDollarForwarded = "$Forwarded" // Non-Standard flag
XFlagForwarded = "Forwarded" // Non-Standard flag
)

const (
FlagSeenLowerCase = `\seen`
FlagAnsweredLowerCase = `\answered`
FlagFlaggedLowerCase = `\flagged`
FlagDeletedLowerCase = `\deleted`
FlagDraftLowerCase = `\draft`
FlagRecentLowerCase = `\recent` // Read-only!.
FlagSeenLowerCase = `\seen`
FlagAnsweredLowerCase = `\answered`
FlagFlaggedLowerCase = `\flagged`
FlagDeletedLowerCase = `\deleted`
FlagDraftLowerCase = `\draft`
FlagRecentLowerCase = `\recent` // Read-only!.
XFlagDollarForwardedLowerCase = "$forwarded"
XFlagForwardedLowerCase = "forwarded"
)

var ForwardFlagList = []string{
XFlagDollarForwarded,
XFlagForwarded,
}

var ForwardFlagListLowerCase = []string{
XFlagDollarForwardedLowerCase,
XFlagForwardedLowerCase,
}

// FlagSet represents a set of IMAP flags. Flags are case-insensitive and no duplicates are allowed.
type FlagSet map[string]string

Expand Down
8 changes: 8 additions & 0 deletions internal/backend/connector_state_write.go
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,14 @@ func (d *DBIMAPStateWrite) PatchMailboxHierarchyWithoutTransforms(ctx context.Co
return d.tx.RenameMailboxWithRemoteID(ctx, id, combined)
}

func (d *DBIMAPStateWrite) AddFlagsToAllMailboxes(ctx context.Context, flags ...string) error {
return d.tx.AddFlagsToAllMailboxes(ctx, flags...)
}

func (d *DBIMAPStateWrite) AddPermFlagsToAllMailboxes(ctx context.Context, flags ...string) error {
return d.tx.AddPermFlagsToAllMailboxes(ctx, flags...)
}

func (d *DBIMAPStateWrite) wrapStateUpdates(ctx context.Context, f func(ctx context.Context, tx db.Transaction) ([]state.Update, error)) error {
updates, err := f(ctx, d.tx)
if err == nil {
Expand Down
17 changes: 17 additions & 0 deletions internal/backend/state_connector_impl.go
Original file line number Diff line number Diff line change
Expand Up @@ -191,6 +191,23 @@ func (sc *stateConnectorImpl) GetMailboxVisibility(ctx context.Context,
return sc.connector.GetMailboxVisibility(ctx, id)
}

func (sc *stateConnectorImpl) SetMessagesForwarded(
ctx context.Context,
tx db.Transaction,
messageIDs []imap.MessageID,
forwarded bool,
) ([]state.Update, error) {
ctx = sc.newContextWithMetadata(ctx)

cache := sc.newDBIMAPWrite(tx)

if err := sc.connector.MarkMessagesForwarded(ctx, &cache, messageIDs, forwarded); err != nil {
return nil, err
}

return cache.stateUpdates, nil
}

func (sc *stateConnectorImpl) getMetadataValue(key string) any {
v, ok := sc.metadata[key]
if !ok {
Expand Down
12 changes: 12 additions & 0 deletions internal/db_impl/sqlite3/utils/tracer.go
Original file line number Diff line number Diff line change
Expand Up @@ -460,3 +460,15 @@ func (w WriteTracer) StoreConnectorSettings(ctx context.Context, settings string

return w.TX.StoreConnectorSettings(ctx, settings)
}

func (w WriteTracer) AddFlagsToAllMailboxes(ctx context.Context, flags ...string) error {
w.Entry.Tracef("AddFlagsToAllMailboxes")

return w.TX.AddFlagsToAllMailboxes(ctx, flags...)
}

func (w WriteTracer) AddPermFlagsToAllMailboxes(ctx context.Context, flags ...string) error {
w.Entry.Tracef("AddPermFlagsToAllMailboxes")

return w.TX.AddPermFlagsToAllMailboxes(ctx, flags...)
}
40 changes: 40 additions & 0 deletions internal/db_impl/sqlite3/write_ops.go
Original file line number Diff line number Diff line change
Expand Up @@ -669,3 +669,43 @@ func (w writeOps) StoreConnectorSettings(ctx context.Context, settings string) e

return err
}

func (w writeOps) AddFlagsToAllMailboxes(ctx context.Context, flags ...string) error {
flagsJoined := strings.Join(xslices.Map(flags, func(s string) string {
return "('" + s + "')"
}), ",")

queryInsert := fmt.Sprintf(
"INSERT OR IGNORE INTO %v (`%v`, `%v`) SELECT `%v`,`value` FROM %v CROSS JOIN (WITH T(value) AS (VALUES %v) SELECT * FROM T)",
v1.MailboxFlagsTableName,
v1.MailboxFlagsFieldMailboxID,
v1.MailboxFlagsFieldValue,
v1.MailboxesFieldID,
v1.MailboxesTableName,
flagsJoined,
)

_, err := utils.ExecQuery(ctx, w.qw, queryInsert)

return err
}

func (w writeOps) AddPermFlagsToAllMailboxes(ctx context.Context, flags ...string) error {
flagsJoined := strings.Join(xslices.Map(flags, func(s string) string {
return "('" + s + "')"
}), ",")

queryInsert := fmt.Sprintf(
"INSERT OR IGNORE INTO %v (`%v`, `%v`) SELECT `%v`,`value` FROM %v CROSS JOIN (WITH T(value) AS (VALUES %v) SELECT * FROM T)",
v1.MailboxPermFlagsTableName,
v1.MailboxPermFlagsFieldMailboxID,
v1.MailboxPermFlagsFieldValue,
v1.MailboxesFieldID,
v1.MailboxesTableName,
flagsJoined,
)

_, err := utils.ExecQuery(ctx, w.qw, queryInsert)

return err
}
3 changes: 3 additions & 0 deletions internal/state/connector.go
Original file line number Diff line number Diff line change
Expand Up @@ -80,4 +80,7 @@ type Connector interface {

// GetMailboxVisibility retrieves the visibility status of a mailbox for a client.
GetMailboxVisibility(ctx context.Context, id imap.MailboxID) imap.MailboxVisibility

// SetMessagesForwarded marks the message with the given ID as forwarded.
SetMessagesForwarded(ctx context.Context, tx db.Transaction, messageIDs []imap.MessageID, forwarded bool) ([]Update, error)
}
Loading

0 comments on commit 745a654

Please sign in to comment.