func FetchMessages(c *imap.Client, uidSet *imap.SeqSet) (fetched []MsgData, err error) { cmd, errF := c.UIDFetch(uidSet, "RFC822") if errF != nil { err = errF return } for cmd.InProgress() { errC := c.Recv(-1) if errC != nil { return } for _, rsp := range cmd.Data { uid := imap.AsNumber(rsp.MessageInfo().Attrs["UID"]) mime := imap.AsBytes(rsp.MessageInfo().Attrs["RFC822"]) msg, errR := mail.ReadMessage(bytes.NewReader(mime)) if errR != nil { continue } if msg != nil { msgdata := GetMessage(msg, uid) fetched = append(fetched, msgdata) } } cmd.Data = nil } return }
func FetchAllUIDs(c *imap.Client) (uids []uint32, err error) { maxmessages := 150000 uids = make([]uint32, maxmessages) set, errS := imap.NewSeqSet("1:*") if errS != nil { err = errS return } cmd, errF := c.UIDFetch(set, "RFC822.SIZE") if errF != nil { err = errF return } messagenum := uint32(0) for cmd.InProgress() { errC := c.Recv(-1) if errC != nil { continue } for _, rsp := range cmd.Data { uid := imap.AsNumber(rsp.MessageInfo().Attrs["UID"]) uids[messagenum] = uid } cmd.Data = nil messagenum++ } uids = uids[:messagenum] return }
// getEmails will fetch the full bodies of all emails listed in the given command. func getEmails(client *imap.Client, cmd *imap.Command) ([]map[string]interface{}, error) { var emails []map[string]interface{} seq := new(imap.SeqSet) for _, rsp := range cmd.Data { for _, uid := range rsp.SearchResults() { seq.AddNum(uid) } } if seq.Empty() { return emails, nil } fCmd, err := imap.Wait(client.UIDFetch(seq, "INTERNALDATE", "BODY[]", "UID", "RFC822.HEADER")) if err != nil { return emails, err } var email map[string]interface{} for _, msgData := range fCmd.Data { msgFields := msgData.MessageInfo().Attrs email, err = newEmailMessage(msgFields) if err != nil { return emails, err } emails = append(emails, email) // mark message as read fSeq := new(imap.SeqSet) fSeq.AddNum(imap.AsNumber(msgFields["UID"])) _, err = imap.Wait(client.UIDStore(fSeq, "+FLAGS", "\\SEEN")) if err != nil { return emails, err } } return emails, nil }
func FetchMessages(c *imap.Client, uidSet *imap.SeqSet) (err error) { cmd, err := c.UIDFetch(uidSet, "RFC822") for cmd.InProgress() { c.Recv(-1) for _, rsp := range cmd.Data { uid := imap.AsNumber(rsp.MessageInfo().Attrs["UID"]) mime := imap.AsBytes(rsp.MessageInfo().Attrs["RFC822"]) if msg, _ := mail.ReadMessage(bytes.NewReader(mime)); msg != nil { PrintMessageAsJSON(msg, uid) } } cmd.Data = nil } return }
func (i *ImapSession) Append(folder string, extraCopies []string, msg io.Reader) error { c := i.client mbox := folder buf := new(bytes.Buffer) buf.ReadFrom(msg) now := time.Now() cmd, err := imap.Wait(c.Append(mbox, nil, &now, imap.NewLiteral(buf.Bytes()))) if err != nil { fmt.Println(err) return err } rsp, err := cmd.Result(imap.OK) if err != nil { fmt.Println(err) return err } uid := imap.AsNumber(rsp.Fields[len(rsp.Fields)-1]) set, _ := imap.NewSeqSet("") set.AddNum(uid) imapMust(imap.Wait(c.Select(mbox, false))) for _, mb := range extraCopies { if i.isGmail { imapMust(imap.Wait(c.UIDStore(set, "X-GM-LABELS", imap.NewFlagSet(mb)))) } else { imapMust(c.UIDCopy(set, mb)) } } return nil }
// Idle setup the processes to wait for notifications from the IMAP source connection. // If an EXISTS or EXPUNGE command comes across the pipe, the appropriate actions will be // taken to update the destinations. If the process decides the inboxes are out of sync, // it will pass a bool to the requestPurge channel. It is expected that the requestPurge // channel is setup to initiate a purge process when it receives the notificaiton. func Idle(src *imap.Client, appendRequests []chan WorkRequest, requestPurge chan bool) (err error) { var nextUID uint32 if nextUID, err = getNextUID(src); err != nil { log.Printf("Unable to get UIDNext: %s", err.Error()) return err } // hold the size so we can determine how to react to commands startSize := src.Mailbox.Messages // setup interrupt signal channel to terminate the idle interrupt := make(chan os.Signal, 1) signal.Notify(interrupt, os.Interrupt, os.Kill) // setup ticker to reset the idle every 20 minutes (RFC-2177 recommends 29 mins max) timeout := time.NewTicker(idleTimeoutMinutes * time.Minute) // setup poller signal for checking for data on the idle command poll := make(chan bool, 1) poll <- true log.Print("beginning idle...") _, idleErr := src.Idle() if (idleErr != nil) && (idleErr != imap.ErrTimeout) { log.Printf("Idle error: %s", idleErr.Error()) return } for { select { // if we receive a 'poll' we should check the pipe for new messages case <-poll: err = src.Recv(0) if (idleErr != nil) && (idleErr != imap.ErrTimeout) { log.Printf("Idle error: %s", idleErr.Error()) go sleep(poll) continue } // cache the data so we dont mess it up while start/stopping idle var tempData []*imap.Response tempData = append(tempData, src.Data...) src.Data = nil for _, data := range tempData { switch data.Type { case imap.Data: // len of 2 likely means its an EXPUNGE or EXISTS command... if len(data.Fields) == 2 { msgNum := imap.AsNumber(data.Fields[0]) switch data.Fields[1] { case "EXPUNGE": log.Printf("Received an EXPUNGE notification requesting purge - %d", msgNum) startSize = msgNum requestPurge <- true case "EXISTS": log.Printf("Received an EXISTS notification - %d", msgNum) if startSize > msgNum { log.Printf("Mailbox decreased in size %d --> %d. Requesting a purge. MAILBOX MAY NEED TO SYNC", startSize, msgNum) requestPurge <- true startSize = msgNum continue } // temporarily term the idle so we can fetch the message if _, err = src.IdleTerm(); err != nil { log.Printf("error while temporarily terminating idle: %s", err.Error()) return } log.Printf("terminated idle. appending message.") newMessages := msgNum - startSize log.Printf("attempting to find/append %d new messages", newMessages) for i := uint32(0); i < newMessages; i++ { var request WorkRequest if request, err = getMessageInfo(src, nextUID); err == nil { log.Printf("creating %d append requests for %d", len(appendRequests), nextUID) for _, requests := range appendRequests { requests <- request } log.Printf("done creating append requests for %d", nextUID) nextUID++ startSize++ } else { log.Printf("Unable to find message for UID (%d): %s", nextUID, err.Error()) } } log.Printf("continuing idle...") // turn idle back on if _, err = src.Idle(); err != nil { log.Printf("Unable to restart idle: %s", err.Error()) return } } } } } go sleep(poll) case <-interrupt: log.Printf("Received interrupt. Terminating idle...") _, err = src.IdleTerm() if err != nil { log.Printf("error while terminating idle: %s", err.Error()) } return case <-timeout.C: log.Printf("resetting idle...") _, err = src.IdleTerm() if err != nil { log.Printf("error while temporarily terminating idle: %s", err.Error()) return } log.Printf("terminated idle.") // turn idle back on _, err = src.Idle() if err != nil { log.Printf("Unable to restart idle: %s", err.Error()) return } log.Printf("idle restarted.") } } return }