Closed webroru closed 4 years ago
@webroru
Update can be one of multiple types (ExpungeUpdate
, MessageUpdate
,MailboxUpdate
...) . You can use type assertion to find out which one. MailboxUpdate
is the one I look for when expecting new mail, although for me it subsequently hangs when trying to fetch messages after receiving the update.
switch v := update.(type) {
case *client.MailboxUpdate:
// Do something.
default:
log.Info("skipping update")
}
I had to perform a type assertion on the interface. So after switching on the underlying type I've asserted to start working with the underlying types. Something like,
// Listen for updates
for {
select {
case update := <-updates:
log.Printf("New update: %v", update)//This will print the pointer address
log.Println(reflect.TypeOf(update).String())//Tell you what type you have
switch update.(type){
case *client.MessageUpdate:
log.Println("Found message update type")
msg, _ := update.(*client.MessageUpdate)//This prints what you want here
log.Println(msg.Message)
case *client.MailboxUpdate:
log.Println("Found mailbox update type")
mbx, _ := update.(*client.MailboxUpdate)
log.Println(mbx.Mailbox)
//etc....
}
// if update
case err := <-done:
if err != nil {
log.Fatal(err)
}
log.Println("Not idling anymore")
return
}
}
It's great to be able to accept any type but I think it is safe to assume that most people want to work with the underlying type in these situations. It would have saved me a little time surfing through godoc if this had been part of the readme, maybe a pull should be added for the readme.
You don't need a type assertion. You can use @taisph's code snippet and v
already has the right type. This is "just" Go syntax, see e.g. the last part of https://gobyexample.com/switch. I'm willing to merge a PR to add this to example_test.go
(maybe ExampleClient_IdleWithFallback
?).
Also note that it's never a good idea to do things like
msg, _ := update.(*client.MessageUpdate)
because it'll ignore errors. Instead, do
msg := update.(*client.MessageUpdate)
so that it panics if the assertion doesn't hold.
I completely overlooked the v :=
assignment and honestly I'm just getting back into Go coding after a couple years off and think I was bringing some bad python habits back with me.
Updated my code and works as expected.
Also noted on the panic, but I don't need to worry anymore in this case as the assertion is not necessary.
I am however now getting a similar issue as @taisph, I suspect I just have a blocking channel in my code somewhere but thought I would confirm the possible issue. It works if I call another routine that reconnects and creates a new client. I think I need some further analysis though before claiming it's an issue.
I do think a PR with a couple of these cases to illustrate the underlying types would be helpful for folks and will push an update when I am able to get a chance.
Oh, and thanks for the libraries :)
FWIW. I ended up with something like the code below. I only need the MailboxUpdate
to get notified about new mail. It keeps listening for updates until an MailboxUpdate
arrives and then exits IDLE freeing the client for fetching messages after which it will reenter the wait function.
func (app *App) Run() {
// ...
for {
upd, err := app.waitForMailboxUpdate(chanupd)
if err != nil {
panic(err)
}
app.fetchMessages(upd.Mailbox)
}
}
func (app *App) waitForMailboxUpdate(chupd chan client.Update) (*client.MailboxUpdate, error) {
done := make(chan error, 1)
stop := make(chan struct{})
go func() {
done <- app.imapIdleClient.IdleWithFallback(stop, 5*time.Minute)
}()
var mboxupd *client.MailboxUpdate
waitLoop:
for {
select {
case upd := <-chupd:
if mboxupd = asMailboxUpdate(upd); mboxupd != nil {
break waitLoop
}
case err := <-done:
if err != nil {
return nil, fmt.Errorf("error while idling: %s", err.Error())
}
return nil, nil
}
}
close(stop)
<-done
return mboxupd, nil
}
func asMailboxUpdate(upd client.Update) *client.MailboxUpdate {
if v, ok := upd.(*client.MailboxUpdate); ok {
return v
}
return nil
}
How would one stop idling in order to fetch a message and then start again? I have been trying to implement that on top of very similar code to this but I keep getting short read errors whenever I either send something into the stop channel or close the stop channel.
Actually when I send something into the stop channel I can fetch the message but if I start idling again it fails with short read error. When I close the stop channel I get an error even when fetching.
func (ec *EClient) ListenForEmails() {
// Listen for updates
for {
select {
case update := <-ec.IdleUpdates:
switch update.(type) {
case *client.MailboxUpdate:
// ec.IdleStop <- struct {}{} // Fetches fine, short read error when trying to fetch again
// close(ec.IdleStop) // short read error upon trying to fetch as well as when trying to re-idle
realUpdate := update.(*client.MailboxUpdate)
uid := realUpdate.Mailbox.Messages
messages := make(chan *imap.Message, 10)
seq := new(imap.SeqSet)
seq.AddRange(uid, uid)
err := ec.Client.Fetch(seq, []imap.FetchItem{imap.FetchBody + "[]"}, messages)
if err == nil {
result := parseMails(messages)
for _, email := range result {
log.Println(email.Date, "\t", email.From, "\t", email.Subject, "\t", email.HTMLBody)
}
} else {
// Deal with error
log.Println("Fetch error: ", err)
}
default:
log.Println("New update: \n", spew.Sdump(update))
}
case err := <-ec.IdleErrors:
if err != nil {
log.Println(err)
}
log.Println("Not idling anymore")
// re-idle
ec.InitIdle()
}
}
}
InitIdle looks like this:
func (ec *EClient) InitIdle() {
// Create IDLE client
idleClient := idle.NewClient(ec.Client)
// Create a channel to receive mailbox updates
updates := make(chan client.Update)
ec.Client.Updates = updates
// Start idling
idleErr := make(chan error, 1)
idleStop:= make(chan struct{}, 1)
go func() {
idleErr <- idleClient.IdleWithFallback(idleStop, 0)
}()
ec.IdleErrors = idleErr
ec.IdleUpdates = updates
ec.IdleStop = idleStop
}
Nevermind, I had to reconnect all together after closing the stop channel and then re-idle after fetching the message.
Same issue hitting here, after closing the stop channel, the Idle() method keeps waiting infinitely on return <-done
, thus blocking all calls on the IMAP client, which waits for the response of.. something.
I have the same problem as @xplodwild . Any updates on this?
Same issue hitting here, after closing the stop channel, the Idle() method keeps waiting infinitely on return <-done, thus blocking all calls on the IMAP client, which waits for the response of.. something.
Can you open a separate issue for this?
Closing this one since the original question was answered.
FWIW. I ended up with something like the code below. I only need the
MailboxUpdate
to get notified about new mail. It keeps listening for updates until anMailboxUpdate
arrives and then exits IDLE freeing the client for fetching messages after which it will reenter the wait function.func (app *App) Run() { // ... for { upd, err := app.waitForMailboxUpdate(chanupd) if err != nil { panic(err) } app.fetchMessages(upd.Mailbox) } } func (app *App) waitForMailboxUpdate(chupd chan client.Update) (*client.MailboxUpdate, error) { done := make(chan error, 1) stop := make(chan struct{}) go func() { done <- app.imapIdleClient.IdleWithFallback(stop, 5*time.Minute) }() var mboxupd *client.MailboxUpdate waitLoop: for { select { case upd := <-chupd: if mboxupd = asMailboxUpdate(upd); mboxupd != nil { break waitLoop } case err := <-done: if err != nil { return nil, fmt.Errorf("error while idling: %s", err.Error()) } return nil, nil } } close(stop) <-done return mboxupd, nil } func asMailboxUpdate(upd client.Update) *client.MailboxUpdate { if v, ok := upd.(*client.MailboxUpdate); ok { return v } return nil }
but i do not work. I can not revice the email. I use QQMail.
package main
import (
"fmt"
"log"
"time"
"github.com/emersion/go-imap"
idle "github.com/emersion/go-imap-idle"
"github.com/emersion/go-imap/client"
)
const (
addr = "imap.qq.com:993"
username = "xxx@qq.com"
password = "xxx"
)
type App struct {
imapIdleClient *idle.Client
}
func (app *App) Run() {
if ok, err := app.imapIdleClient.SupportIdle(); err == nil && ok {
log.Println("支持imap idle")
} else {
log.Println("不支持imap idle")
}
chanupd := make(chan client.Update)
// ...
for {
upd, err := app.waitForMailboxUpdate(chanupd)
if err != nil {
panic(err)
}
app.fetchMessages(upd.Mailbox)
}
}
func (app *App) waitForMailboxUpdate(chupd chan client.Update) (*client.MailboxUpdate, error) {
done := make(chan error, 1)
stop := make(chan struct{})
go func() {
done <- app.imapIdleClient.IdleWithFallback(stop, 5*time.Minute)
}()
var mboxupd *client.MailboxUpdate
waitLoop:
for {
select {
case upd := <-chupd:
if mboxupd = asMailboxUpdate(upd); mboxupd != nil {
break waitLoop
}
case err := <-done:
if err != nil {
return nil, fmt.Errorf("error while idling: %s", err.Error())
}
return nil, nil
}
}
close(stop)
<-done
return mboxupd, nil
}
func asMailboxUpdate(upd client.Update) *client.MailboxUpdate {
if v, ok := upd.(*client.MailboxUpdate); ok {
return v
}
return nil
}
func (this *App) fetchMessages(mailbox *imap.MailboxStatus) {
log.Println("fetchMessages", mailbox)
}
func main() {
c, err := client.DialTLS(addr, nil)
if err != nil {
log.Fatal(err)
}
log.Println("Connected")
// Don't forget to logout
defer c.Logout()
// Login
if err := c.Login(username, password); err != nil {
log.Fatal(err)
}
log.Println("Logged in")
// Select INBOX
mbox, err := c.Select("INBOX", false)
if err != nil {
log.Fatal(err)
}
log.Println("Flags for INBOX:", mbox.Flags)
idleClient := idle.NewClient(c)
app := &App{idleClient}
app.Run()
}
Hello,
I ran code from example, sent mail to myself and got message in console: "New update: &{0xc000114000}". What is this uid? Is it possible to get a message by this number?