File: seqmap.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 (110 lines) | stat: -rw-r--r-- 2,705 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
package imap

import (
	"slices"
	"sync"

	"git.sr.ht/~rjarry/aerc/lib/log"
)

type SeqMap struct {
	lock sync.Mutex
	// map of IMAP sequence numbers to message UIDs
	m []uint32
}

// Initialize sets the initial seqmap of the mailbox
func (s *SeqMap) Initialize(uids []uint32) {
	s.lock.Lock()
	s.m = make([]uint32, len(uids))
	copy(s.m, uids)
	s.sort()
	s.lock.Unlock()
}

func (s *SeqMap) Size() int {
	s.lock.Lock()
	size := len(s.m)
	s.lock.Unlock()
	return size
}

// Get returns the UID of the given seqnum
func (s *SeqMap) Get(seqnum uint32) (uint32, bool) {
	if int(seqnum) > s.Size() || seqnum < 1 {
		return 0, false
	}
	s.lock.Lock()
	uid := s.m[seqnum-1]
	s.lock.Unlock()
	return uid, true
}

// Put adds a UID to the slice. Put should only be used to add new messages
// into the slice
func (s *SeqMap) Put(uid uint32) {
	s.lock.Lock()
	if slices.Contains(s.m, uid) {
		// We already have this UID, don't insert it.
		s.lock.Unlock()
		return
	}
	s.m = append(s.m, uid)
	s.sort()
	s.lock.Unlock()
}

// Take a snapshot of the SequenceNumber=>UID mappings for the given UIDs,
// remove those UIDs from the SeqMap, and return the snapshot it to the caller,
// as well as the loweest sequence number it contains.
func (s *SeqMap) Snapshot(uids []uint32) (map[uint32]uint32, uint32) {
	// Take the snapshot.
	snapshot := make(map[uint32]uint32)
	var minSequenceNum uint32 = 0
	var snapshotSeqNums []uint32
	s.lock.Lock()
	for num, uid := range s.m {
		if slices.Contains(uids, uid) {
			// IMAP sequence numbers start at 1
			seqNum := uint32(num) + 1
			snapshotSeqNums = append(snapshotSeqNums, seqNum)
			if minSequenceNum == 0 {
				minSequenceNum = seqNum
			}
			snapshot[seqNum] = uid
		}
	}
	s.lock.Unlock()

	// Remove the snapshotted mappings from the sequence; we need to do it from
	// the highest to the lowest key since a SeqMap.Pop moves all the items on
	// the right of the popped sequence number by one position to the left.
	for i := len(snapshotSeqNums) - 1; i >= 0; i-- {
		_, ok := s.Pop(snapshotSeqNums[i])
		if !ok {
			log.Errorf("Unable to pop %d from SeqMap", snapshotSeqNums[i])
		}
	}

	return snapshot, minSequenceNum
}

// Pop removes seqnum from the SeqMap. seqnum must be a valid seqnum, ie
// [1:size of mailbox]
func (s *SeqMap) Pop(seqnum uint32) (uint32, bool) {
	s.lock.Lock()
	defer s.lock.Unlock()
	if int(seqnum) > len(s.m) || seqnum < 1 {
		return 0, false
	}
	uid := s.m[seqnum-1]
	s.m = append(s.m[:seqnum-1], s.m[seqnum:]...)
	return uid, true
}

// sort sorts the slice in ascending UID order. See:
// https://datatracker.ietf.org/doc/html/rfc3501#section-2.3.1.2
func (s *SeqMap) sort() {
	// Always be sure the SeqMap is sorted
	slices.Sort(s.m)
}