From cce7cb48081ca090ac2d3a0e781dfbc25d581946 Mon Sep 17 00:00:00 2001 From: Ben Burwell Date: Sun, 7 Jul 2019 22:43:56 -0400 Subject: [PATCH] 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 | 37 ++++++++++++++------------- models/models.go | 52 ++++++++++++++++++++++++++++++++++++++ widgets/account.go | 9 ++++--- widgets/dirlist.go | 2 +- widgets/msglist.go | 4 +-- widgets/msgviewer.go | 16 ++++++------ widgets/providesmessage.go | 4 +-- worker/imap/fetch.go | 41 +++++++++++++++++++----------- worker/imap/list.go | 9 ++++--- worker/imap/worker.go | 27 ++++++++++++-------- worker/types/messages.go | 25 +++++------------- 12 files changed, 145 insertions(+), 85 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..77160ae 100644 --- a/lib/msgstore.go +++ b/lib/msgstore.go @@ -6,14 +6,15 @@ import ( "github.com/emersion/go-imap" + "git.sr.ht/~sircmpwn/aerc/models" "git.sr.ht/~sircmpwn/aerc/worker/types" ) // Accesses to fields must be guarded by MessageStore.Lock/Unlock type MessageStore struct { Deleted map[uint32]interface{} - DirInfo types.DirectoryInfo - Messages map[uint32]*types.MessageInfo + DirInfo models.DirectoryInfo + Messages map[uint32]*models.MessageInfo // Ordered list of known UIDs Uids []uint32 @@ -33,7 +34,7 @@ type MessageStore struct { } func NewMessageStore(worker *types.Worker, - dirInfo *types.DirectoryInfo) *MessageStore { + dirInfo *models.DirectoryInfo) *MessageStore { return &MessageStore{ Deleted: make(map[uint32]interface{}), @@ -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 } @@ -131,11 +132,11 @@ func (store *MessageStore) Update(msg types.WorkerMessage) { update := false switch msg := msg.(type) { case *types.DirectoryInfo: - store.DirInfo = *msg + store.DirInfo = *msg.Info 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..cff05b1 --- /dev/null +++ b/models/models.go @@ -0,0 +1,52 @@ +package models + +import ( + "io" + "time" + + "github.com/emersion/go-imap" + "github.com/emersion/go-message/mail" +) + +type Directory struct { + Name string + Attributes []string +} + +type DirectoryInfo struct { + Name string + Flags []string + ReadOnly bool + + // The total number of messages in this mailbox. + Exists int + + // The number of messages not seen since the last time the mailbox was opened. + Recent int + + // The number of unread messages + Unseen int +} + +// 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 0948c5c..e08a253 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" ) @@ -169,7 +170,7 @@ func (acct *AccountView) SelectedAccount() *AccountView { return acct } -func (acct *AccountView) SelectedMessage() *types.MessageInfo { +func (acct *AccountView) SelectedMessage() *models.MessageInfo { return acct.msglist.Selected() } @@ -195,11 +196,11 @@ func (acct *AccountView) onMessage(msg types.WorkerMessage) { acct.dirlist.UpdateList(nil) } case *types.DirectoryInfo: - if store, ok := acct.msgStores[msg.Name]; ok { + if store, ok := acct.msgStores[msg.Info.Name]; ok { store.Update(msg) } else { - store = lib.NewMessageStore(acct.worker, msg) - acct.msgStores[msg.Name] = store + store = lib.NewMessageStore(acct.worker, msg.Info) + acct.msgStores[msg.Info.Name] = store store.OnUpdate(func(_ *lib.MessageStore) { store.OnUpdate(nil) acct.msglist.SetStore(store) diff --git a/widgets/dirlist.go b/widgets/dirlist.go index 4dc8fd2..c5e4a0c 100644 --- a/widgets/dirlist.go +++ b/widgets/dirlist.go @@ -55,7 +55,7 @@ func (dirlist *DirectoryList) UpdateList(done func(dirs []string)) { switch msg := msg.(type) { case *types.Directory: - dirs = append(dirs, msg.Name) + dirs = append(dirs, msg.Dir.Name) case *types.Done: sort.Strings(dirs) dirlist.store.Update(dirs) 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 b0ae79e..6a645f9 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 @@ -333,7 +333,7 @@ type PartViewer struct { } 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 4b71637..d8b1e77 100644 --- a/widgets/providesmessage.go +++ b/widgets/providesmessage.go @@ -5,7 +5,7 @@ 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 PartInfo struct { @@ -19,6 +19,6 @@ type ProvidesMessage interface { ui.Drawable Store() *lib.MessageStore SelectedAccount() *AccountView - SelectedMessage() *types.MessageInfo + SelectedMessage() *models.MessageInfo SelectedMessagePart() *PartInfo } 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/list.go b/worker/imap/list.go index 708e70f..42be50e 100644 --- a/worker/imap/list.go +++ b/worker/imap/list.go @@ -3,6 +3,7 @@ package imap import ( "github.com/emersion/go-imap" + "git.sr.ht/~sircmpwn/aerc/models" "git.sr.ht/~sircmpwn/aerc/worker/types" ) @@ -18,9 +19,11 @@ func (imapw *IMAPWorker) handleListDirectories(msg *types.ListDirectories) { continue } imapw.worker.PostMessage(&types.Directory{ - Message: types.RespondTo(msg), - Name: mbox.Name, - Attributes: mbox.Attributes, + Message: types.RespondTo(msg), + Dir: &models.Directory{ + Name: mbox.Name, + Attributes: mbox.Attributes, + }, }, nil) } done <- nil diff --git a/worker/imap/worker.go b/worker/imap/worker.go index 5005620..9ddaa47 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" ) @@ -169,13 +170,15 @@ func (w *IMAPWorker) handleImapUpdate(update client.Update) { w.selected = *status } w.worker.PostMessage(&types.DirectoryInfo{ - Flags: status.Flags, - Name: status.Name, - ReadOnly: status.ReadOnly, + Info: &models.DirectoryInfo{ + Flags: status.Flags, + Name: status.Name, + ReadOnly: status.ReadOnly, - Exists: int(status.Messages), - Recent: int(status.Recent), - Unseen: int(status.Unseen), + Exists: int(status.Messages), + Recent: int(status.Recent), + Unseen: int(status.Unseen), + }, }, nil) case *client.MessageUpdate: msg := update.Message @@ -183,11 +186,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..bb2505a 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 { @@ -139,17 +139,12 @@ type AppendMessage struct { type Directory struct { Message - Attributes []string - Name string + Dir *models.Directory } type DirectoryInfo struct { Message - Flags []string - Name string - ReadOnly bool - - Exists, Recent, Unseen int + Info *models.DirectoryInfo } type DirectoryContents struct { @@ -164,25 +159,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 {