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:
parent
c79577d376
commit
cce7cb4808
12 changed files with 145 additions and 85 deletions
|
@ -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))
|
||||
|
|
|
@ -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
52
models/models.go
Normal 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
|
||||
}
|
|
@ -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)
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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]]
|
||||
}
|
||||
|
|
|
@ -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) {
|
||||
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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 {
|
||||
|
|
Loading…
Reference in a new issue