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.
This commit is contained in:
Ben Burwell 2019-07-07 22:43:56 -04:00 committed by Drew DeVault
parent c79577d376
commit cce7cb4808
12 changed files with 145 additions and 85 deletions

View file

@ -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))

View file

@ -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]]
}

52
models/models.go Normal file
View file

@ -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
}

View file

@ -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)

View file

@ -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)

View file

@ -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]]
}

View file

@ -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) {

View file

@ -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
}

View file

@ -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)
}
}

View file

@ -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

View file

@ -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

View file

@ -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 {