From 91529df0fecc68d5b0fdbb682529ee545884e7c5 Mon Sep 17 00:00:00 2001
From: Ben Burwell <ben@benburwell.com>
Date: Wed, 3 Jul 2019 16:10:16 -0400
Subject: Factor UI models out of the worker message package

Before, the information needed to display different parts of the UI was
tightly coupled to the specific messages being sent back and forth to
the backend worker. Separating out a models package allows us to be more
specific about exactly what a backend is able to and required to
provide for the UI.
---
 lib/indexformat.go         |  4 ++--
 lib/msgstore.go            | 31 ++++++++++++++++---------------
 models/models.go           | 32 ++++++++++++++++++++++++++++++++
 widgets/account.go         |  3 ++-
 widgets/msglist.go         |  4 ++--
 widgets/msgviewer.go       | 18 +++++++++---------
 widgets/providesmessage.go |  4 ++--
 worker/imap/fetch.go       | 41 ++++++++++++++++++++++++++---------------
 worker/imap/worker.go      | 13 ++++++++-----
 worker/types/messages.go   | 16 ++++------------
 10 files changed, 103 insertions(+), 63 deletions(-)
 create mode 100644 models/models.go

diff --git a/lib/indexformat.go b/lib/indexformat.go
index 9e7a805..43d2ef8 100644
--- a/lib/indexformat.go
+++ b/lib/indexformat.go
@@ -9,11 +9,11 @@ import (
 	"github.com/emersion/go-imap"
 
 	"git.sr.ht/~sircmpwn/aerc/config"
-	"git.sr.ht/~sircmpwn/aerc/worker/types"
+	"git.sr.ht/~sircmpwn/aerc/models"
 )
 
 func ParseIndexFormat(conf *config.AercConfig, number int,
-	msg *types.MessageInfo) (string, []interface{}, error) {
+	msg *models.MessageInfo) (string, []interface{}, error) {
 
 	format := conf.Ui.IndexFormat
 	retval := make([]byte, 0, len(format))
diff --git a/lib/msgstore.go b/lib/msgstore.go
index 09cf31f..19f328d 100644
--- a/lib/msgstore.go
+++ b/lib/msgstore.go
@@ -6,6 +6,7 @@ import (
 
 	"github.com/emersion/go-imap"
 
+	"git.sr.ht/~sircmpwn/aerc/models"
 	"git.sr.ht/~sircmpwn/aerc/worker/types"
 )
 
@@ -13,7 +14,7 @@ import (
 type MessageStore struct {
 	Deleted  map[uint32]interface{}
 	DirInfo  types.DirectoryInfo
-	Messages map[uint32]*types.MessageInfo
+	Messages map[uint32]*models.MessageInfo
 	// Ordered list of known UIDs
 	Uids []uint32
 
@@ -106,11 +107,11 @@ func (store *MessageStore) FetchBodyPart(
 		if !ok {
 			return
 		}
-		cb(msg.Reader)
+		cb(msg.Part.Reader)
 	})
 }
 
-func merge(to *types.MessageInfo, from *types.MessageInfo) {
+func merge(to *models.MessageInfo, from *models.MessageInfo) {
 	if from.BodyStructure != nil {
 		to.BodyStructure = from.BodyStructure
 	}
@@ -135,7 +136,7 @@ func (store *MessageStore) Update(msg types.WorkerMessage) {
 		store.worker.PostAction(&types.FetchDirectoryContents{}, nil)
 		update = true
 	case *types.DirectoryContents:
-		newMap := make(map[uint32]*types.MessageInfo)
+		newMap := make(map[uint32]*models.MessageInfo)
 		for _, uid := range msg.Uids {
 			if msg, ok := store.Messages[uid]; ok {
 				newMap[uid] = msg
@@ -147,14 +148,14 @@ func (store *MessageStore) Update(msg types.WorkerMessage) {
 		store.Uids = msg.Uids
 		update = true
 	case *types.MessageInfo:
-		if existing, ok := store.Messages[msg.Uid]; ok && existing != nil {
-			merge(existing, msg)
+		if existing, ok := store.Messages[msg.Info.Uid]; ok && existing != nil {
+			merge(existing, msg.Info)
 		} else {
-			store.Messages[msg.Uid] = msg
+			store.Messages[msg.Info.Uid] = msg.Info
 		}
-		if _, ok := store.pendingHeaders[msg.Uid]; msg.Envelope != nil && ok {
-			delete(store.pendingHeaders, msg.Uid)
-			if cbs, ok := store.headerCallbacks[msg.Uid]; ok {
+		if _, ok := store.pendingHeaders[msg.Info.Uid]; msg.Info.Envelope != nil && ok {
+			delete(store.pendingHeaders, msg.Info.Uid)
+			if cbs, ok := store.headerCallbacks[msg.Info.Uid]; ok {
 				for _, cb := range cbs {
 					cb(msg)
 				}
@@ -162,11 +163,11 @@ func (store *MessageStore) Update(msg types.WorkerMessage) {
 		}
 		update = true
 	case *types.FullMessage:
-		if _, ok := store.pendingBodies[msg.Uid]; ok {
-			delete(store.pendingBodies, msg.Uid)
-			if cbs, ok := store.bodyCallbacks[msg.Uid]; ok {
+		if _, ok := store.pendingBodies[msg.Content.Uid]; ok {
+			delete(store.pendingBodies, msg.Content.Uid)
+			if cbs, ok := store.bodyCallbacks[msg.Content.Uid]; ok {
 				for _, cb := range cbs {
-					cb(msg.Reader)
+					cb(msg.Content.Reader)
 				}
 			}
 		}
@@ -283,7 +284,7 @@ func (store *MessageStore) Read(uids []uint32, read bool,
 	}, cb)
 }
 
-func (store *MessageStore) Selected() *types.MessageInfo {
+func (store *MessageStore) Selected() *models.MessageInfo {
 	return store.Messages[store.Uids[len(store.Uids)-store.selected-1]]
 }
 
diff --git a/models/models.go b/models/models.go
new file mode 100644
index 0000000..00297e9
--- /dev/null
+++ b/models/models.go
@@ -0,0 +1,32 @@
+package models
+
+import (
+	"io"
+	"time"
+
+	"github.com/emersion/go-imap"
+	"github.com/emersion/go-message/mail"
+)
+
+// A MessageInfo holds information about the structure of a message
+type MessageInfo struct {
+	BodyStructure *imap.BodyStructure
+	Envelope      *imap.Envelope
+	Flags         []string
+	InternalDate  time.Time
+	RFC822Headers *mail.Header
+	Size          uint32
+	Uid           uint32
+}
+
+// A MessageBodyPart can be displayed in the message viewer
+type MessageBodyPart struct {
+	Reader io.Reader
+	Uid    uint32
+}
+
+// A FullMessage is the entire message
+type FullMessage struct {
+	Reader io.Reader
+	Uid    uint32
+}
diff --git a/widgets/account.go b/widgets/account.go
index 824f958..ae0cf56 100644
--- a/widgets/account.go
+++ b/widgets/account.go
@@ -9,6 +9,7 @@ import (
 	"git.sr.ht/~sircmpwn/aerc/config"
 	"git.sr.ht/~sircmpwn/aerc/lib"
 	"git.sr.ht/~sircmpwn/aerc/lib/ui"
+	"git.sr.ht/~sircmpwn/aerc/models"
 	"git.sr.ht/~sircmpwn/aerc/worker"
 	"git.sr.ht/~sircmpwn/aerc/worker/types"
 )
@@ -165,7 +166,7 @@ func (acct *AccountView) Store() *lib.MessageStore {
 	return acct.msglist.Store()
 }
 
-func (acct *AccountView) SelectedMessage() *types.MessageInfo {
+func (acct *AccountView) SelectedMessage() *models.MessageInfo {
 	return acct.msglist.Selected()
 }
 
diff --git a/widgets/msglist.go b/widgets/msglist.go
index 211cbce..7051478 100644
--- a/widgets/msglist.go
+++ b/widgets/msglist.go
@@ -11,7 +11,7 @@ import (
 	"git.sr.ht/~sircmpwn/aerc/config"
 	"git.sr.ht/~sircmpwn/aerc/lib"
 	"git.sr.ht/~sircmpwn/aerc/lib/ui"
-	"git.sr.ht/~sircmpwn/aerc/worker/types"
+	"git.sr.ht/~sircmpwn/aerc/models"
 )
 
 type MessageList struct {
@@ -176,7 +176,7 @@ func (ml *MessageList) Empty() bool {
 	return store == nil || len(store.Uids) == 0
 }
 
-func (ml *MessageList) Selected() *types.MessageInfo {
+func (ml *MessageList) Selected() *models.MessageInfo {
 	store := ml.Store()
 	return store.Messages[store.Uids[len(store.Uids)-ml.store.SelectedIndex()-1]]
 }
diff --git a/widgets/msgviewer.go b/widgets/msgviewer.go
index 10c2182..49b4dd4 100644
--- a/widgets/msgviewer.go
+++ b/widgets/msgviewer.go
@@ -20,7 +20,7 @@ import (
 	"git.sr.ht/~sircmpwn/aerc/config"
 	"git.sr.ht/~sircmpwn/aerc/lib"
 	"git.sr.ht/~sircmpwn/aerc/lib/ui"
-	"git.sr.ht/~sircmpwn/aerc/worker/types"
+	"git.sr.ht/~sircmpwn/aerc/models"
 )
 
 var ansi = regexp.MustCompile("^\x1B\\[[0-?]*[ -/]*[@-~]")
@@ -31,7 +31,7 @@ type MessageViewer struct {
 	conf     *config.AercConfig
 	err      error
 	grid     *ui.Grid
-	msg      *types.MessageInfo
+	msg      *models.MessageInfo
 	switcher *PartSwitcher
 	store    *lib.MessageStore
 }
@@ -44,7 +44,7 @@ type PartSwitcher struct {
 }
 
 func NewMessageViewer(acct *AccountView, conf *config.AercConfig,
-	store *lib.MessageStore, msg *types.MessageInfo) *MessageViewer {
+	store *lib.MessageStore, msg *models.MessageInfo) *MessageViewer {
 
 	grid := ui.NewGrid().Rows([]ui.GridSpec{
 		{ui.SIZE_EXACT, 4}, // TODO: Based on number of header rows
@@ -112,7 +112,7 @@ handle_error:
 }
 
 func enumerateParts(conf *config.AercConfig, store *lib.MessageStore,
-	msg *types.MessageInfo, body *imap.BodyStructure,
+	msg *models.MessageInfo, body *imap.BodyStructure,
 	showHeaders bool, index []int) ([]*PartViewer, error) {
 
 	var parts []*PartViewer
@@ -140,7 +140,7 @@ func enumerateParts(conf *config.AercConfig, store *lib.MessageStore,
 }
 
 func createSwitcher(switcher *PartSwitcher, conf *config.AercConfig,
-	store *lib.MessageStore, msg *types.MessageInfo, showHeaders bool) error {
+	store *lib.MessageStore, msg *models.MessageInfo, showHeaders bool) error {
 	var err error
 	switcher.showHeaders = showHeaders
 
@@ -212,7 +212,7 @@ func (mv *MessageViewer) SelectedAccount() *AccountView {
 	return mv.acct
 }
 
-func (mv *MessageViewer) SelectedMessage() *types.MessageInfo {
+func (mv *MessageViewer) SelectedMessage() *models.MessageInfo {
 	return mv.msg
 }
 
@@ -321,7 +321,7 @@ type PartViewer struct {
 	fetched     bool
 	filter      *exec.Cmd
 	index       []int
-	msg         *types.MessageInfo
+	msg         *models.MessageInfo
 	pager       *exec.Cmd
 	pagerin     io.WriteCloser
 	part        *imap.BodyStructure
@@ -334,13 +334,13 @@ type PartViewer struct {
 
 type PartInfo struct {
 	Index []int
-	Msg   *types.MessageInfo
+	Msg   *models.MessageInfo
 	Part  *imap.BodyStructure
 	Store *lib.MessageStore
 }
 
 func NewPartViewer(conf *config.AercConfig,
-	store *lib.MessageStore, msg *types.MessageInfo,
+	store *lib.MessageStore, msg *models.MessageInfo,
 	part *imap.BodyStructure, showHeaders bool,
 	index []int) (*PartViewer, error) {
 
diff --git a/widgets/providesmessage.go b/widgets/providesmessage.go
index 7be8e7e..cff546b 100644
--- a/widgets/providesmessage.go
+++ b/widgets/providesmessage.go
@@ -3,12 +3,12 @@ package widgets
 import (
 	"git.sr.ht/~sircmpwn/aerc/lib"
 	"git.sr.ht/~sircmpwn/aerc/lib/ui"
-	"git.sr.ht/~sircmpwn/aerc/worker/types"
+	"git.sr.ht/~sircmpwn/aerc/models"
 )
 
 type ProvidesMessage interface {
 	ui.Drawable
 	Store() *lib.MessageStore
-	SelectedMessage() *types.MessageInfo
+	SelectedMessage() *models.MessageInfo
 	SelectedAccount() *AccountView
 }
diff --git a/worker/imap/fetch.go b/worker/imap/fetch.go
index 7d1bfcf..d5bb9aa 100644
--- a/worker/imap/fetch.go
+++ b/worker/imap/fetch.go
@@ -8,6 +8,7 @@ import (
 	"github.com/emersion/go-message/mail"
 	"github.com/emersion/go-message/textproto"
 
+	"git.sr.ht/~sircmpwn/aerc/models"
 	"git.sr.ht/~sircmpwn/aerc/worker/types"
 )
 
@@ -82,39 +83,49 @@ func (imapw *IMAPWorker) handleFetchMessages(
 					header = &mail.Header{message.Header{textprotoHeader}}
 				}
 				imapw.worker.PostMessage(&types.MessageInfo{
-					Message:       types.RespondTo(msg),
-					BodyStructure: _msg.BodyStructure,
-					Envelope:      _msg.Envelope,
-					Flags:         _msg.Flags,
-					InternalDate:  _msg.InternalDate,
-					RFC822Headers: header,
-					Uid:           _msg.Uid,
+					Message: types.RespondTo(msg),
+					Info: &models.MessageInfo{
+						BodyStructure: _msg.BodyStructure,
+						Envelope:      _msg.Envelope,
+						Flags:         _msg.Flags,
+						InternalDate:  _msg.InternalDate,
+						RFC822Headers: header,
+						Uid:           _msg.Uid,
+					},
 				}, nil)
 			case *types.FetchFullMessages:
 				reader := _msg.GetBody(section)
 				imapw.worker.PostMessage(&types.FullMessage{
 					Message: types.RespondTo(msg),
-					Reader:  reader,
-					Uid:     _msg.Uid,
+					Content: &models.FullMessage{
+						Reader: reader,
+						Uid:    _msg.Uid,
+					},
 				}, nil)
 				// Update flags (to mark message as read)
 				imapw.worker.PostMessage(&types.MessageInfo{
 					Message: types.RespondTo(msg),
-					Flags:   _msg.Flags,
-					Uid:     _msg.Uid,
+					Info: &models.MessageInfo{
+						Flags: _msg.Flags,
+						Uid:   _msg.Uid,
+					},
 				}, nil)
 			case *types.FetchMessageBodyPart:
 				reader := _msg.GetBody(section)
 				imapw.worker.PostMessage(&types.MessageBodyPart{
 					Message: types.RespondTo(msg),
-					Reader:  reader,
-					Uid:     _msg.Uid,
+					Part: &models.MessageBodyPart{
+						Reader: reader,
+						Uid:    _msg.Uid,
+					},
 				}, nil)
 				// Update flags (to mark message as read)
 				imapw.worker.PostMessage(&types.MessageInfo{
 					Message: types.RespondTo(msg),
-					Flags:   _msg.Flags,
-					Uid:     _msg.Uid,
+					Info: &models.MessageInfo{
+						Flags: _msg.Flags,
+						Uid:   _msg.Uid,
+					},
 				}, nil)
 			}
 		}
diff --git a/worker/imap/worker.go b/worker/imap/worker.go
index 5005620..ff02a78 100644
--- a/worker/imap/worker.go
+++ b/worker/imap/worker.go
@@ -10,6 +10,7 @@ import (
 	idle "github.com/emersion/go-imap-idle"
 	"github.com/emersion/go-imap/client"
 
+	"git.sr.ht/~sircmpwn/aerc/models"
 	"git.sr.ht/~sircmpwn/aerc/worker/types"
 )
 
@@ -183,11 +184,13 @@ func (w *IMAPWorker) handleImapUpdate(update client.Update) {
 			msg.Uid = w.seqMap[msg.SeqNum-1]
 		}
 		w.worker.PostMessage(&types.MessageInfo{
-			BodyStructure: msg.BodyStructure,
-			Envelope:      msg.Envelope,
-			Flags:         msg.Flags,
-			InternalDate:  msg.InternalDate,
-			Uid:           msg.Uid,
+			Info: &models.MessageInfo{
+				BodyStructure: msg.BodyStructure,
+				Envelope:      msg.Envelope,
+				Flags:         msg.Flags,
+				InternalDate:  msg.InternalDate,
+				Uid:           msg.Uid,
+			},
 		}, nil)
 	case *client.ExpungeUpdate:
 		i := update.SeqNum - 1
diff --git a/worker/types/messages.go b/worker/types/messages.go
index d9e911f..7eac896 100644
--- a/worker/types/messages.go
+++ b/worker/types/messages.go
@@ -5,9 +5,9 @@ import (
 	"time"
 
 	"github.com/emersion/go-imap"
-	"github.com/emersion/go-message/mail"
 
 	"git.sr.ht/~sircmpwn/aerc/config"
+	"git.sr.ht/~sircmpwn/aerc/models"
 )
 
 type WorkerMessage interface {
@@ -164,25 +164,17 @@ type SearchResults struct {
 
 type MessageInfo struct {
 	Message
-	BodyStructure *imap.BodyStructure
-	Envelope      *imap.Envelope
-	Flags         []string
-	InternalDate  time.Time
-	RFC822Headers *mail.Header
-	Size          uint32
-	Uid           uint32
+	Info *models.MessageInfo
 }
 
 type FullMessage struct {
 	Message
-	Reader io.Reader
-	Uid    uint32
+	Content *models.FullMessage
 }
 
 type MessageBodyPart struct {
 	Message
-	Reader io.Reader
-	Uid    uint32
+	Part *models.MessageBodyPart
 }
 
 type MessagesDeleted struct {
-- 
cgit v1.2.3