mirror of
https://github.com/mjl-/mox.git
synced 2025-07-10 10:34:40 +03:00
add condstore & qresync imap extensions
for conditional storing and quick resynchronisation (not sure if mail clients are actually using it that). each message now has a "modseq". it is increased for each change. with condstore, imap clients can request changes since a certain modseq. that already allows quickly finding changes since a previous connection. condstore also allows storing (e.g. setting new message flags) only when the modseq of a message hasn't changed. qresync should make it fast for clients to get a full list of changed messages for a mailbox, including removals. we now also keep basic metadata of messages that have been removed (expunged). just enough (uid, modseq) to tell client that the messages have been removed. this does mean we have to be careful when querying messages from the database. we must now often filter the expunged messages out. we also keep "createseq", the modseq when a message was created. this will be useful for the jmap implementation.
This commit is contained in:
@ -80,15 +80,31 @@ func (ss numSet) containsUID(uid store.UID, uids []store.UID, searchResult []sto
|
||||
return false
|
||||
}
|
||||
|
||||
func (ss numSet) String() string {
|
||||
if ss.searchResult {
|
||||
return "$"
|
||||
}
|
||||
s := ""
|
||||
// contains returns whether the numset contains the number.
|
||||
// only allowed on basic, strictly increasing numsets.
|
||||
func (ss numSet) contains(v uint32) bool {
|
||||
for _, r := range ss.ranges {
|
||||
if s != "" {
|
||||
s += ","
|
||||
if r.first.number == v || r.last != nil && v > r.first.number && v <= r.last.number {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (ss numSet) empty() bool {
|
||||
return !ss.searchResult && len(ss.ranges) == 0
|
||||
}
|
||||
|
||||
// Strings returns the numset in zero or more strings of maxSize bytes. If
|
||||
// maxSize is <= 0, a single string is returned.
|
||||
func (ss numSet) Strings(maxSize int) []string {
|
||||
if ss.searchResult {
|
||||
return []string{"$"}
|
||||
}
|
||||
var l []string
|
||||
var line string
|
||||
for _, r := range ss.ranges {
|
||||
s := ""
|
||||
if r.first.star {
|
||||
s += "*"
|
||||
} else {
|
||||
@ -98,16 +114,41 @@ func (ss numSet) String() string {
|
||||
if r.first.star {
|
||||
panic("invalid numSet range first star without last")
|
||||
}
|
||||
} else {
|
||||
s += ":"
|
||||
if r.last.star {
|
||||
s += "*"
|
||||
} else {
|
||||
s += fmt.Sprintf("%d", r.last.number)
|
||||
}
|
||||
}
|
||||
|
||||
nsize := len(line) + len(s)
|
||||
if line != "" {
|
||||
nsize++ // comma
|
||||
}
|
||||
if maxSize > 0 && nsize > maxSize {
|
||||
l = append(l, line)
|
||||
line = s
|
||||
continue
|
||||
}
|
||||
s += ":"
|
||||
if r.last.star {
|
||||
s += "*"
|
||||
} else {
|
||||
s += fmt.Sprintf("%d", r.last.number)
|
||||
if line != "" {
|
||||
line += ","
|
||||
}
|
||||
line += s
|
||||
}
|
||||
return s
|
||||
if line != "" {
|
||||
l = append(l, line)
|
||||
}
|
||||
return l
|
||||
}
|
||||
|
||||
func (ss numSet) String() string {
|
||||
l := ss.Strings(0)
|
||||
if len(l) == 0 {
|
||||
return ""
|
||||
}
|
||||
return l[0]
|
||||
}
|
||||
|
||||
type setNumber struct {
|
||||
@ -120,6 +161,127 @@ type numRange struct {
|
||||
last *setNumber // if nil, this numRange is just a setNumber in "first" and first.star will be false
|
||||
}
|
||||
|
||||
// interpretStar returns a numset that interprets stars in a numset, returning a new
|
||||
// numset without stars with increasing first/last.
|
||||
func (s numSet) interpretStar(uids []store.UID) numSet {
|
||||
var ns numSet
|
||||
for _, r := range s.ranges {
|
||||
first := r.first.number
|
||||
if r.first.star {
|
||||
if len(uids) == 0 {
|
||||
continue
|
||||
}
|
||||
first = uint32(uids[0])
|
||||
}
|
||||
last := first
|
||||
if r.last != nil {
|
||||
last = r.last.number
|
||||
if r.last.star {
|
||||
if len(uids) == 0 {
|
||||
continue
|
||||
}
|
||||
last = uint32(uids[len(uids)-1])
|
||||
if first > last {
|
||||
first = last
|
||||
}
|
||||
} else if r.first.star && last < first {
|
||||
last = first
|
||||
}
|
||||
}
|
||||
if first > last {
|
||||
first, last = last, first
|
||||
}
|
||||
nr := numRange{first: setNumber{number: first}}
|
||||
if first != last {
|
||||
nr.last = &setNumber{number: last}
|
||||
}
|
||||
ns.ranges = append(ns.ranges, nr)
|
||||
}
|
||||
return ns
|
||||
}
|
||||
|
||||
// whether numSet only has numbers (no star/search), and is strictly increasing.
|
||||
func (s *numSet) isBasicIncreasing() bool {
|
||||
if s.searchResult {
|
||||
return false
|
||||
}
|
||||
var last uint32
|
||||
for _, r := range s.ranges {
|
||||
if r.first.star || r.first.number <= last || r.last != nil && (r.last.star || r.last.number < r.first.number) {
|
||||
return false
|
||||
}
|
||||
last = r.first.number
|
||||
if r.last != nil {
|
||||
last = r.last.number
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
type numIter struct {
|
||||
s numSet
|
||||
i int
|
||||
r *rangeIter
|
||||
}
|
||||
|
||||
// newIter must only be called on a numSet that is basic (no star/search) and ascending.
|
||||
func (s numSet) newIter() *numIter {
|
||||
return &numIter{s: s, i: 0, r: s.ranges[0].newIter()}
|
||||
}
|
||||
|
||||
func (i *numIter) Next() (uint32, bool) {
|
||||
if v, ok := i.r.Next(); ok {
|
||||
return v, ok
|
||||
}
|
||||
i.i++
|
||||
if i.i >= len(i.s.ranges) {
|
||||
return 0, false
|
||||
}
|
||||
i.r = i.s.ranges[i.i].newIter()
|
||||
return i.r.Next()
|
||||
}
|
||||
|
||||
type rangeIter struct {
|
||||
r numRange
|
||||
o int
|
||||
}
|
||||
|
||||
// newIter must only be called on a range in a numSet that is basic (no star/search) and ascending.
|
||||
func (r numRange) newIter() *rangeIter {
|
||||
return &rangeIter{r: r, o: 0}
|
||||
}
|
||||
|
||||
func (r *rangeIter) Next() (uint32, bool) {
|
||||
if r.o == 0 {
|
||||
r.o++
|
||||
return r.r.first.number, true
|
||||
}
|
||||
if r.r.last == nil || r.r.first.number+uint32(r.o) > r.r.last.number {
|
||||
return 0, false
|
||||
}
|
||||
v := r.r.first.number + uint32(r.o)
|
||||
r.o++
|
||||
return v, true
|
||||
}
|
||||
|
||||
// append adds a new number to the set, extending a range, or starting a new one (possibly the first).
|
||||
// can only be used on basic numsets, without star/searchResult.
|
||||
func (s *numSet) append(v uint32) {
|
||||
if len(s.ranges) == 0 {
|
||||
s.ranges = []numRange{{first: setNumber{number: v}}}
|
||||
return
|
||||
}
|
||||
ri := len(s.ranges) - 1
|
||||
r := s.ranges[ri]
|
||||
if v == r.first.number+1 && r.last == nil {
|
||||
s.ranges[ri].last = &setNumber{number: v}
|
||||
} else if r.last != nil && v == r.last.number+1 {
|
||||
r.last.number++
|
||||
} else {
|
||||
s.ranges = append(s.ranges, numRange{first: setNumber{number: v}})
|
||||
}
|
||||
}
|
||||
|
||||
type partial struct {
|
||||
offset uint32
|
||||
count uint32
|
||||
@ -156,17 +318,18 @@ type fetchAtt struct {
|
||||
|
||||
type searchKey struct {
|
||||
// Only one of searchKeys, seqSet and op can be non-nil/non-empty.
|
||||
searchKeys []searchKey // In case of nested/multiple keys. Also for the top-level command.
|
||||
seqSet *numSet // In case of bare sequence set. For op UID, field uidSet contains the parameter.
|
||||
op string // Determines which of the fields below are set.
|
||||
headerField string
|
||||
astring string
|
||||
date time.Time
|
||||
atom string
|
||||
number int64
|
||||
searchKey *searchKey
|
||||
searchKey2 *searchKey
|
||||
uidSet numSet
|
||||
searchKeys []searchKey // In case of nested/multiple keys. Also for the top-level command.
|
||||
seqSet *numSet // In case of bare sequence set. For op UID, field uidSet contains the parameter.
|
||||
op string // Determines which of the fields below are set.
|
||||
headerField string
|
||||
astring string
|
||||
date time.Time
|
||||
atom string
|
||||
number int64
|
||||
searchKey *searchKey
|
||||
searchKey2 *searchKey
|
||||
uidSet numSet
|
||||
clientModseq *int64
|
||||
}
|
||||
|
||||
func compactUIDSet(l []store.UID) (r numSet) {
|
||||
|
Reference in New Issue
Block a user