forked from emersion/go-imap
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathsearch.go
142 lines (119 loc) · 3.39 KB
/
search.go
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
129
130
131
132
133
134
135
136
137
138
139
140
141
142
package imap
import (
"reflect"
"time"
)
// SearchOptions contains options for the SEARCH command.
type SearchOptions struct {
// Requires IMAP4rev2 or ESEARCH
ReturnMin bool
ReturnMax bool
ReturnAll bool
ReturnCount bool
// Requires IMAP4rev2 or SEARCHRES
ReturnSave bool
}
// SearchCriteria is a criteria for the SEARCH command.
//
// When multiple fields are populated, the result is the intersection ("and"
// function) of all messages that match the fields.
type SearchCriteria struct {
SeqNum []SeqSet
UID []SeqSet
// Only the date is used, the time and timezone are ignored
Since time.Time
Before time.Time
SentSince time.Time
SentBefore time.Time
Header []SearchCriteriaHeaderField
Body []string
Text []string
Flag []Flag
NotFlag []Flag
Larger int64
Smaller int64
Not []SearchCriteria
Or [][2]SearchCriteria
}
// And intersects two search criteria.
func (criteria *SearchCriteria) And(other *SearchCriteria) {
criteria.SeqNum = append(criteria.SeqNum, other.SeqNum...)
criteria.UID = append(criteria.UID, other.UID...)
criteria.Since = intersectSince(criteria.Since, other.Since)
criteria.Before = intersectBefore(criteria.Before, other.Before)
criteria.SentSince = intersectSince(criteria.SentSince, other.SentSince)
criteria.SentBefore = intersectBefore(criteria.SentBefore, other.SentBefore)
criteria.Header = append(criteria.Header, other.Header...)
criteria.Body = append(criteria.Body, other.Body...)
criteria.Text = append(criteria.Text, other.Text...)
criteria.Flag = append(criteria.Flag, other.Flag...)
criteria.NotFlag = append(criteria.NotFlag, other.NotFlag...)
if criteria.Larger == 0 || other.Larger > criteria.Larger {
criteria.Larger = other.Larger
}
if criteria.Smaller == 0 || other.Smaller < criteria.Smaller {
criteria.Smaller = other.Smaller
}
criteria.Not = append(criteria.Not, other.Not...)
criteria.Or = append(criteria.Or, other.Or...)
}
func intersectSince(t1, t2 time.Time) time.Time {
switch {
case t1.IsZero():
return t2
case t2.IsZero():
return t1
case t1.After(t2):
return t1
default:
return t2
}
}
func intersectBefore(t1, t2 time.Time) time.Time {
switch {
case t1.IsZero():
return t2
case t2.IsZero():
return t1
case t1.Before(t2):
return t1
default:
return t2
}
}
type SearchCriteriaHeaderField struct {
Key, Value string
}
// SearchData is the data returned by a SEARCH command.
type SearchData struct {
All SeqSet
// requires IMAP4rev2 or ESEARCH
UID bool
Min uint32
Max uint32
Count uint32
}
// AllNums returns All as a slice of numbers.
func (data *SearchData) AllNums() []uint32 {
// Note: a dynamic sequence set would be a server bug
nums, _ := data.All.Nums()
return nums
}
// searchRes is a special empty SeqSet which can be used as a marker. It has
// a non-zero cap so that its data pointer is non-nil and can be compared.
var (
searchRes = make(SeqSet, 0, 1)
searchResAddr = reflect.ValueOf(searchRes).Pointer()
)
// SearchRes returns a special marker which can be used instead of a SeqSet to
// reference the last SEARCH result. On the wire, it's encoded as '$'.
//
// It requires IMAP4rev2 or the SEARCHRES extension.
func SearchRes() SeqSet {
return searchRes
}
// IsSearchRes checks whether a sequence set is a reference to the last SEARCH
// result. See SearchRes.
func IsSearchRes(seqSet SeqSet) bool {
return reflect.ValueOf(seqSet).Pointer() == searchResAddr
}