File: expungehandler.go

package info (click to toggle)
aerc 0.21.0-2
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid
  • size: 3,900 kB
  • sloc: ansic: 1,181; python: 1,000; sh: 553; awk: 360; makefile: 23
file content (128 lines) | stat: -rw-r--r-- 3,993 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
package imap

import (
	"sync"
)

// Provider dependent EXPUNGE handler
//
// To delete N items in a single command, aerc does the following:
//   1. Set the \Deleted flag on those N items (identified by their sequence
//      number)
//   2. Call the EXPUNGE command
// It then gets N ExpungeData messages from go-imap, that reference the
// individual message actually deleted.
// Unfortunately the IMAP RFC does not specify the order in which those
// messages should be sent, and different providers have different policies.
// In particular:
//   - GMail and FastMail delete messages by increasing sequence number, and
//     at each individual delete, decrement the sequence number of all the
//     messages that still need to be deleted.
//   - Office 365 deletes messages by decreasing sequence number.
//   - Dovecot deletes messages in a seemingly random order.
// The role of ExpungeHandler is to abstract out those differences, and
// automatically adapt to the IMAP server's behaviour.
// Since there's a non-zero probability that the automatic detection is wrong
// if the server deletes items in a random order, it's also possible to
// statically configure the expunge policy in accounts.conf.

// The IMAP server behaviour when deleting multiple messages.
const (
	// Automatically detect behaviour from the first reply.
	ExpungePolicyAuto = iota
	// The server deletes message in increasing sequence number. After each
	// delete, outstanding messages need to have their sequence numbers
	// decremented.
	ExpungePolicyLowToHigh
	// The server deletes messages in any order, but does not change any of the
	// sequence numbers.
	ExpungePolicyStable
)

type ExpungeHandler struct {
	lock      sync.Mutex
	worker    *IMAPWorker
	policy    int
	items     map[uint32]uint32
	minNum    uint32
	forDelete bool
	gotFirst  bool
}

// Create a new ExpungeHandler for a list of UIDs that are being deleted or
// moved.
func NewExpungeHandler(worker *IMAPWorker, uids []uint32, forDelete bool) *ExpungeHandler {
	snapshot, min := worker.seqMap.Snapshot(uids)
	return &ExpungeHandler{
		worker:    worker,
		policy:    worker.config.expungePolicy,
		items:     snapshot,
		minNum:    min,
		forDelete: forDelete,
		gotFirst:  false,
	}
}

// Translate the sequence number received from the IMAP server into the
// associated UID, deduce the policy used by the server from the first reply,
// and update the remaining mappings according to that policy if required.
func (h *ExpungeHandler) PopSequenceNumber(seqNum uint32) (uint32, bool) {
	h.lock.Lock()
	defer h.lock.Unlock()
	if !h.gotFirst {
		h.gotFirst = true
		logPrefix := "Configured"
		// This is the very first reply we get; use it to infer the IMAP
		// server's policy if the configuration asks us to.
		if h.policy == ExpungePolicyAuto {
			logPrefix = "Deduced"
			if seqNum == h.minNum {
				h.policy = ExpungePolicyLowToHigh
			} else {
				h.policy = ExpungePolicyStable
			}
		}
		switch h.policy {
		case ExpungePolicyLowToHigh:
			h.worker.worker.Debugf("%s expunge policy: low-to-high", logPrefix)
		case ExpungePolicyStable:
			h.worker.worker.Debugf("%s expunge policy: stable", logPrefix)
		}
	}
	// Resolve the UID from the sequence number and pop the expunger entry.
	uid, ok := h.items[seqNum]
	delete(h.items, seqNum)

	// If the server uses the "low to high" policy, we need to decrement all
	// the remaining entries since the server is doing the same on its end.
	if ok && h.policy == ExpungePolicyLowToHigh {
		newSeq := make(map[uint32]uint32)

		for s, uid := range h.items {
			newSeq[s-1] = uid
		}

		h.items = newSeq
	}

	if !ok {
		h.worker.worker.Errorf("Unexpected sequence number; consider" +
			"overriding the expunge-policy IMAP configuration")
	}

	return uid, ok
}

func (h *ExpungeHandler) IsExpungingForDelete(uid uint32) bool {
	h.lock.Lock()
	defer h.lock.Unlock()
	if !h.forDelete {
		return false
	}
	for _, u := range h.items {
		if u == uid {
			return true
		}
	}
	return false
}