Skip to content

Commit df4fb42

Browse files
committed
feat: Allow connector to persist initial UID validity of mailboxes
1 parent 87a8781 commit df4fb42

8 files changed

Lines changed: 104 additions & 37 deletions

File tree

connector/connector.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,12 @@ type Connector interface {
4949
// It is recommended that the returned channel is buffered with at least constants.ChannelBufferCount.
5050
GetUpdates() <-chan imap.Update
5151

52+
// GetUIDValidity returns the default UID validity for this user.
53+
GetUIDValidity() imap.UID
54+
55+
// SetUIDValidity sets the default UID validity for this user.
56+
SetUIDValidity(uidValidity imap.UID) error
57+
5258
// Close the connector will no longer be used and all resources should be closed/released.
5359
Close(ctx context.Context) error
5460
}

connector/dummy.go

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -246,6 +246,14 @@ func (conn *Dummy) MarkMessagesFlagged(ctx context.Context, messageIDs []imap.Me
246246
return nil
247247
}
248248

249+
func (conn *Dummy) GetUIDValidity() imap.UID {
250+
return 1
251+
}
252+
253+
func (conn *Dummy) SetUIDValidity(imap.UID) error {
254+
return nil
255+
}
256+
249257
func (conn *Dummy) Sync(ctx context.Context) error {
250258
for _, mailbox := range conn.state.getLabels() {
251259
update := imap.NewMailboxCreated(mailbox)

internal/backend/connector_updates.go

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -94,7 +94,18 @@ func (user *user) applyMailboxDeleted(ctx context.Context, update *imap.MailboxD
9494
}
9595

9696
return user.db.Write(ctx, func(ctx context.Context, tx *ent.Tx) error {
97-
return db.DeleteMailboxWithRemoteID(ctx, tx, update.MailboxID)
97+
uidValidity, increased, err := db.DeleteMailboxWithRemoteID(ctx, tx, update.MailboxID)
98+
if err != nil {
99+
return err
100+
}
101+
102+
if increased {
103+
if err := user.connector.SetUIDValidity(uidValidity); err != nil {
104+
return err
105+
}
106+
}
107+
108+
return nil
98109
})
99110
}
100111

internal/backend/state_connector_impl.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -139,6 +139,10 @@ func (sc *stateConnectorImpl) SetMessagesFlagged(ctx context.Context, messageIDs
139139
return nil
140140
}
141141

142+
func (sc *stateConnectorImpl) SetUIDValidity(uidValidity imap.UID) error {
143+
return sc.connector.SetUIDValidity(uidValidity)
144+
}
145+
142146
func (sc *stateConnectorImpl) getMetadataValue(key string) any {
143147
v, ok := sc.metadata[key]
144148
if !ok {

internal/backend/user.go

Lines changed: 16 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,14 @@ type user struct {
4242
messageIDCounter uint64
4343
}
4444

45-
func newUser(ctx context.Context, userID string, database *db.DB, conn connector.Connector, store store.Store, delimiter string) (*user, error) {
45+
func newUser(
46+
ctx context.Context,
47+
userID string,
48+
database *db.DB,
49+
conn connector.Connector,
50+
store store.Store,
51+
delimiter string,
52+
) (*user, error) {
4653
if err := database.Init(ctx); err != nil {
4754
return nil, err
4855
}
@@ -55,6 +62,13 @@ func newUser(ctx context.Context, userID string, database *db.DB, conn connector
5562
return nil, err
5663
}
5764

65+
// Init the global UID validity.
66+
if err := database.Write(ctx, func(ctx context.Context, tx *ent.Tx) error {
67+
return db.InitGlobalUIDValidity(ctx, tx, conn.GetUIDValidity())
68+
}); err != nil {
69+
return nil, err
70+
}
71+
5872
user := &user{
5973
userID: userID,
6074
connector: conn,
@@ -79,10 +93,8 @@ func newUser(ctx context.Context, userID string, database *db.DB, conn connector
7993
ctx, cancel := context.WithCancel(context.Background())
8094
defer cancel()
8195

82-
ctx = contexts.NewRemoteUpdateCtx(ctx)
83-
8496
labels := pprof.Labels("go", "Connector Updates", "UserID", user.userID)
85-
pprof.Do(ctx, labels, func(_ context.Context) {
97+
pprof.Do(contexts.NewRemoteUpdateCtx(ctx), labels, func(_ context.Context) {
8698
updateCh := user.updateInjector.GetUpdates()
8799
for {
88100
select {

internal/db/mailbox.go

Lines changed: 47 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -70,27 +70,43 @@ func RenameMailboxWithRemoteID(ctx context.Context, tx *ent.Tx, mboxID imap.Labe
7070
return nil
7171
}
7272

73-
func DeleteMailboxWithRemoteID(ctx context.Context, tx *ent.Tx, mboxID imap.LabelID) error {
74-
// get uid validity
73+
// DeleteMailboxWithRemoteID deletes the mailbox with the given remote ID.
74+
// It returns the (potentially new) global UID validity, along with a bool indicating whether it has been increased.
75+
func DeleteMailboxWithRemoteID(ctx context.Context, tx *ent.Tx, mboxID imap.LabelID) (imap.UID, bool, error) {
7576
mbox, err := tx.Mailbox.Query().
7677
Where(mailbox.RemoteID(mboxID)).
7778
Select(mailbox.FieldUIDValidity).
7879
Only(ctx)
7980
if err != nil {
80-
return err
81+
return 0, false, err
8182
}
8283

83-
if err := updateGlobalUIDValidity(ctx, tx, mbox.UIDValidity); err != nil {
84-
return err
84+
curUIDValidity, err := getGlobalUIDValidity(ctx, tx)
85+
if err != nil {
86+
return 0, false, err
87+
}
88+
89+
var newUIDValidity imap.UID
90+
91+
if mbox.UIDValidity == curUIDValidity {
92+
newUIDValidity = curUIDValidity.Add(1)
93+
} else {
94+
newUIDValidity = curUIDValidity
95+
}
96+
97+
if newUIDValidity > curUIDValidity {
98+
if err := setGlobalUIDValidity(ctx, tx, newUIDValidity); err != nil {
99+
return 0, false, err
100+
}
85101
}
86102

87103
if _, err := tx.Mailbox.Delete().
88104
Where(mailbox.RemoteID(mboxID)).
89105
Exec(ctx); err != nil {
90-
return err
106+
return 0, false, err
91107
}
92108

93-
return nil
109+
return newUIDValidity, newUIDValidity > curUIDValidity, nil
94110
}
95111

96112
func UpdateRemoteMailboxID(ctx context.Context, tx *ent.Tx, internalID imap.InternalMailboxID, remoteID imap.LabelID) error {
@@ -303,42 +319,42 @@ func FilterMailboxContains(ctx context.Context, client *ent.Client, mboxID imap.
303319
return result, nil
304320
}
305321

306-
func getGlobalUIDValidity(ctx context.Context, tx *ent.Tx) (imap.UID, error) {
307-
globalUIDValidity, err := tx.UIDValidity.Query().
308-
Select(uidvalidity.FieldUIDValidity).
309-
Only(ctx)
322+
func InitGlobalUIDValidity(ctx context.Context, tx *ent.Tx, uidValidity imap.UID) error {
323+
curUIDValidity, err := tx.UIDValidity.Query().Select(uidvalidity.FieldUIDValidity).Only(ctx)
324+
if err != nil {
325+
if !ent.IsNotFound(err) {
326+
return fmt.Errorf("failed to get current UID validity: %w", err)
327+
}
310328

311-
if ent.IsNotFound(err) {
312-
_, err := tx.UIDValidity.Create().
313-
SetUIDValidity(mailbox.DefaultUIDValidity).
314-
Save(ctx)
315-
if err != nil {
316-
return 0, err
329+
if _, err := tx.UIDValidity.Create().SetUIDValidity(uidValidity).Save(ctx); err != nil {
330+
return fmt.Errorf("failed to create UID validity entry: %w", err)
317331
}
318332

319-
return mailbox.DefaultUIDValidity, nil
333+
return nil
320334
}
321335

322-
if err != nil {
323-
return 0, err
336+
if uidValidity > curUIDValidity.UIDValidity {
337+
if err := setGlobalUIDValidity(ctx, tx, uidValidity); err != nil {
338+
return fmt.Errorf("failed to set UID validity: %w", err)
339+
}
324340
}
325341

326-
return globalUIDValidity.UIDValidity, nil
342+
return nil
327343
}
328344

329-
func updateGlobalUIDValidity(ctx context.Context, tx *ent.Tx, deletedUIDValidity imap.UID) error {
330-
globalUIDValidity, err := getGlobalUIDValidity(ctx, tx)
345+
func getGlobalUIDValidity(ctx context.Context, tx *ent.Tx) (imap.UID, error) {
346+
uidValidity, err := tx.UIDValidity.Query().Select(uidvalidity.FieldUIDValidity).Only(ctx)
331347
if err != nil {
332-
return err
348+
return 0, err
333349
}
334350

335-
if deletedUIDValidity < globalUIDValidity {
336-
return nil
337-
}
351+
return uidValidity.UIDValidity, nil
352+
}
338353

339-
_, err = tx.UIDValidity.Update().
340-
SetUIDValidity(deletedUIDValidity.Add(1)).
341-
Save(ctx)
354+
func setGlobalUIDValidity(ctx context.Context, tx *ent.Tx, uidValidity imap.UID) error {
355+
if _, err := tx.UIDValidity.Update().SetUIDValidity(uidValidity).Save(ctx); err != nil {
356+
return err
357+
}
342358

343-
return err
359+
return nil
344360
}

internal/state/actions.go

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -61,10 +61,17 @@ func (state *State) actionDeleteMailbox(ctx context.Context, tx *ent.Tx, mboxID
6161
return err
6262
}
6363

64-
if err := db.DeleteMailboxWithRemoteID(ctx, tx, mboxID.RemoteID); err != nil {
64+
uidValidity, increased, err := db.DeleteMailboxWithRemoteID(ctx, tx, mboxID.RemoteID)
65+
if err != nil {
6566
return err
6667
}
6768

69+
if increased {
70+
if err := state.user.GetRemote().SetUIDValidity(uidValidity); err != nil {
71+
return err
72+
}
73+
}
74+
6875
return state.user.QueueOrApplyStateUpdate(ctx, tx, NewMailboxDeletedStateUpdate(mboxID.InternalID))
6976
}
7077

internal/state/connector.go

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

7070
// SetMessagesFlagged marks the message with the given ID as seen or unseen.
7171
SetMessagesFlagged(ctx context.Context, messageIDs []imap.MessageID, flagged bool) error
72+
73+
// SetUIDValidity sets the UID Validity for the user.
74+
SetUIDValidity(imap.UID) error
7275
}

0 commit comments

Comments
 (0)