GODT-1954: Draft message support

Add special case handling for draft messages so that if a Draft is
updated via an event it is correctly updated on the IMAP client via a
the new `imap.MessageUpdated event`.

This patch also updates Gluon to the latest version.
This commit is contained in:
Jakub
2022-10-31 13:52:11 +01:00
committed by James Houlahan
parent c548ba85fe
commit 1e29a5210f
14 changed files with 286 additions and 53 deletions

View File

@ -18,6 +18,7 @@
package logging
import (
"context"
"fmt"
"io"
"os"
@ -147,3 +148,26 @@ func MatchLogName(name string) bool {
func MatchGUILogName(name string) bool {
return regexp.MustCompile(`^gui_v.*\.log$`).MatchString(name)
}
type logKey string
const logrusFields = logKey("logrus")
func WithLogrusField(ctx context.Context, key string, value interface{}) context.Context {
fields, ok := ctx.Value(logrusFields).(logrus.Fields)
if !ok || fields == nil {
fields = logrus.Fields{}
}
fields[key] = value
return context.WithValue(ctx, logrusFields, fields)
}
func LogFromContext(ctx context.Context) *logrus.Entry {
fields, ok := ctx.Value(logrusFields).(logrus.Fields)
if !ok || fields == nil {
return logrus.WithField("ctx", "empty")
}
return logrus.WithFields(fields)
}

View File

@ -35,6 +35,10 @@ import (
// handleAPIEvent handles the given liteapi.Event.
func (user *User) handleAPIEvent(ctx context.Context, event liteapi.Event) error {
ctx = logging.WithLogrusField(ctx, "eventID", event.EventID)
logging.LogFromContext(ctx).Info("Handling event")
if event.User != nil {
if err := user.handleUserEvent(ctx, *event.User); err != nil {
return err
@ -308,19 +312,44 @@ func (user *User) handleDeleteLabelEvent(_ context.Context, event liteapi.LabelE
// handleMessageEvents handles the given message events.
func (user *User) handleMessageEvents(ctx context.Context, messageEvents []liteapi.MessageEvent) error {
for _, event := range messageEvents {
ctx = logging.WithLogrusField(ctx, "messageID", event.ID)
switch event.Action {
case liteapi.EventCreate:
if err := user.handleCreateMessageEvent(ctx, event); err != nil {
if err := user.handleCreateMessageEvent(
logging.WithLogrusField(ctx, "action", "create message"),
event,
); err != nil {
return fmt.Errorf("failed to handle create message event: %w", err)
}
case liteapi.EventUpdate, liteapi.EventUpdateFlags:
if err := user.handleUpdateMessageEvent(ctx, event); err != nil {
// GODT-2028 - Use better events here. It should be possible to have 3 separate events that refrain to
// whether the flags, labels or read only data (header+body) has been changed. This requires fixing liteapi
// first so that it correctly reports those cases.
// Issue regular update to handle mailboxes and flag changes.
if err := user.handleUpdateMessageEvent(
logging.WithLogrusField(ctx, "action", "update message"),
event,
); err != nil {
return fmt.Errorf("failed to handle update message event: %w", err)
}
// Only issue body updates if the message is a draft.
if event.Message.IsDraft() {
if err := user.handleUpdateDraftEvent(
logging.WithLogrusField(ctx, "action", "update draft"),
event,
); err != nil {
return fmt.Errorf("failed to handle update draft event: %w", err)
}
}
case liteapi.EventDelete:
if err := user.handleDeleteMessageEvent(ctx, event); err != nil {
if err := user.handleDeleteMessageEvent(
logging.WithLogrusField(ctx, "action", "delete message"),
event,
); err != nil {
return fmt.Errorf("failed to handle delete message event: %w", err)
}
}
@ -354,7 +383,7 @@ func (user *User) handleCreateMessageEvent(ctx context.Context, event liteapi.Me
}, user.apiUserLock, user.apiAddrsLock, user.updateChLock)
}
func (user *User) handleUpdateMessageEvent(_ context.Context, event liteapi.MessageEvent) error { //nolint:unparam
func (user *User) handleUpdateMessageEvent(ctx context.Context, event liteapi.MessageEvent) error { //nolint:unparam
return safe.RLockRet(func() error {
user.log.WithFields(logrus.Fields{
"messageID": event.ID,
@ -374,7 +403,7 @@ func (user *User) handleUpdateMessageEvent(_ context.Context, event liteapi.Mess
}, user.updateChLock)
}
func (user *User) handleDeleteMessageEvent(_ context.Context, event liteapi.MessageEvent) error { //nolint:unparam
func (user *User) handleDeleteMessageEvent(ctx context.Context, event liteapi.MessageEvent) error { //nolint:unparam
return safe.RLockRet(func() error {
user.log.WithField("messageID", event.ID).Info("Handling message deleted event")
@ -390,6 +419,27 @@ func (user *User) handleDeleteMessageEvent(_ context.Context, event liteapi.Mess
}, user.updateChLock)
}
func (user *User) handleUpdateDraftEvent(ctx context.Context, event liteapi.MessageEvent) error { //nolint:unparam
return safe.RLockRet(func() error {
full, err := user.client.GetFullMessage(ctx, event.Message.ID)
if err != nil {
return fmt.Errorf("failed to get full draft: %w", err)
}
return withAddrKR(user.apiUser, user.apiAddrs[event.Message.AddressID], user.vault.KeyPass(), func(_, addrKR *crypto.KeyRing) error {
buildRes, err := buildRFC822(full, addrKR)
if err != nil {
return fmt.Errorf("failed to build RFC822 draft: %w", err)
}
update := imap.NewMessageUpdated(buildRes.update.Message, buildRes.update.Literal, buildRes.update.MailboxIDs, buildRes.update.ParsedMessage)
user.updateCh[full.AddressID].Enqueue(update)
return nil
})
}, user.apiUserLock, user.apiAddrsLock, user.updateChLock)
}
func getMailboxName(label liteapi.Label) []string {
var name []string

View File

@ -275,6 +275,8 @@ func (conn *imapConnector) CreateMessage(
var wantFlags liteapi.MessageFlag
unread := !flags.Contains(imap.FlagSeen)
if mailboxID != liteapi.DraftsLabel {
header, err := rfc822.Parse(literal).ParseHeader()
if err != nil {
@ -294,13 +296,15 @@ func (conn *imapConnector) CreateMessage(
default:
wantFlags = wantFlags.Add(liteapi.MessageFlagSent)
}
} else {
unread = false
}
if flags.Contains(imap.FlagAnswered) {
wantFlags = wantFlags.Add(liteapi.MessageFlagReplied)
}
return conn.importMessage(ctx, literal, wantLabelIDs, wantFlags, !flags.Contains(imap.FlagSeen))
return conn.importMessage(ctx, literal, wantLabelIDs, wantFlags, unread)
}
// AddMessagesToMailbox labels the given messages with the given label ID.
@ -318,7 +322,7 @@ func (conn *imapConnector) RemoveMessagesFromMailbox(ctx context.Context, messag
return err
}
if mailboxID == liteapi.SpamLabel || mailboxID == liteapi.TrashLabel {
if mailboxID == liteapi.SpamLabel || mailboxID == liteapi.TrashLabel || mailboxID == liteapi.DraftsLabel {
var metadata []liteapi.MessageMetadata
// There's currently no limit on how many IDs we can filter on,
@ -331,9 +335,18 @@ func (conn *imapConnector) RemoveMessagesFromMailbox(ctx context.Context, messag
return err
}
m = xslices.Filter(m, func(m liteapi.MessageMetadata) bool {
return len(m.LabelIDs) == 1 && m.LabelIDs[0] == liteapi.AllMailLabel
})
if mailboxID == liteapi.DraftsLabel {
// Also have to check for all drafts label.
m = xslices.Filter(m, func(m liteapi.MessageMetadata) bool {
return len(m.LabelIDs) == 2 &&
((m.LabelIDs[0] == liteapi.AllMailLabel && m.LabelIDs[1] == liteapi.AllDraftsLabel) ||
(m.LabelIDs[1] == liteapi.AllMailLabel && m.LabelIDs[0] == liteapi.AllDraftsLabel))
})
} else {
m = xslices.Filter(m, func(m liteapi.MessageMetadata) bool {
return len(m.LabelIDs) == 1 && m.LabelIDs[0] == liteapi.AllMailLabel
})
}
metadata = append(metadata, m...)
}