Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Replace various update channels with one unique channel #137

Merged
merged 5 commits into from
Aug 16, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
88 changes: 39 additions & 49 deletions client/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,26 @@ var errClosed = fmt.Errorf("imap: connection closed")
// errUnregisterHandler is returned by a response handler to unregister itself.
var errUnregisterHandler = fmt.Errorf("imap: unregister handler")

// StatusUpdate is delivered when a status update is received.
type StatusUpdate struct {
Status *imap.StatusResp
}

// MailboxUpdate is delivered when a mailbox status changes.
type MailboxUpdate struct {
Mailbox *imap.MailboxStatus
}

// ExpungeUpdate is delivered when a message is deleted.
type ExpungeUpdate struct {
SeqNum uint32
}

// MessageUpdate is delivered when a message attribute changes.
type MessageUpdate struct {
Message *imap.Message
}

// Client is an IMAP client.
type Client struct {
conn *imap.Conn
Expand All @@ -43,20 +63,12 @@ type Client struct {
// access.
locker sync.Mutex

// A channel where info messages from the server will be sent.
Infos chan *imap.StatusResp
// A channel where warning messages from the server will be sent.
Warnings chan *imap.StatusResp
// A channel where error messages from the server will be sent.
Errors chan *imap.StatusResp
// A channel where bye messages from the server will be sent.
Byes chan *imap.StatusResp
// A channel where mailbox updates from the server will be sent.
MailboxUpdates chan *imap.MailboxStatus
// A channel where deleted message IDs will be sent.
Expunges chan uint32
// A channel where messages updates from the server will be sent.
MessageUpdates chan *imap.Message
// A channel to which unilateral updates from the server will be sent. An
// update can be one of: *StatusUpdate, *MailboxUpdate, *MessageUpdate,
// *ExpungeUpdate. Note that blocking this channel blocks the whole client,
// so it's recommended to use a separate goroutine and a buffered channel to
// prevent deadlocks.
Updates chan<- interface{}

// ErrorLog specifies an optional logger for errors accepting
// connections and unexpected behavior from handlers.
Expand Down Expand Up @@ -291,23 +303,9 @@ func (c *Client) handleUnilateral() {
}

switch resp.Type {
case imap.StatusOk:
if c.Infos != nil {
go func() {
c.Infos <- resp
}()
}
case imap.StatusNo:
if c.Warnings != nil {
go func() {
c.Warnings <- resp
}()
}
case imap.StatusBad:
if c.Errors != nil {
go func() {
c.Errors <- resp
}()
case imap.StatusOk, imap.StatusNo, imap.StatusBad:
if c.Updates != nil {
c.Updates <- &StatusUpdate{resp}
}
case imap.StatusBye:
c.locker.Lock()
Expand All @@ -317,10 +315,8 @@ func (c *Client) handleUnilateral() {

c.conn.Close()

if c.Byes != nil {
go func() {
c.Byes <- resp
}()
if c.Updates != nil {
c.Updates <- &StatusUpdate{resp}
}
default:
return responses.ErrUnhandled
Expand Down Expand Up @@ -349,10 +345,8 @@ func (c *Client) handleUnilateral() {
c.mailbox.ItemsLocker.Unlock()
}

if c.MailboxUpdates != nil {
go func() {
c.MailboxUpdates <- c.Mailbox()
}()
if c.Updates != nil {
c.Updates <- &MailboxUpdate{c.Mailbox()}
}
case "RECENT":
if c.Mailbox() == nil {
Expand All @@ -369,16 +363,14 @@ func (c *Client) handleUnilateral() {
c.mailbox.ItemsLocker.Unlock()
}

if c.MailboxUpdates != nil {
go func() {
c.MailboxUpdates <- c.Mailbox()
}()
if c.Updates != nil {
c.Updates <- &MailboxUpdate{c.Mailbox()}
}
case "EXPUNGE":
seqNum, _ := imap.ParseNumber(fields[0])

if c.Expunges != nil {
c.Expunges <- seqNum
if c.Updates != nil {
c.Updates <- &ExpungeUpdate{seqNum}
}
case "FETCH":
seqNum, _ := imap.ParseNumber(fields[0])
Expand All @@ -389,10 +381,8 @@ func (c *Client) handleUnilateral() {
break
}

if c.MessageUpdates != nil {
go func() {
c.MessageUpdates <- msg
}()
if c.Updates != nil {
c.Updates <- &MessageUpdate{msg}
}
default:
return responses.ErrUnhandled
Expand Down
42 changes: 16 additions & 26 deletions client/client_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -133,51 +133,41 @@ func TestClient_unilateral(t *testing.T) {

setClientState(c, imap.SelectedState, imap.NewMailboxStatus("INBOX", nil))

statuses := make(chan *imap.MailboxStatus, 1)
c.MailboxUpdates = statuses
expunges := make(chan uint32, 1)
c.Expunges = expunges
messages := make(chan *imap.Message, 1)
c.MessageUpdates = messages
infos := make(chan *imap.StatusResp, 1)
c.Infos = infos
warns := make(chan *imap.StatusResp, 1)
c.Warnings = warns
errors := make(chan *imap.StatusResp, 1)
c.Errors = errors
updates := make(chan interface{}, 1)
c.Updates = updates

s.WriteString("* 42 EXISTS\r\n")
if status := <-statuses; status.Messages != 42 {
t.Errorf("Invalid messages count: expected %v but got %v", 42, status.Messages)
if update, ok := (<-updates).(*MailboxUpdate); !ok || update.Mailbox.Messages != 42 {
t.Errorf("Invalid messages count: expected %v but got %v", 42, update.Mailbox.Messages)
}

s.WriteString("* 587 RECENT\r\n")
if status := <-statuses; status.Recent != 587 {
t.Errorf("Invalid recent count: expected %v but got %v", 587, status.Recent)
if update, ok := (<-updates).(* MailboxUpdate); !ok || update.Mailbox.Recent != 587 {
t.Errorf("Invalid recent count: expected %v but got %v", 587, update.Mailbox.Recent)
}

s.WriteString("* 65535 EXPUNGE\r\n")
if seqNum := <-expunges; seqNum != 65535 {
t.Errorf("Invalid expunged sequence number: expected %v but got %v", 65535, seqNum)
if update, ok := (<-updates).(*ExpungeUpdate); !ok || update.SeqNum != 65535 {
t.Errorf("Invalid expunged sequence number: expected %v but got %v", 65535, update.SeqNum)
}

s.WriteString("* 431 FETCH (FLAGS (\\Seen))\r\n")
if msg := <-messages; msg.SeqNum != 431 {
t.Errorf("Invalid expunged sequence number: expected %v but got %v", 431, msg.SeqNum)
if update, ok := (<-updates).(*MessageUpdate); !ok || update.Message.SeqNum != 431 {
t.Errorf("Invalid expunged sequence number: expected %v but got %v", 431, update.Message.SeqNum)
}

s.WriteString("* OK Reticulating splines...\r\n")
if status := <-infos; status.Info != "Reticulating splines..." {
t.Errorf("Invalid info: got %v", status.Info)
if update, ok := (<-updates).(*StatusUpdate); !ok || update.Status.Info != "Reticulating splines..." {
t.Errorf("Invalid info: got %v", update.Status.Info)
}

s.WriteString("* NO Kansai band competition is in 30 seconds !\r\n")
if status := <-warns; status.Info != "Kansai band competition is in 30 seconds !" {
t.Errorf("Invalid warning: got %v", status.Info)
if update, ok := (<-updates).(*StatusUpdate); !ok || update.Status.Info != "Kansai band competition is in 30 seconds !" {
t.Errorf("Invalid warning: got %v", update.Status.Info)
}

s.WriteString("* BAD Battery level too low, shutting down.\r\n")
if status := <-errors; status.Info != "Battery level too low, shutting down." {
t.Errorf("Invalid error: got %v", status.Info)
if update, ok := (<-updates).(*StatusUpdate); !ok || update.Status.Info != "Battery level too low, shutting down." {
t.Errorf("Invalid error: got %v", update.Status.Info)
}
}