Skip to content

Commit 058b429

Browse files
committed
Add SeqSet and UIDSet
Closes: #526
1 parent ae08e17 commit 058b429

31 files changed

+843
-606
lines changed

copy.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,6 @@ package imap
44
type CopyData struct {
55
// requires UIDPLUS or IMAP4rev2
66
UIDValidity uint32
7-
SourceUIDs NumSet
8-
DestUIDs NumSet
7+
SourceUIDs UIDSet
8+
DestUIDs UIDSet
99
}

imapclient/client.go

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -975,11 +975,14 @@ func (c *Client) Unsubscribe(mailbox string) *Command {
975975
return cmd
976976
}
977977

978-
func uidCmdName(name string, uid bool) string {
979-
if uid {
980-
return "UID " + name
981-
} else {
978+
func uidCmdName(name string, kind imapwire.NumKind) string {
979+
switch kind {
980+
case imapwire.NumKindSeq:
982981
return name
982+
case imapwire.NumKindUID:
983+
return "UID " + name
984+
default:
985+
panic("imapclient: invalid imapwire.NumKind")
983986
}
984987
}
985988

imapclient/client_test.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -119,7 +119,7 @@ func TestFetch_invalid(t *testing.T) {
119119
defer client.Close()
120120
defer server.Close()
121121

122-
_, err := client.UIDFetch(imap.NumSetNum(), nil).Collect()
122+
_, err := client.Fetch(imap.UIDSet(nil), nil).Collect()
123123
if err == nil {
124124
t.Fatalf("UIDFetch().Collect() = %v", err)
125125
}
@@ -130,7 +130,7 @@ func TestFetch_closeUnreadBody(t *testing.T) {
130130
defer client.Close()
131131
defer server.Close()
132132

133-
fetchCmd := client.UIDFetch(imap.NumSetNum(1), &imap.FetchOptions{
133+
fetchCmd := client.Fetch(imap.SeqSetNum(1), &imap.FetchOptions{
134134
BodySection: []*imap.FetchItemBodySection{
135135
{
136136
Specifier: imap.PartSpecifierNone,

imapclient/copy.go

Lines changed: 6 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -7,26 +7,15 @@ import (
77
"github.com/emersion/go-imap/v2/internal/imapwire"
88
)
99

10-
func (c *Client) copy(uid bool, seqSet imap.NumSet, mailbox string) *CopyCommand {
10+
// Copy sends a COPY command.
11+
func (c *Client) Copy(numSet imap.NumSet, mailbox string) *CopyCommand {
1112
cmd := &CopyCommand{}
12-
enc := c.beginCommand(uidCmdName("COPY", uid), cmd)
13-
enc.SP().NumSet(seqSet).SP().Mailbox(mailbox)
13+
enc := c.beginCommand(uidCmdName("COPY", imapwire.NumSetKind(numSet)), cmd)
14+
enc.SP().NumSet(numSet).SP().Mailbox(mailbox)
1415
enc.end()
1516
return cmd
1617
}
1718

18-
// Copy sends a COPY command.
19-
func (c *Client) Copy(seqSet imap.NumSet, mailbox string) *CopyCommand {
20-
return c.copy(false, seqSet, mailbox)
21-
}
22-
23-
// UIDCopy sends a UID COPY command.
24-
//
25-
// See Copy.
26-
func (c *Client) UIDCopy(seqSet imap.NumSet, mailbox string) *CopyCommand {
27-
return c.copy(true, seqSet, mailbox)
28-
}
29-
3019
// CopyCommand is a COPY command.
3120
type CopyCommand struct {
3221
cmd
@@ -37,8 +26,8 @@ func (cmd *CopyCommand) Wait() (*imap.CopyData, error) {
3726
return &cmd.data, cmd.cmd.Wait()
3827
}
3928

40-
func readRespCodeCopyUID(dec *imapwire.Decoder) (uidValidity uint32, srcUIDs, dstUIDs imap.NumSet, err error) {
41-
if !dec.ExpectNumber(&uidValidity) || !dec.ExpectSP() || !dec.ExpectNumSet(&srcUIDs) || !dec.ExpectSP() || !dec.ExpectNumSet(&dstUIDs) {
29+
func readRespCodeCopyUID(dec *imapwire.Decoder) (uidValidity uint32, srcUIDs, dstUIDs imap.UIDSet, err error) {
30+
if !dec.ExpectNumber(&uidValidity) || !dec.ExpectSP() || !dec.ExpectUIDSet(&srcUIDs) || !dec.ExpectSP() || !dec.ExpectUIDSet(&dstUIDs) {
4231
return 0, nil, nil, dec.Err()
4332
}
4433
if srcUIDs.Dynamic() || dstUIDs.Dynamic() {

imapclient/example_test.go

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ func ExampleClient() {
3939
log.Printf("INBOX contains %v messages", selectedMbox.NumMessages)
4040

4141
if selectedMbox.NumMessages > 0 {
42-
seqSet := imap.NumSetNum(1)
42+
seqSet := imap.SeqSetNum(1)
4343
fetchOptions := &imap.FetchOptions{Envelope: true}
4444
messages, err := c.Fetch(seqSet, fetchOptions).Collect()
4545
if err != nil {
@@ -56,13 +56,13 @@ func ExampleClient() {
5656
func ExampleClient_pipelining() {
5757
var c *imapclient.Client
5858

59-
uid := uint32(42)
59+
uid := imap.UID(42)
6060
fetchOptions := &imap.FetchOptions{Envelope: true}
6161

6262
// Login, select and fetch a message in a single roundtrip
6363
loginCmd := c.Login("root", "root")
6464
selectCmd := c.Select("INBOX", nil)
65-
fetchCmd := c.UIDFetch(imap.NumSetNum(uid), fetchOptions)
65+
fetchCmd := c.Fetch(imap.UIDSetNum(uid), fetchOptions)
6666

6767
if err := loginCmd.Wait(); err != nil {
6868
log.Fatalf("failed to login: %v", err)
@@ -130,7 +130,7 @@ func ExampleClient_List_stream() {
130130
func ExampleClient_Store() {
131131
var c *imapclient.Client
132132

133-
seqSet := imap.NumSetNum(1)
133+
seqSet := imap.SeqSetNum(1)
134134
storeFlags := imap.StoreFlags{
135135
Op: imap.StoreFlagsAdd,
136136
Flags: []imap.Flag{imap.FlagFlagged},
@@ -144,7 +144,7 @@ func ExampleClient_Store() {
144144
func ExampleClient_Fetch() {
145145
var c *imapclient.Client
146146

147-
seqSet := imap.NumSetNum(1)
147+
seqSet := imap.SeqSetNum(1)
148148
fetchOptions := &imap.FetchOptions{
149149
Flags: true,
150150
Envelope: true,
@@ -172,7 +172,7 @@ func ExampleClient_Fetch() {
172172
func ExampleClient_Fetch_streamBody() {
173173
var c *imapclient.Client
174174

175-
seqSet := imap.NumSetNum(1)
175+
seqSet := imap.SeqSetNum(1)
176176
fetchOptions := &imap.FetchOptions{
177177
UID: true,
178178
BodySection: []*imap.FetchItemBodySection{{}},
@@ -214,7 +214,7 @@ func ExampleClient_Fetch_parseBody() {
214214
var c *imapclient.Client
215215

216216
// Send a FETCH command to fetch the message body
217-
seqSet := imap.NumSetNum(1)
217+
seqSet := imap.SeqSetNum(1)
218218
fetchOptions := &imap.FetchOptions{
219219
BodySection: []*imap.FetchItemBodySection{{}},
220220
}
@@ -302,7 +302,7 @@ func ExampleClient_Search() {
302302
if err != nil {
303303
log.Fatalf("UID SEARCH command failed: %v", err)
304304
}
305-
log.Fatalf("UIDs matching the search criteria: %v", data.AllNums())
305+
log.Fatalf("UIDs matching the search criteria: %v", data.AllUIDs())
306306
}
307307

308308
func ExampleClient_Idle() {

imapclient/expunge.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ func (c *Client) Expunge() *ExpungeCommand {
1414
// UIDExpunge sends a UID EXPUNGE command.
1515
//
1616
// This command requires support for IMAP4rev2 or the UIDPLUS extension.
17-
func (c *Client) UIDExpunge(uids imap.NumSet) *ExpungeCommand {
17+
func (c *Client) UIDExpunge(uids imap.UIDSet) *ExpungeCommand {
1818
cmd := &ExpungeCommand{seqNums: make(chan uint32, 128)}
1919
enc := c.beginCommand("UID EXPUNGE", cmd)
2020
enc.SP().NumSet(uids)

imapclient/fetch.go

Lines changed: 49 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -12,49 +12,39 @@ import (
1212
"github.com/emersion/go-imap/v2/internal/imapwire"
1313
)
1414

15-
func (c *Client) fetch(uid bool, seqSet imap.NumSet, options *imap.FetchOptions) *FetchCommand {
15+
// Fetch sends a FETCH command.
16+
//
17+
// The caller must fully consume the FetchCommand. A simple way to do so is to
18+
// defer a call to FetchCommand.Close.
19+
//
20+
// A nil options pointer is equivalent to a zero options value.
21+
func (c *Client) Fetch(numSet imap.NumSet, options *imap.FetchOptions) *FetchCommand {
1622
if options == nil {
1723
options = new(imap.FetchOptions)
1824
}
1925

26+
numKind := imapwire.NumSetKind(numSet)
27+
2028
cmd := &FetchCommand{
21-
uid: uid,
22-
seqSet: seqSet,
29+
numSet: numSet,
2330
msgs: make(chan *FetchMessageData, 128),
2431
}
25-
enc := c.beginCommand(uidCmdName("FETCH", uid), cmd)
26-
enc.SP().NumSet(seqSet).SP()
27-
writeFetchItems(enc.Encoder, uid, options)
32+
enc := c.beginCommand(uidCmdName("FETCH", numKind), cmd)
33+
enc.SP().NumSet(numSet).SP()
34+
writeFetchItems(enc.Encoder, numKind, options)
2835
if options.ChangedSince != 0 {
2936
enc.SP().Special('(').Atom("CHANGEDSINCE").SP().ModSeq(options.ChangedSince).Special(')')
3037
}
3138
enc.end()
3239
return cmd
3340
}
3441

35-
// Fetch sends a FETCH command.
36-
//
37-
// The caller must fully consume the FetchCommand. A simple way to do so is to
38-
// defer a call to FetchCommand.Close.
39-
//
40-
// A nil options pointer is equivalent to a zero options value.
41-
func (c *Client) Fetch(seqSet imap.NumSet, options *imap.FetchOptions) *FetchCommand {
42-
return c.fetch(false, seqSet, options)
43-
}
44-
45-
// UIDFetch sends a UID FETCH command.
46-
//
47-
// See Fetch.
48-
func (c *Client) UIDFetch(seqSet imap.NumSet, options *imap.FetchOptions) *FetchCommand {
49-
return c.fetch(true, seqSet, options)
50-
}
51-
52-
func writeFetchItems(enc *imapwire.Encoder, uid bool, options *imap.FetchOptions) {
42+
func writeFetchItems(enc *imapwire.Encoder, numKind imapwire.NumKind, options *imap.FetchOptions) {
5343
listEnc := enc.BeginList()
5444

5545
// Ensure we request UID as the first data item for UID FETCH, to be safer.
5646
// We want to get it before any literal.
57-
if options.UID || uid {
47+
if options.UID || numKind == imapwire.NumKindUID {
5848
listEnc.Item().Atom("UID")
5949
}
6050

@@ -159,14 +149,42 @@ func writeSectionPartial(enc *imapwire.Encoder, partial *imap.SectionPartial) {
159149
type FetchCommand struct {
160150
cmd
161151

162-
uid bool
163-
seqSet imap.NumSet
164-
recvNumSet imap.NumSet
152+
numSet imap.NumSet
153+
recvSeqSet imap.SeqSet
154+
recvUIDSet imap.UIDSet
165155

166156
msgs chan *FetchMessageData
167157
prev *FetchMessageData
168158
}
169159

160+
func (cmd *FetchCommand) recvSeqNum(seqNum uint32) bool {
161+
set, ok := cmd.numSet.(imap.SeqSet)
162+
if !ok || !set.Contains(seqNum) {
163+
return false
164+
}
165+
166+
if cmd.recvSeqSet.Contains(seqNum) {
167+
return false
168+
}
169+
170+
cmd.recvSeqSet.AddNum(seqNum)
171+
return true
172+
}
173+
174+
func (cmd *FetchCommand) recvUID(uid imap.UID) bool {
175+
set, ok := cmd.numSet.(imap.UIDSet)
176+
if !ok || !set.Contains(uid) {
177+
return false
178+
}
179+
180+
if cmd.recvUIDSet.Contains(uid) {
181+
return false
182+
}
183+
184+
cmd.recvUIDSet.AddNum(uid)
185+
return true
186+
}
187+
170188
// Next advances to the next message.
171189
//
172190
// On success, the message is returned. On error or if there are no more
@@ -464,18 +482,11 @@ func (c *Client) handleFetch(seqNum uint32) error {
464482
}
465483

466484
// Skip if we haven't requested or already handled this message
467-
var num uint32
468-
if cmd.uid {
469-
num = uint32(uid)
485+
if _, ok := cmd.numSet.(imap.UIDSet); ok {
486+
return uid != 0 && cmd.recvUID(uid)
470487
} else {
471-
num = seqNum
472-
}
473-
if num == 0 || !cmd.seqSet.Contains(num) || cmd.recvNumSet.Contains(num) {
474-
return false
488+
return seqNum != 0 && cmd.recvSeqNum(seqNum)
475489
}
476-
cmd.recvNumSet.AddNum(num)
477-
478-
return true
479490
})
480491
if cmd != nil {
481492
cmd := cmd.(*FetchCommand)

imapclient/move.go

Lines changed: 11 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,14 @@ package imapclient
22

33
import (
44
"github.com/emersion/go-imap/v2"
5+
"github.com/emersion/go-imap/v2/internal/imapwire"
56
)
67

7-
func (c *Client) move(uid bool, seqSet imap.NumSet, mailbox string) *MoveCommand {
8+
// Move sends a MOVE command.
9+
//
10+
// If the server doesn't support IMAP4rev2 nor the MOVE extension, a fallback
11+
// with COPY + STORE + EXPUNGE commands is used.
12+
func (c *Client) Move(numSet imap.NumSet, mailbox string) *MoveCommand {
813
// If the server doesn't support MOVE, fallback to [UID] COPY,
914
// [UID] STORE +FLAGS.SILENT \Deleted and [UID] EXPUNGE
1015
cmdName := "MOVE"
@@ -13,18 +18,18 @@ func (c *Client) move(uid bool, seqSet imap.NumSet, mailbox string) *MoveCommand
1318
}
1419

1520
cmd := &MoveCommand{}
16-
enc := c.beginCommand(uidCmdName(cmdName, uid), cmd)
17-
enc.SP().NumSet(seqSet).SP().Mailbox(mailbox)
21+
enc := c.beginCommand(uidCmdName(cmdName, imapwire.NumSetKind(numSet)), cmd)
22+
enc.SP().NumSet(numSet).SP().Mailbox(mailbox)
1823
enc.end()
1924

2025
if cmdName == "COPY" {
21-
cmd.store = c.store(uid, seqSet, &imap.StoreFlags{
26+
cmd.store = c.Store(numSet, &imap.StoreFlags{
2227
Op: imap.StoreFlagsAdd,
2328
Silent: true,
2429
Flags: []imap.Flag{imap.FlagDeleted},
2530
}, nil)
26-
if uid && c.Caps().Has(imap.CapUIDPlus) {
27-
cmd.expunge = c.UIDExpunge(seqSet)
31+
if uidSet, ok := numSet.(imap.UIDSet); ok && c.Caps().Has(imap.CapUIDPlus) {
32+
cmd.expunge = c.UIDExpunge(uidSet)
2833
} else {
2934
cmd.expunge = c.Expunge()
3035
}
@@ -33,21 +38,6 @@ func (c *Client) move(uid bool, seqSet imap.NumSet, mailbox string) *MoveCommand
3338
return cmd
3439
}
3540

36-
// Move sends a MOVE command.
37-
//
38-
// If the server doesn't support IMAP4rev2 nor the MOVE extension, a fallback
39-
// with COPY + STORE + EXPUNGE commands is used.
40-
func (c *Client) Move(seqSet imap.NumSet, mailbox string) *MoveCommand {
41-
return c.move(false, seqSet, mailbox)
42-
}
43-
44-
// UIDMove sends a UID MOVE command.
45-
//
46-
// See Move.
47-
func (c *Client) UIDMove(seqSet imap.NumSet, mailbox string) *MoveCommand {
48-
return c.move(true, seqSet, mailbox)
49-
}
50-
5141
// MoveCommand is a MOVE command.
5242
type MoveCommand struct {
5343
cmd

0 commit comments

Comments
 (0)