diff options
| author | Drew DeVault <sir@cmpwn.com> | 2019-03-29 22:35:53 -0400 | 
|---|---|---|
| committer | Drew DeVault <sir@cmpwn.com> | 2019-03-29 22:36:15 -0400 | 
| commit | 77ede6eb5a22a5407541ac587736189fcca0037f (patch) | |
| tree | 45627c15f7087f91ed9881e776a566d440bd878e | |
| parent | 84e9853c1613f43f76bd46f0c1eb143d1db16ac2 (diff) | |
Add body fetching support code
| -rw-r--r-- | commands/account/fetch-msg.go | 29 | ||||
| -rw-r--r-- | go.mod | 2 | ||||
| -rw-r--r-- | go.sum | 4 | ||||
| -rw-r--r-- | lib/msgstore.go | 77 | ||||
| -rw-r--r-- | widgets/account.go | 3 | ||||
| -rw-r--r-- | widgets/msglist.go | 2 | ||||
| -rw-r--r-- | worker/imap/fetch.go | 61 | ||||
| -rw-r--r-- | worker/imap/worker.go | 2 | ||||
| -rw-r--r-- | worker/types/messages.go | 9 | 
9 files changed, 165 insertions, 24 deletions
diff --git a/commands/account/fetch-msg.go b/commands/account/fetch-msg.go new file mode 100644 index 0000000..631a8ee --- /dev/null +++ b/commands/account/fetch-msg.go @@ -0,0 +1,29 @@ +package account + +import ( +	"errors" + +	"github.com/mohamedattahri/mail" + +	"git.sr.ht/~sircmpwn/aerc2/widgets" +) + +func init() { +	register("fetch-message", FetchMessage) +} + +func FetchMessage(aerc *widgets.Aerc, args []string) error { +	if len(args) != 1 { +		return errors.New("Usage: :fetch-message") +	} +	acct := aerc.SelectedAccount() +	if acct == nil { +		return errors.New("No account selected") +	} +	store := acct.Messages().Store() +	msg := acct.Messages().Selected() +	store.FetchBodies([]uint32{msg.Uid}, func(msg *mail.Message) { +		aerc.SetStatus("got message body, woohoo") +	}) +	return nil +} @@ -15,8 +15,10 @@ require (  	github.com/mattn/go-isatty v0.0.3  	github.com/mattn/go-runewidth v0.0.2  	github.com/mitchellh/go-homedir v1.1.0 +	github.com/mohamedattahri/mail v0.0.0-20150907213728-52bc9c59063f  	github.com/nsf/termbox-go v0.0.0-20180129072728-88b7b944be8b  	github.com/riywo/loginshell v0.0.0-20181227004642-c2f4167b2303  	github.com/stretchr/testify v1.3.0  	golang.org/x/text v0.3.0 +	gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc // indirect  ) @@ -56,6 +56,8 @@ github.com/micromaomao/go-libvterm v0.0.0-20190126085614-2401b10ee7ed h1:SDQJB+u  github.com/micromaomao/go-libvterm v0.0.0-20190126085614-2401b10ee7ed/go.mod h1:TEYd4HSsUc2pZan5xJmjJQLA7c3d9dkV9lNsf8Xh3TY=  github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y=  github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= +github.com/mohamedattahri/mail v0.0.0-20150907213728-52bc9c59063f h1:eUB6ohYEAv7lbqKAMQXBfPfRxhvOUUQIrHYrs/+1UQs= +github.com/mohamedattahri/mail v0.0.0-20150907213728-52bc9c59063f/go.mod h1:lB0PjFC/A+yHl9ZdreyVugcdsF9KkK3JOHebiPhU1F8=  github.com/nsf/termbox-go v0.0.0-20180129072728-88b7b944be8b/go.mod h1:IuKpRQcYE1Tfu+oAQqaLisqDeXgjyyltCfsaoYN18NQ=  github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=  github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= @@ -67,3 +69,5 @@ github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UV  golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=  golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg=  golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc h1:2gGKlE2+asNV9m7xrywl36YYNnBG5ZQ0r/BOOxqPpmk= +gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc/go.mod h1:m7x9LTH6d71AHyAX77c9yqWCCa3UKHcVEj9y7hAtKDk= diff --git a/lib/msgstore.go b/lib/msgstore.go index 35c6606..be124df 100644 --- a/lib/msgstore.go +++ b/lib/msgstore.go @@ -2,6 +2,7 @@ package lib  import (  	"github.com/emersion/go-imap" +	"github.com/mohamedattahri/mail"  	"git.sr.ht/~sircmpwn/aerc2/worker/types"  ) @@ -11,6 +12,10 @@ type MessageStore struct {  	Messages map[uint32]*types.MessageInfo  	// Ordered list of known UIDs  	Uids []uint32 + +	bodyCallbacks   map[uint32][]func(*mail.Message) +	headerCallbacks map[uint32][]func(*types.MessageInfo) +  	// Map of uids we've asked the worker to fetch  	onUpdate       func(store *MessageStore) // TODO: multiple onUpdate handlers  	pendingBodies  map[uint32]interface{} @@ -24,13 +29,18 @@ func NewMessageStore(worker *types.Worker,  	return &MessageStore{  		DirInfo: *dirInfo, +		bodyCallbacks:   make(map[uint32][]func(*mail.Message)), +		headerCallbacks: make(map[uint32][]func(*types.MessageInfo)), +  		pendingBodies:  make(map[uint32]interface{}),  		pendingHeaders: make(map[uint32]interface{}),  		worker:         worker,  	}  } -func (store *MessageStore) FetchHeaders(uids []uint32) { +func (store *MessageStore) FetchHeaders(uids []uint32, +	cb func(*types.MessageInfo)) { +  	// TODO: this could be optimized by pre-allocating toFetch and trimming it  	// at the end. In practice we expect to get most messages back in one frame.  	var toFetch imap.SeqSet @@ -38,12 +48,50 @@ func (store *MessageStore) FetchHeaders(uids []uint32) {  		if _, ok := store.pendingHeaders[uid]; !ok {  			toFetch.AddNum(uint32(uid))  			store.pendingHeaders[uid] = nil +			if cb != nil { +				if list, ok := store.headerCallbacks[uid]; ok { +					store.headerCallbacks[uid] = append(list, cb) +				} else { +					store.headerCallbacks[uid] = []func(*types.MessageInfo){cb} +				} +			} +		} +	} +	if !toFetch.Empty() { +		store.worker.PostAction(&types.FetchMessageHeaders{Uids: toFetch}, nil) +	} +} + +func (store *MessageStore) FetchBodies(uids []uint32, +	cb func(*mail.Message)) { + +	// TODO: this could be optimized by pre-allocating toFetch and trimming it +	// at the end. In practice we expect to get most messages back in one frame. +	var toFetch imap.SeqSet +	for _, uid := range uids { +		if _, ok := store.pendingBodies[uid]; !ok { +			toFetch.AddNum(uint32(uid)) +			store.pendingBodies[uid] = nil +			if cb != nil { +				if list, ok := store.bodyCallbacks[uid]; ok { +					store.bodyCallbacks[uid] = append(list, cb) +				} else { +					store.bodyCallbacks[uid] = []func(*mail.Message){cb} +				} +			}  		}  	}  	if !toFetch.Empty() { -		store.worker.PostAction(&types.FetchMessageHeaders{ -			Uids: toFetch, -		}, nil) +		store.worker.PostAction(&types.FetchMessageBodies{Uids: toFetch}, nil) +	} +} + +func (store *MessageStore) merge( +	to *types.MessageInfo, from *types.MessageInfo) { + +	// TODO: Merge more shit +	if from.Envelope != nil { +		to.Envelope = from.Envelope  	}  } @@ -66,12 +114,29 @@ func (store *MessageStore) Update(msg types.WorkerMessage) {  		store.Uids = msg.Uids  		update = true  	case *types.MessageInfo: -		// TODO: merge message info into existing record, if applicable -		store.Messages[msg.Uid] = msg +		if existing, ok := store.Messages[msg.Uid]; ok && existing != nil { +			store.merge(existing, msg) +		} else { +			store.Messages[msg.Uid] = msg +		}  		if _, ok := store.pendingHeaders[msg.Uid]; msg.Envelope != nil && ok {  			delete(store.pendingHeaders, msg.Uid) +			if cbs, ok := store.headerCallbacks[msg.Uid]; ok { +				for _, cb := range cbs { +					cb(msg) +				} +			}  		}  		update = true +	case *types.MessageBody: +		if _, ok := store.pendingBodies[msg.Uid]; ok { +			delete(store.pendingBodies, msg.Uid) +			if cbs, ok := store.bodyCallbacks[msg.Uid]; ok { +				for _, cb := range cbs { +					cb(msg.Mail) +				} +			} +		}  	case *types.MessagesDeleted:  		toDelete := make(map[uint32]interface{})  		for _, uid := range msg.Uids { diff --git a/widgets/account.go b/widgets/account.go index f42ff6c..dd779b3 100644 --- a/widgets/account.go +++ b/widgets/account.go @@ -173,6 +173,9 @@ func (acct *AccountView) onMessage(msg types.WorkerMessage) {  	case *types.DirectoryContents:  		store := acct.msgStores[acct.dirlist.selected]  		store.Update(msg) +	case *types.MessageBody: +		store := acct.msgStores[acct.dirlist.selected] +		store.Update(msg)  	case *types.MessageInfo:  		store := acct.msgStores[acct.dirlist.selected]  		store.Update(msg) diff --git a/widgets/msglist.go b/widgets/msglist.go index b72fb03..18a7019 100644 --- a/widgets/msglist.go +++ b/widgets/msglist.go @@ -88,7 +88,7 @@ func (ml *MessageList) Draw(ctx *ui.Context) {  	}  	if len(needsHeaders) != 0 { -		ml.store.FetchHeaders(needsHeaders) +		ml.store.FetchHeaders(needsHeaders, nil)  		ml.spinner.Start()  	} else {  		ml.spinner.Stop() diff --git a/worker/imap/fetch.go b/worker/imap/fetch.go index 489dbe4..884ab73 100644 --- a/worker/imap/fetch.go +++ b/worker/imap/fetch.go @@ -2,6 +2,7 @@ package imap  import (  	"github.com/emersion/go-imap" +	"github.com/mohamedattahri/mail"  	"git.sr.ht/~sircmpwn/aerc2/worker/types"  ) @@ -10,28 +11,58 @@ func (imapw *IMAPWorker) handleFetchMessageHeaders(  	msg *types.FetchMessageHeaders) {  	imapw.worker.Logger.Printf("Fetching message headers") +	items := []imap.FetchItem{ +		imap.FetchEnvelope, +		imap.FetchInternalDate, +		imap.FetchFlags, +		imap.FetchUid, +	} + +	imapw.handleFetchMessages(msg, &msg.Uids, items) +} + +func (imapw *IMAPWorker) handleFetchMessageBodies( +	msg *types.FetchMessageBodies) { + +	imapw.worker.Logger.Printf("Fetching message bodies") +	section := &imap.BodySectionName{} +	items := []imap.FetchItem{section.FetchItem()} +	imapw.handleFetchMessages(msg, &msg.Uids, items) +} + +func (imapw *IMAPWorker) handleFetchMessages( +	msg types.WorkerMessage, uids *imap.SeqSet, items []imap.FetchItem) {  	go func() {  		messages := make(chan *imap.Message)  		done := make(chan error, 1) -		items := []imap.FetchItem{ -			imap.FetchEnvelope, -			imap.FetchInternalDate, -			imap.FetchFlags, -			imap.FetchUid, -		}  		go func() { -			done <- imapw.client.UidFetch(&msg.Uids, items, messages) +			done <- imapw.client.UidFetch(uids, items, messages)  		}()  		go func() { -			for msg := range messages { -				imapw.seqMap[msg.SeqNum-1] = msg.Uid -				imapw.worker.PostMessage(&types.MessageInfo{ -					Envelope:     msg.Envelope, -					Flags:        msg.Flags, -					InternalDate: msg.InternalDate, -					Uid:          msg.Uid, -				}, nil) +			section := &imap.BodySectionName{} +			for _msg := range messages { +				imapw.seqMap[_msg.SeqNum-1] = _msg.Uid +				if reader := _msg.GetBody(section); reader != nil { +					email, err := mail.ReadMessage(reader) +					if err != nil { +						imapw.worker.PostMessage(&types.Error{ +							Message: types.RespondTo(msg), +							Error:   err, +						}, nil) +					} +					imapw.worker.PostMessage(&types.MessageBody{ +						Mail: email, +						Uid:  _msg.Uid, +					}, nil) +				} else { +					imapw.worker.PostMessage(&types.MessageInfo{ +						Envelope:     _msg.Envelope, +						Flags:        _msg.Flags, +						InternalDate: _msg.InternalDate, +						Uid:          _msg.Uid, +					}, nil) +				}  			}  			if err := <-done; err != nil {  				imapw.worker.PostMessage(&types.Error{ diff --git a/worker/imap/worker.go b/worker/imap/worker.go index ea7f317..2f98595 100644 --- a/worker/imap/worker.go +++ b/worker/imap/worker.go @@ -158,6 +158,8 @@ func (w *IMAPWorker) handleMessage(msg types.WorkerMessage) error {  		w.handleFetchDirectoryContents(msg)  	case *types.FetchMessageHeaders:  		w.handleFetchMessageHeaders(msg) +	case *types.FetchMessageBodies: +		w.handleFetchMessageBodies(msg)  	case *types.DeleteMessages:  		w.handleDeleteMessages(msg)  	default: diff --git a/worker/types/messages.go b/worker/types/messages.go index ff2c36b..f38bb22 100644 --- a/worker/types/messages.go +++ b/worker/types/messages.go @@ -2,10 +2,10 @@ package types  import (  	"crypto/x509" -	"net/mail"  	"time"  	"github.com/emersion/go-imap" +	"github.com/mohamedattahri/mail"  	"git.sr.ht/~sircmpwn/aerc2/config"  ) @@ -123,11 +123,16 @@ type MessageInfo struct {  	Envelope     *imap.Envelope  	Flags        []string  	InternalDate time.Time -	Mail         *mail.Message  	Size         uint32  	Uid          uint32  } +type MessageBody struct { +	Message +	Mail *mail.Message +	Uid  uint32 +} +  type MessagesDeleted struct {  	Message  	Uids []uint32  | 
