Added info subcommands to examine mailbox(es)

This commit is contained in:
Blallo 2021-02-01 19:48:45 +01:00
parent 54fb7b3be3
commit 28e0b28a71
No known key found for this signature in database
GPG key ID: 0CBE577C9B72DC3F
2 changed files with 272 additions and 6 deletions

View file

@ -1,15 +1,23 @@
package main package main
import ( import (
"errors"
"flag" "flag"
"fmt" "fmt"
"io" "io"
"io/ioutil"
"math"
"os" "os"
"strconv"
"strings"
"git.lattuga.net/blallo/papero/cli" "git.lattuga.net/blallo/papero/cli"
"git.lattuga.net/blallo/papero/imaputils" "git.lattuga.net/blallo/papero/imaputils"
"github.com/emersion/go-imap"
) )
const maxUInt32 = int(^uint32(0))
var infoVerbs cli.CommandMap var infoVerbs cli.CommandMap
func init() { func init() {
@ -54,11 +62,19 @@ func (i InfoCmd) Help(w io.Writer, set *flag.FlagSet) {
type LsMailboxCmd struct{} type LsMailboxCmd struct{}
type mailboxInfo struct {
name string
attrs string
}
func (l LsMailboxCmd) Func(args []string) error { func (l LsMailboxCmd) Func(args []string) error {
if Session.Info.Opts.Debug { if Session.Info.Opts.Debug {
Log.Debug("enter info list-mailboxes") Log.Debug("enter info list-mailboxes")
} }
var withAttributes bool var withAttributes bool
var output string
var maxLen int
var mailboxes []mailboxInfo
flagset := flag.NewFlagSet(args[0], flag.ExitOnError) flagset := flag.NewFlagSet(args[0], flag.ExitOnError)
flagset.BoolVar(&withAttributes, "with-attrs", false, "toggle attributes display") flagset.BoolVar(&withAttributes, "with-attrs", false, "toggle attributes display")
flagset.Usage = func() { l.Help(os.Stdout, flagset) } flagset.Usage = func() { l.Help(os.Stdout, flagset) }
@ -73,17 +89,30 @@ func (l LsMailboxCmd) Func(args []string) error {
} }
for _, box := range mboxInfo { for _, box := range mboxInfo {
if withAttributes { if withAttributes {
attrs := fmt.Sprint(box.Attributes) if nameLen := len(box.Name); nameLen > maxLen {
fmt.Printf("%s (attrs: %s)", box.Name, attrs) maxLen = nameLen
}
mailboxes = append(mailboxes, mailboxInfo{name: box.Name, attrs: fmt.Sprint(box.Attributes)})
} else { } else {
fmt.Println(box.Name) output += fmt.Sprintln(box.Name)
}
}
if maxLen == 0 {
fmt.Print(output)
} else {
padFmtStr := fmt.Sprintf("%%-%dv\t", maxLen)
for _, m := range mailboxes {
line := fmt.Sprintf(padFmtStr, m.name)
line += m.attrs
fmt.Println(line)
} }
} }
return nil return nil
} }
func (l LsMailboxCmd) Help(w io.Writer, set *flag.FlagSet) { func (l LsMailboxCmd) Help(w io.Writer, set *flag.FlagSet) {
fmt.Fprintf(w, "USAGE: %s info list-mailboxes\n", Session.Info.Name) fmt.Fprintf(w, "USAGE: %s info list-mailboxes [opts]\n", Session.Info.Name)
fmt.Fprintf(w, "\nOPTS:\n")
set.PrintDefaults() set.PrintDefaults()
} }
@ -94,8 +123,14 @@ func (l LsMsgCmd) Func(args []string) error {
Log.Debug("enter info list-messages") Log.Debug("enter info list-messages")
} }
var limit uint var limit uint
var withSeq, withFrom, withDate bool
var sep string
flagset := flag.NewFlagSet(args[0], flag.ExitOnError) flagset := flag.NewFlagSet(args[0], flag.ExitOnError)
flagset.StringVar(&sep, "sep", "\t", "separator between fields")
flagset.UintVar(&limit, "limit", 0, "maximum number of messages to display (0 means no limit)") flagset.UintVar(&limit, "limit", 0, "maximum number of messages to display (0 means no limit)")
flagset.BoolVar(&withSeq, "seq", false, "show sequence number")
flagset.BoolVar(&withFrom, "from", false, "show From address")
flagset.BoolVar(&withDate, "date", false, "show message date")
flagset.Usage = func() { l.Help(os.Stdout, flagset) } flagset.Usage = func() { l.Help(os.Stdout, flagset) }
flagset.Parse(args[1:]) flagset.Parse(args[1:])
@ -104,6 +139,12 @@ func (l LsMsgCmd) Func(args []string) error {
l.Help(os.Stderr, flagset) l.Help(os.Stderr, flagset)
os.Exit(1) os.Exit(1)
} }
msgs, err := imaputils.ListMessages(Session.Config, uint32(limit), Session.Info.Opts.Debug, subArgs[0])
if err != nil {
return err
}
printMsgs(msgs, withSeq, withFrom, withDate, sep)
return nil return nil
} }
@ -113,25 +154,154 @@ func (l LsMsgCmd) Help(w io.Writer, set *flag.FlagSet) {
set.PrintDefaults() set.PrintDefaults()
} }
func numberSize(n uint32) int {
return int(math.Floor(math.Log10(float64(n))))
}
func printMsgs(msgs []*imap.Message, withSeq, withFrom, withDate bool, sep string) {
var maxSeq, maxDateLen, maxFromLen int
var out, seqFmtStr, dateFmtStr, fromFmtStr string
for _, msg := range msgs {
if seqSize := numberSize(msg.SeqNum); withSeq && seqSize > maxSeq {
maxSeq = seqSize
}
if withDate {
date := fmt.Sprint(msg.Envelope.Date)
if dateLen := len(date); dateLen > maxDateLen {
maxDateLen = dateLen
}
}
if withFrom {
for _, from := range msg.Envelope.From {
if fromLen := len(from.Address()); fromLen > maxFromLen {
maxFromLen = fromLen
}
}
}
}
if withSeq {
seqFmtStr = fmt.Sprintf("%%%dv%s", maxSeq, sep)
}
if withDate {
dateFmtStr = fmt.Sprintf("%%-%dv%s", maxDateLen, sep)
}
if withFrom {
fromFmtStr = fmt.Sprintf("%%-%dv%s", maxFromLen, sep)
}
for _, msg := range msgs {
var line string
if withSeq {
line += fmt.Sprintf(seqFmtStr, msg.SeqNum)
}
if withDate {
line += fmt.Sprintf(dateFmtStr, msg.Envelope.Date)
}
if withFrom {
line += fmt.Sprintf(fromFmtStr, msg.Envelope.From[0].Address())
}
line += msg.Envelope.Subject
out += fmt.Sprintln(line)
}
fmt.Println(out)
}
type CatMsgCmd struct{} type CatMsgCmd struct{}
func (c CatMsgCmd) Func(args []string) error { func (c CatMsgCmd) Func(args []string) error {
if Session.Info.Opts.Debug { if Session.Info.Opts.Debug {
Log.Debug("enter info display-message") Log.Debug("enter info display-message")
} }
var idList []uint32
flagset := flag.NewFlagSet(args[0], flag.ExitOnError) flagset := flag.NewFlagSet(args[0], flag.ExitOnError)
flagset.Usage = func() { c.Help(os.Stdout, flagset) } flagset.Usage = func() { c.Help(os.Stdout, flagset) }
flagset.Parse(args[1:]) flagset.Parse(args[1:])
subArgs := flagset.Args() subArgs := flagset.Args()
if len(subArgs) != 1 { lenSubArgs := len(subArgs)
if lenSubArgs < 2 {
c.Help(os.Stderr, flagset) c.Help(os.Stderr, flagset)
os.Exit(1) os.Exit(1)
} }
mailbox := subArgs[0]
if lenSubArgs > 1 {
for _, id := range subArgs[1:] {
mailId, err := parseToUint32(id)
if err != nil {
return err
}
idList = append(idList, mailId)
}
}
messages, err := imaputils.FetchMessages(Session.Config, mailbox, idList, Session.Info.Opts.Debug)
if err != nil {
return err
}
for _, m := range messages {
err = printMessage(m)
if err != nil {
return err
}
}
return nil return nil
} }
func (c CatMsgCmd) Help(w io.Writer, set *flag.FlagSet) { func (c CatMsgCmd) Help(w io.Writer, set *flag.FlagSet) {
fmt.Fprintf(w, "USAGE: %s info display-message MAILBOX_NAME MESSAGE_ID\n", Session.Info.Name) fmt.Fprintf(w, "USAGE: %s info display-message [opts] MAILBOX_NAME [MESSAGE_ID1 [MESSAGE_ID2 [...]]]\n", Session.Info.Name)
fmt.Fprintf(w, "\nOPTS:\n")
set.PrintDefaults() set.PrintDefaults()
} }
var ErrOutOfBounds = errors.New("number is out of bounds")
func parseToUint32(s string) (uint32, error) {
out, err := strconv.Atoi(s)
if err != nil {
return 0, err
}
if out < 0 || out > maxUInt32 {
return 0, ErrOutOfBounds
}
return uint32(out), nil
}
func printMessage(m *imap.Message) error {
var out string
out += fmt.Sprintln(formatAddresses(m.Envelope.From, "From"))
out += fmt.Sprintln(formatAddresses(m.Envelope.Sender, "Sender"))
out += fmt.Sprintln(formatAddresses(m.Envelope.Cc, "Cc"))
out += fmt.Sprintln(formatAddresses(m.Envelope.Bcc, "Bcc"))
out += fmt.Sprintln(formatAddresses(m.Envelope.ReplyTo, "ReplyTo"))
out += fmt.Sprintf("InReplyTo: %s\n", m.Envelope.InReplyTo)
out += fmt.Sprintf("Date: %v\n", m.Envelope.Date)
out += fmt.Sprintf("Subject: %s\n", m.Envelope.Subject)
bodyReader := m.GetBody(&imap.BodySectionName{BodyPartName: imap.BodyPartName{}, Peek: true})
body, err := ioutil.ReadAll(bodyReader)
if err != nil {
return err
}
out += fmt.Sprintln(string(body))
fmt.Println(out)
return nil
}
func formatAddresses(addresses []*imap.Address, name string) string {
out := fmt.Sprintf("%s: ", name)
for _, address := range addresses {
if address.PersonalName != "" {
out += fmt.Sprintf("%s <%s>, ", address.PersonalName, address.Address())
} else {
out += fmt.Sprintf("%s, ", address.Address())
}
}
return strings.TrimRight(out, ", ")
}

View file

@ -31,3 +31,99 @@ func ListMailboxes(conf *config.AccountData, debug bool) ([]*imap.MailboxInfo, e
return mailboxes, nil return mailboxes, nil
} }
func ListMessages(conf *config.AccountData, limit uint32, debug bool, mailbox string) ([]*imap.Message, error) {
var messages []*imap.Message
conn := NewConnection(conf)
err := conn.Start(debug)
if err != nil {
return messages, err
}
defer conn.Close()
mbox, err := conn.client.Select(mailbox, true)
if err != nil {
return messages, err
}
return getMessages(mbox, conn, limit, 10)
}
func getMessages(mbox *imap.MailboxStatus, conn *IMAPConnection, limit, size uint32) ([]*imap.Message, error) {
var start uint32
var msgs []*imap.Message
seqset := new(imap.SeqSet)
messages := make(chan *imap.Message, size)
done := make(chan error, 1)
switch {
case limit == 0:
start = 1
case mbox.Messages > limit:
start = mbox.Messages - limit + 1
default:
start = 1
}
seqset.AddRange(start, mbox.Messages)
go func() {
done <- conn.client.Fetch(seqset, []imap.FetchItem{imap.FetchEnvelope}, messages)
}()
for msg := range messages {
msgs = append(msgs, msg)
}
if err := <-done; err != nil {
return []*imap.Message{}, err
}
return msgs, nil
}
func FetchMessages(conf *config.AccountData, mailbox string, idList []uint32, debug bool) ([]*imap.Message, error) {
var messages []*imap.Message
conn := NewConnection(conf)
err := conn.Start(debug)
if err != nil {
return messages, err
}
defer conn.Close()
mbox, err := conn.client.Select(mailbox, true)
if err != nil {
return messages, err
}
for _, id := range idList {
m, err := fetchMessage(mbox, conn, id)
if err != nil {
return messages, err
}
messages = append(messages, m)
}
return messages, nil
}
func fetchMessage(mbox *imap.MailboxStatus, conn *IMAPConnection, id uint32) (*imap.Message, error) {
var message *imap.Message
seqset := new(imap.SeqSet)
messages := make(chan *imap.Message, 1)
done := make(chan error, 1)
section := &imap.BodySectionName{}
section.Peek = true
items := []imap.FetchItem{imap.FetchEnvelope, section.FetchItem()}
seqset.AddNum(id)
go func() {
done <- conn.client.Fetch(seqset, items, messages)
}()
msg := <-messages
if err := <-done; err != nil {
return message, err
}
return msg, nil
}