fix: load uid and name separately (#45)

drew created

Change summary

fetcher/fetcher.go | 59 ++++++++++++++++++++++++++++++-----------------
main.go            | 32 ++++++++++++++++++++++++++
tui/messages.go    |  2 
3 files changed, 71 insertions(+), 22 deletions(-)

Detailed changes

fetcher/fetcher.go 🔗

@@ -5,6 +5,7 @@ import (
 	"fmt"
 	"io"
 	"io/ioutil"
+	"log"
 	"mime"
 	"strings"
 	"time"
@@ -146,13 +147,28 @@ func FetchEmails(cfg *config.Config, limit, offset uint32) ([]Email, error) {
 		done <- c.Fetch(seqset, fetchItems, messages)
 	}()
 
-	var emails []Email
+	// Collect messages from the channel into a slice
+	var msgs []*imap.Message
 	for msg := range messages {
-		if msg == nil {
+		msgs = append(msgs, msg)
+	}
+
+	// Wait for the fetch to complete and check for errors
+	if err := <-done; err != nil {
+		return nil, err
+	}
+
+	var emails []Email
+	for _, msg := range msgs {
+		if msg == nil || msg.Envelope == nil {
 			continue
 		}
 
-		fromAddrs := msg.Envelope.From[0].Address()
+		var fromAddr string
+		if len(msg.Envelope.From) > 0 {
+			fromAddr = msg.Envelope.From[0].Address()
+		}
+
 		var toAddrList []string
 		for _, addr := range msg.Envelope.To {
 			toAddrList = append(toAddrList, addr.Address())
@@ -160,17 +176,13 @@ func FetchEmails(cfg *config.Config, limit, offset uint32) ([]Email, error) {
 
 		emails = append(emails, Email{
 			UID:     msg.Uid,
-			From:    fromAddrs,
+			From:    fromAddr,
 			To:      toAddrList,
 			Subject: decodeHeader(msg.Envelope.Subject),
 			Date:    msg.Envelope.Date,
 		})
 	}
 
-	if err := <-done; err != nil {
-		return nil, err
-	}
-
 	// Reverse the order to show newest first
 	for i, j := 0, len(emails)-1; i < j; i, j = i+1, j-1 {
 		emails[i], emails[j] = emails[j], emails[i]
@@ -179,8 +191,6 @@ func FetchEmails(cfg *config.Config, limit, offset uint32) ([]Email, error) {
 	return emails, nil
 }
 
-
-// New function to fetch the body for a single email
 func FetchEmailBody(cfg *config.Config, uid uint32) (string, []Attachment, error) {
 	c, err := connect(cfg)
 	if err != nil {
@@ -199,12 +209,23 @@ func FetchEmailBody(cfg *config.Config, uid uint32) (string, []Attachment, error
 	done := make(chan error, 1)
 	fetchItems := []imap.FetchItem{imap.FetchItem("BODY[]")}
 	go func() {
-		done <- c.Fetch(seqset, fetchItems, messages)
+		done <- c.UidFetch(seqset, fetchItems, messages)
 	}()
 
-	msg := <-messages
-	if msg == nil {
-		return "", nil, fmt.Errorf("could not fetch email body")
+	// Wait for the fetch operation to complete first.
+	if err := <-done; err != nil {
+		return "", nil, err
+	}
+
+	// Now that the fetch is complete, check for a message.
+	var msg *imap.Message
+	select {
+	case msg = <-messages:
+		if msg == nil {
+			return "", nil, fmt.Errorf("fetched a nil message")
+		}
+	default:
+		return "", nil, fmt.Errorf("no message found with UID %d", uid)
 	}
 
 	bodyLiteral := msg.GetBody(&imap.BodySectionName{})
@@ -224,7 +245,8 @@ func FetchEmailBody(cfg *config.Config, uid uint32) (string, []Attachment, error
 		if err == io.EOF {
 			break
 		} else if err != nil {
-			return "", nil, fmt.Errorf("error getting next part: %v", err)
+			log.Printf("Error getting next part: %v", err)
+			break
 		}
 
 		cdHeader := p.Header.Get("Content-Disposition")
@@ -256,14 +278,9 @@ func FetchEmailBody(cfg *config.Config, uid uint32) (string, []Attachment, error
 		}
 	}
 
-	if err := <-done; err != nil {
-		return "", nil, err
-	}
-
 	return body, attachments, nil
 }
 
-
 func moveEmail(cfg *config.Config, uid uint32, destMailbox string) error {
 	c, err := connect(cfg)
 	if err != nil {
@@ -314,4 +331,4 @@ func ArchiveEmail(cfg *config.Config, uid uint32) error {
 		archiveMailbox = "Archive"
 	}
 	return moveEmail(cfg, uid, archiveMailbox)
-}
+}

main.go 🔗

@@ -152,6 +152,21 @@ func (m *mainModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
 		return m, m.current.Init()
 
 	case tui.ViewEmailMsg:
+		// Show a status message while fetching the email body
+		m.current = tui.NewStatus("Fetching email content...")
+		// Pass the index directly to the command
+		return m, tea.Batch(m.current.Init(), fetchEmailBodyCmd(m.config, m.emails[msg.Index], msg.Index))
+
+	case tui.EmailBodyFetchedMsg:
+		if msg.Err != nil {
+			log.Printf("could not fetch email body: %v", msg.Err)
+			m.current = m.inbox
+			return m, nil
+		}
+		// Use the index from the message to update the correct email
+		m.emails[msg.Index].Body = msg.Body
+		m.emails[msg.Index].Attachments = msg.Attachments
+		
 		emailView := tui.NewEmailView(m.emails[msg.Index], m.width, m.height)
 		m.current = emailView
 		return m, m.current.Init()
@@ -248,6 +263,23 @@ func (m *mainModel) View() string {
 	return m.current.View()
 }
 
+func fetchEmailBodyCmd(cfg *config.Config, email fetcher.Email, index int) tea.Cmd {
+	return func() tea.Msg {
+		body, attachments, err := fetcher.FetchEmailBody(cfg, email.UID)
+		if err != nil {
+			return tui.EmailBodyFetchedMsg{Index: index, Err: err}
+		}
+
+		// Return the fetched data along with the original index
+		return tui.EmailBodyFetchedMsg{
+			Index:       index,
+			Body:        body,
+			Attachments: attachments,
+		}
+	}
+}
+
+
 func markdownToHTML(md []byte) []byte {
 	var buf bytes.Buffer
 	p := goldmark.New(goldmark.WithRendererOptions(html.WithUnsafe()))

tui/messages.go 🔗

@@ -116,4 +116,4 @@ type EmailBodyFetchedMsg struct {
 	Body        string
 	Attachments []fetcher.Attachment
 	Err         error
-}
+}