mirror of
https://git.sr.ht/~rjarry/aerc
synced 2025-02-22 14:53:57 +01:00

When an error occurs during the opening of a message because its contents cannot be parsed, the PartSwitcher object is left to nil and the err field is set to the reported error. This defers the error reporting after the viewer tab is displayed but it is not handled in all sub functions which assume that switcher cannot be nil. Error: runtime error: invalid memory address or nil pointer dereference git.sr.ht/~rjarry/aerc/app.(*PartSwitcher).Show(...) /build/aerc/src/aerc/app/partswitcher.go:77 git.sr.ht/~rjarry/aerc/app.(*MessageViewer).Show(...) /build/aerc/src/aerc/app/msgviewer.go:409 git.sr.ht/~rjarry/aerc/lib/ui.(*Tabs).selectPriv(...) /build/aerc/src/aerc/lib/ui/tab.go:181 git.sr.ht/~rjarry/aerc/lib/ui.(*Tabs).Add(...) /build/aerc/src/aerc/lib/ui/tab.go:75 git.sr.ht/~rjarry/aerc/app.(*Aerc).NewTab(...) /build/aerc/src/aerc/app/aerc.go:511 git.sr.ht/~rjarry/aerc/app.NewTab(...) /build/aerc/src/aerc/app/app.go:61 git.sr.ht/~rjarry/aerc/commands/account.ViewMessage.Execute.func1(...) /build/aerc/src/aerc/commands/account/view.go:71 git.sr.ht/~rjarry/aerc/lib.NewMessageStoreView.func1(...) /build/aerc/src/aerc/lib/messageview.go:80 git.sr.ht/~rjarry/aerc/lib.NewMessageStoreView(...) /build/aerc/src/aerc/lib/messageview.go:124 git.sr.ht/~rjarry/aerc/commands/account.ViewMessage.Execute(...) /build/aerc/src/aerc/commands/account/view.go:52 git.sr.ht/~rjarry/aerc/commands.ExecuteCommand(...) /build/aerc/src/aerc/commands/commands.go:205 main.execCommand(...) Remove that private err field and return an explicit error when the message cannot be opened to enforce handling of the error by the caller. When the msg argument is nil (only used in split viewer), return an empty message viewer object and ensure that all code paths that read the switcher or msg fields perform a nil check before accessing it. Link: https://lists.sr.ht/~rjarry/aerc-devel/%3C12c465e4-b733-4b15-b4b0-62f87429fdf7@gmail.com%3E Link: https://lists.sr.ht/~rjarry/aerc-devel/%3C2C55CF50-A636-46E5-9BA8-FE60A2303ECA@proton.me%3E Link: https://lists.sr.ht/~rjarry/aerc-devel/%3CD51PEB6OMNDT.1KVSX0UCNL2MB@posteo.de%3E Reported-by: Benjamin Braun <ben.braun@posteo.de> Reported-by: Filip <filip.sh@proton.me> Reported-by: Sarthak Bhan <sbstratos79@gmail.com> Signed-off-by: Robin Jarry <robin@jarry.cc>
776 lines
19 KiB
Go
776 lines
19 KiB
Go
package app
|
|
|
|
import (
|
|
"bytes"
|
|
"errors"
|
|
"fmt"
|
|
"strings"
|
|
"sync"
|
|
"time"
|
|
|
|
"git.sr.ht/~rjarry/aerc/config"
|
|
"git.sr.ht/~rjarry/aerc/lib"
|
|
"git.sr.ht/~rjarry/aerc/lib/hooks"
|
|
"git.sr.ht/~rjarry/aerc/lib/log"
|
|
"git.sr.ht/~rjarry/aerc/lib/marker"
|
|
"git.sr.ht/~rjarry/aerc/lib/pama"
|
|
"git.sr.ht/~rjarry/aerc/lib/sort"
|
|
"git.sr.ht/~rjarry/aerc/lib/state"
|
|
"git.sr.ht/~rjarry/aerc/lib/templates"
|
|
"git.sr.ht/~rjarry/aerc/lib/ui"
|
|
"git.sr.ht/~rjarry/aerc/models"
|
|
"git.sr.ht/~rjarry/aerc/worker"
|
|
"git.sr.ht/~rjarry/aerc/worker/types"
|
|
"git.sr.ht/~rockorager/vaxis"
|
|
)
|
|
|
|
var _ ProvidesMessages = (*AccountView)(nil)
|
|
|
|
type AccountView struct {
|
|
sync.Mutex
|
|
acct *config.AccountConfig
|
|
dirlist DirectoryLister
|
|
labels []string
|
|
grid *ui.Grid
|
|
tab *ui.Tab
|
|
msglist *MessageList
|
|
worker *types.Worker
|
|
state state.AccountState
|
|
newConn bool // True if this is a first run after a new connection/reconnection
|
|
|
|
split *MessageViewer
|
|
splitSize int
|
|
splitDebounce *time.Timer
|
|
splitDir config.SplitDirection
|
|
splitLoaded bool
|
|
|
|
// Check-mail ticker
|
|
ticker *time.Ticker
|
|
checkingMail bool
|
|
}
|
|
|
|
func (acct *AccountView) UiConfig() *config.UIConfig {
|
|
if dirlist := acct.Directories(); dirlist != nil {
|
|
return dirlist.UiConfig("")
|
|
}
|
|
return config.Ui.ForAccount(acct.acct.Name)
|
|
}
|
|
|
|
func NewAccountView(
|
|
acct *config.AccountConfig, deferLoop chan struct{},
|
|
) (*AccountView, error) {
|
|
view := &AccountView{
|
|
acct: acct,
|
|
}
|
|
|
|
worker, err := worker.NewWorker(acct.Source, acct.Name)
|
|
if err != nil {
|
|
SetError(fmt.Sprintf("%s: %s", acct.Name, err))
|
|
log.Errorf("%s: %v", acct.Name, err)
|
|
return view, err
|
|
}
|
|
view.worker = worker
|
|
|
|
view.dirlist = NewDirectoryList(acct, worker)
|
|
|
|
view.msglist = NewMessageList(view)
|
|
|
|
view.Configure()
|
|
|
|
go func() {
|
|
defer log.PanicHandler()
|
|
|
|
if deferLoop != nil {
|
|
<-deferLoop
|
|
}
|
|
|
|
worker.Backend.Run()
|
|
}()
|
|
|
|
worker.PostAction(&types.Configure{Config: acct}, nil)
|
|
worker.PostAction(&types.Connect{}, nil)
|
|
view.SetStatus(state.ConnectionActivity("Connecting..."))
|
|
if acct.CheckMail.Minutes() > 0 {
|
|
view.CheckMailTimer(acct.CheckMail)
|
|
}
|
|
|
|
return view, nil
|
|
}
|
|
|
|
func (acct *AccountView) Configure() {
|
|
acct.dirlist.OnVirtualNode(func() {
|
|
acct.msglist.SetStore(nil)
|
|
acct.Invalidate()
|
|
})
|
|
sidebar := acct.UiConfig().SidebarWidth
|
|
acct.grid = ui.NewGrid().Rows([]ui.GridSpec{
|
|
{Strategy: ui.SIZE_WEIGHT, Size: ui.Const(1)},
|
|
}).Columns([]ui.GridSpec{
|
|
{Strategy: ui.SIZE_EXACT, Size: func() int {
|
|
return sidebar
|
|
}},
|
|
{Strategy: ui.SIZE_WEIGHT, Size: ui.Const(1)},
|
|
})
|
|
if sidebar > 0 {
|
|
acct.grid.AddChild(ui.NewBordered(acct.dirlist, ui.BORDER_RIGHT, acct.UiConfig()))
|
|
}
|
|
acct.grid.AddChild(acct.msglist).At(0, 1)
|
|
acct.setTitle()
|
|
|
|
// handle splits
|
|
if acct.split != nil {
|
|
acct.split.Close()
|
|
}
|
|
splitDirection := acct.splitDir
|
|
acct.splitDir = config.SPLIT_NONE
|
|
switch splitDirection {
|
|
case config.SPLIT_HORIZONTAL:
|
|
acct.Split(acct.SplitSize())
|
|
case config.SPLIT_VERTICAL:
|
|
acct.Vsplit(acct.SplitSize())
|
|
}
|
|
}
|
|
|
|
func (acct *AccountView) SetStatus(setters ...state.SetStateFunc) {
|
|
for _, fn := range setters {
|
|
fn(&acct.state, acct.SelectedDirectory())
|
|
}
|
|
acct.UpdateStatus()
|
|
}
|
|
|
|
func (acct *AccountView) UpdateStatus() {
|
|
if acct.isSelected() {
|
|
UpdateStatus()
|
|
}
|
|
}
|
|
|
|
func (acct *AccountView) Select() {
|
|
for i, widget := range aerc.tabs.TabContent.Children() {
|
|
if widget == acct {
|
|
aerc.SelectTabIndex(i)
|
|
}
|
|
}
|
|
}
|
|
|
|
func (acct *AccountView) PushStatus(status string, expiry time.Duration) {
|
|
PushStatus(fmt.Sprintf("%s: %s", acct.acct.Name, status), expiry)
|
|
}
|
|
|
|
func (acct *AccountView) PushError(err error) {
|
|
PushError(fmt.Sprintf("%s: %v", acct.acct.Name, err))
|
|
}
|
|
|
|
func (acct *AccountView) PushWarning(warning string) {
|
|
PushWarning(fmt.Sprintf("%s: %s", acct.acct.Name, warning))
|
|
}
|
|
|
|
func (acct *AccountView) AccountConfig() *config.AccountConfig {
|
|
return acct.acct
|
|
}
|
|
|
|
func (acct *AccountView) Worker() *types.Worker {
|
|
return acct.worker
|
|
}
|
|
|
|
func (acct *AccountView) Name() string {
|
|
return acct.acct.Name
|
|
}
|
|
|
|
func (acct *AccountView) Invalidate() {
|
|
ui.Invalidate()
|
|
}
|
|
|
|
func (acct *AccountView) Draw(ctx *ui.Context) {
|
|
acct.grid.Draw(ctx)
|
|
}
|
|
|
|
func (acct *AccountView) MouseEvent(localX int, localY int, event vaxis.Event) {
|
|
acct.grid.MouseEvent(localX, localY, event)
|
|
}
|
|
|
|
func (acct *AccountView) Focus(focus bool) {
|
|
// TODO: Unfocus children I guess
|
|
}
|
|
|
|
func (acct *AccountView) Directories() DirectoryLister {
|
|
return acct.dirlist
|
|
}
|
|
|
|
func (acct *AccountView) SetDirectories(d DirectoryLister) {
|
|
if acct.grid != nil {
|
|
acct.grid.ReplaceChild(acct.dirlist, d)
|
|
}
|
|
acct.dirlist = d
|
|
}
|
|
|
|
func (acct *AccountView) Labels() []string {
|
|
return acct.labels
|
|
}
|
|
|
|
func (acct *AccountView) Messages() *MessageList {
|
|
return acct.msglist
|
|
}
|
|
|
|
func (acct *AccountView) Store() *lib.MessageStore {
|
|
if acct.msglist == nil {
|
|
return nil
|
|
}
|
|
return acct.msglist.Store()
|
|
}
|
|
|
|
func (acct *AccountView) SelectedAccount() *AccountView {
|
|
return acct
|
|
}
|
|
|
|
func (acct *AccountView) SelectedDirectory() string {
|
|
return acct.dirlist.Selected()
|
|
}
|
|
|
|
func (acct *AccountView) SelectedMessage() (*models.MessageInfo, error) {
|
|
if acct.msglist == nil || acct.msglist.Store() == nil {
|
|
return nil, errors.New("init in progress")
|
|
}
|
|
if len(acct.msglist.Store().Uids()) == 0 {
|
|
return nil, errors.New("no message selected")
|
|
}
|
|
msg := acct.msglist.Selected()
|
|
if msg == nil {
|
|
return nil, errors.New("message not loaded")
|
|
}
|
|
return msg, nil
|
|
}
|
|
|
|
func (acct *AccountView) MarkedMessages() ([]models.UID, error) {
|
|
if store := acct.Store(); store != nil {
|
|
return store.Marker().Marked(), nil
|
|
}
|
|
return nil, errors.New("no store available")
|
|
}
|
|
|
|
func (acct *AccountView) SelectedMessagePart() *PartInfo {
|
|
return nil
|
|
}
|
|
|
|
func (acct *AccountView) Terminal() *Terminal {
|
|
if acct.split == nil {
|
|
return nil
|
|
}
|
|
|
|
return acct.split.Terminal()
|
|
}
|
|
|
|
func (acct *AccountView) isSelected() bool {
|
|
return acct == SelectedAccount()
|
|
}
|
|
|
|
func (acct *AccountView) newStore(name string) *lib.MessageStore {
|
|
uiConf := acct.dirlist.UiConfig(name)
|
|
dir := acct.dirlist.Directory(name)
|
|
role := ""
|
|
if dir != nil {
|
|
role = string(dir.Role)
|
|
}
|
|
backend := acct.AccountConfig().Backend
|
|
store := lib.NewMessageStore(acct.worker, name,
|
|
func() *config.UIConfig {
|
|
return config.Ui.
|
|
ForAccount(acct.Name()).
|
|
ForFolder(name)
|
|
},
|
|
func(msg *models.MessageInfo) {
|
|
err := hooks.RunHook(&hooks.MailReceived{
|
|
Account: acct.Name(),
|
|
Backend: backend,
|
|
Folder: name,
|
|
Role: role,
|
|
MsgInfo: msg,
|
|
})
|
|
if err != nil {
|
|
msg := fmt.Sprintf("mail-received hook: %s", err)
|
|
PushError(msg)
|
|
}
|
|
}, func() {
|
|
if uiConf.NewMessageBell {
|
|
aerc.Beep()
|
|
}
|
|
}, func() {
|
|
err := hooks.RunHook(&hooks.MailDeleted{
|
|
Account: acct.Name(),
|
|
Backend: backend,
|
|
Folder: name,
|
|
Role: role,
|
|
})
|
|
if err != nil {
|
|
msg := fmt.Sprintf("mail-deleted hook: %s", err)
|
|
PushError(msg)
|
|
}
|
|
}, func(dest string) {
|
|
err := hooks.RunHook(&hooks.MailAdded{
|
|
Account: acct.Name(),
|
|
Backend: backend,
|
|
Folder: dest,
|
|
Role: role,
|
|
})
|
|
if err != nil {
|
|
msg := fmt.Sprintf("mail-added hook: %s", err)
|
|
PushError(msg)
|
|
}
|
|
}, func(add []string, remove []string) {
|
|
err := hooks.RunHook(&hooks.TagModified{
|
|
Account: acct.Name(),
|
|
Backend: backend,
|
|
Add: add,
|
|
Remove: remove,
|
|
})
|
|
if err != nil {
|
|
msg := fmt.Sprintf("tag-modified hook: %s", err)
|
|
PushError(msg)
|
|
}
|
|
}, func(flagname string) {
|
|
err := hooks.RunHook(&hooks.FlagChanged{
|
|
Account: acct.Name(),
|
|
Backend: backend,
|
|
Folder: acct.SelectedDirectory(),
|
|
Role: role,
|
|
FlagName: flagname,
|
|
})
|
|
if err != nil {
|
|
msg := fmt.Sprintf("flag-changed hook: %s", err)
|
|
PushError(msg)
|
|
}
|
|
},
|
|
func(msg *models.MessageInfo) {
|
|
acct.updateSplitView(msg)
|
|
|
|
auto := false
|
|
if c := acct.AccountConfig(); c != nil {
|
|
r, ok := c.Params["pama-auto-switch"]
|
|
if ok {
|
|
if strings.ToLower(r) == "true" {
|
|
auto = true
|
|
}
|
|
}
|
|
}
|
|
if !auto {
|
|
return
|
|
}
|
|
var name string
|
|
if msg != nil && msg.Envelope != nil {
|
|
name = pama.FromSubject(msg.Envelope.Subject)
|
|
}
|
|
pama.DebouncedSwitchProject(name)
|
|
},
|
|
)
|
|
store.Configure(acct.SortCriteria(uiConf))
|
|
store.SetMarker(marker.New(store))
|
|
return store
|
|
}
|
|
|
|
func (acct *AccountView) onMessage(msg types.WorkerMessage) {
|
|
msg = acct.worker.ProcessMessage(msg)
|
|
switch msg := msg.(type) {
|
|
case *types.Done:
|
|
switch resp := msg.InResponseTo().(type) {
|
|
case *types.Connect, *types.Reconnect:
|
|
acct.SetStatus(state.ConnectionActivity("Listing mailboxes..."))
|
|
log.Infof("[%s] connected.", acct.acct.Name)
|
|
acct.SetStatus(state.SetConnected(true))
|
|
log.Tracef("Listing mailboxes...")
|
|
acct.worker.PostAction(&types.ListDirectories{}, nil)
|
|
case *types.Disconnect:
|
|
acct.dirlist.ClearList()
|
|
acct.msglist.SetStore(nil)
|
|
log.Infof("[%s] disconnected.", acct.acct.Name)
|
|
acct.SetStatus(state.SetConnected(false))
|
|
case *types.OpenDirectory:
|
|
acct.dirlist.Update(msg)
|
|
if store, ok := acct.dirlist.SelectedMsgStore(); ok {
|
|
// If we've opened this dir before, we can re-render it from
|
|
// memory while we wait for the update and the UI feels
|
|
// snappier. If not, we'll unset the store and show the spinner
|
|
// while we download the UID list.
|
|
acct.msglist.SetStore(store)
|
|
acct.Store().Update(msg.InResponseTo())
|
|
} else {
|
|
acct.msglist.SetStore(nil)
|
|
}
|
|
case *types.CreateDirectory:
|
|
store := acct.newStore(resp.Directory)
|
|
acct.dirlist.SetMsgStore(&models.Directory{
|
|
Name: resp.Directory,
|
|
}, store)
|
|
acct.dirlist.Update(msg)
|
|
case *types.RemoveDirectory:
|
|
acct.dirlist.Update(msg)
|
|
case *types.FetchMessageHeaders:
|
|
if acct.newConn {
|
|
acct.checkMailOnStartup()
|
|
}
|
|
case *types.ListDirectories:
|
|
acct.dirlist.Update(msg)
|
|
if dir := acct.dirlist.Selected(); dir != "" {
|
|
acct.dirlist.Select(dir)
|
|
return
|
|
}
|
|
// Nothing selected, select based on config
|
|
dirs := acct.dirlist.List()
|
|
var dir string
|
|
for _, _dir := range dirs {
|
|
if _dir == acct.acct.Default {
|
|
dir = _dir
|
|
break
|
|
}
|
|
}
|
|
if dir == "" && len(dirs) > 0 {
|
|
dir = dirs[0]
|
|
}
|
|
if dir != "" {
|
|
acct.dirlist.Select(dir)
|
|
}
|
|
acct.msglist.SetInitDone()
|
|
acct.newConn = true
|
|
}
|
|
case *types.Directory:
|
|
store, ok := acct.dirlist.MsgStore(msg.Dir.Name)
|
|
if !ok {
|
|
store = acct.newStore(msg.Dir.Name)
|
|
}
|
|
acct.dirlist.SetMsgStore(msg.Dir, store)
|
|
case *types.DirectoryInfo:
|
|
acct.dirlist.Update(msg)
|
|
case *types.DirectoryContents:
|
|
if store, ok := acct.dirlist.SelectedMsgStore(); ok {
|
|
if acct.msglist.Store() == nil {
|
|
acct.msglist.SetStore(store)
|
|
}
|
|
store.Update(msg)
|
|
acct.SetStatus(state.Threading(store.ThreadedView()))
|
|
}
|
|
if acct.newConn && len(msg.Uids) == 0 {
|
|
acct.checkMailOnStartup()
|
|
}
|
|
case *types.DirectoryThreaded:
|
|
if store, ok := acct.dirlist.SelectedMsgStore(); ok {
|
|
if acct.msglist.Store() == nil {
|
|
acct.msglist.SetStore(store)
|
|
}
|
|
store.Update(msg)
|
|
acct.SetStatus(state.Threading(store.ThreadedView()))
|
|
}
|
|
if acct.newConn && len(msg.Threads) == 0 {
|
|
acct.checkMailOnStartup()
|
|
}
|
|
case *types.FullMessage:
|
|
if store, ok := acct.dirlist.SelectedMsgStore(); ok {
|
|
store.Update(msg)
|
|
}
|
|
case *types.MessageInfo:
|
|
if store, ok := acct.dirlist.SelectedMsgStore(); ok {
|
|
store.Update(msg)
|
|
}
|
|
case *types.MessagesDeleted:
|
|
if dir := acct.dirlist.SelectedDirectory(); dir != nil {
|
|
dir.Exists -= len(msg.Uids)
|
|
}
|
|
if store, ok := acct.dirlist.SelectedMsgStore(); ok {
|
|
store.Update(msg)
|
|
}
|
|
case *types.MessagesCopied:
|
|
acct.updateDirCounts(msg.Destination, msg.Uids)
|
|
case *types.MessagesMoved:
|
|
acct.updateDirCounts(msg.Destination, msg.Uids)
|
|
case *types.LabelList:
|
|
acct.labels = msg.Labels
|
|
case *types.ConnError:
|
|
log.Errorf("[%s] connection error: %v", acct.acct.Name, msg.Error)
|
|
acct.SetStatus(state.SetConnected(false))
|
|
acct.PushError(msg.Error)
|
|
acct.msglist.SetStore(nil)
|
|
acct.worker.PostAction(&types.Reconnect{}, nil)
|
|
case *types.Error:
|
|
log.Errorf("[%s] unexpected error: %v", acct.acct.Name, msg.Error)
|
|
acct.PushError(msg.Error)
|
|
}
|
|
acct.UpdateStatus()
|
|
acct.setTitle()
|
|
}
|
|
|
|
func (acct *AccountView) updateDirCounts(destination string, uids []models.UID) {
|
|
// Only update the destination destDir if it is initialized
|
|
if destDir := acct.dirlist.Directory(destination); destDir != nil {
|
|
var recent, unseen int
|
|
var accurate bool = true
|
|
for _, uid := range uids {
|
|
// Get the message from the originating store
|
|
msg, ok := acct.Store().Messages[uid]
|
|
if !ok {
|
|
continue
|
|
}
|
|
// If message that was not yet loaded is copied
|
|
if msg == nil {
|
|
accurate = false
|
|
break
|
|
}
|
|
seen := msg.Flags.Has(models.SeenFlag)
|
|
if msg.Flags.Has(models.RecentFlag) {
|
|
recent++
|
|
}
|
|
if !seen {
|
|
unseen++
|
|
}
|
|
}
|
|
if accurate {
|
|
destDir.Recent += recent
|
|
destDir.Unseen += unseen
|
|
destDir.Exists += len(uids)
|
|
} else {
|
|
destDir.Exists += len(uids)
|
|
}
|
|
}
|
|
}
|
|
|
|
func (acct *AccountView) SortCriteria(uiConf *config.UIConfig) []*types.SortCriterion {
|
|
if uiConf == nil {
|
|
return nil
|
|
}
|
|
if len(uiConf.Sort) == 0 {
|
|
return nil
|
|
}
|
|
criteria, err := sort.GetSortCriteria(uiConf.Sort)
|
|
if err != nil {
|
|
acct.PushError(fmt.Errorf("ui sort: %w", err))
|
|
return nil
|
|
}
|
|
return criteria
|
|
}
|
|
|
|
func (acct *AccountView) GetSortCriteria() []*types.SortCriterion {
|
|
return acct.SortCriteria(acct.UiConfig())
|
|
}
|
|
|
|
func (acct *AccountView) CheckMail() {
|
|
acct.Lock()
|
|
defer acct.Unlock()
|
|
if acct.checkingMail {
|
|
return
|
|
}
|
|
// Exclude selected mailbox, per IMAP specification
|
|
exclude := append(acct.AccountConfig().CheckMailExclude, acct.dirlist.Selected()) //nolint:gocritic // intentional append to different slice
|
|
dirs := acct.dirlist.List()
|
|
dirs = acct.dirlist.FilterDirs(dirs, acct.AccountConfig().CheckMailInclude, false)
|
|
dirs = acct.dirlist.FilterDirs(dirs, exclude, true)
|
|
log.Debugf("Checking for new mail on account %s", acct.Name())
|
|
acct.SetStatus(state.ConnectionActivity("Checking for new mail..."))
|
|
msg := &types.CheckMail{
|
|
Directories: dirs,
|
|
Command: acct.acct.CheckMailCmd,
|
|
Timeout: acct.acct.CheckMailTimeout,
|
|
}
|
|
acct.checkingMail = true
|
|
|
|
var cb func(types.WorkerMessage)
|
|
cb = func(response types.WorkerMessage) {
|
|
dirsMsg, ok := response.(*types.CheckMailDirectories)
|
|
if ok {
|
|
checkMailMsg := &types.CheckMail{
|
|
Directories: dirsMsg.Directories,
|
|
Command: acct.acct.CheckMailCmd,
|
|
Timeout: acct.acct.CheckMailTimeout,
|
|
}
|
|
acct.worker.PostAction(checkMailMsg, cb)
|
|
} else { // Done
|
|
acct.SetStatus(state.ConnectionActivity(""))
|
|
acct.Lock()
|
|
acct.checkingMail = false
|
|
acct.Unlock()
|
|
}
|
|
}
|
|
acct.worker.PostAction(msg, cb)
|
|
}
|
|
|
|
// CheckMailReset resets the check-mail timer
|
|
func (acct *AccountView) CheckMailReset() {
|
|
if acct.ticker != nil {
|
|
d := acct.AccountConfig().CheckMail
|
|
acct.ticker = time.NewTicker(d)
|
|
}
|
|
}
|
|
|
|
func (acct *AccountView) checkMailOnStartup() {
|
|
if acct.AccountConfig().CheckMail.Minutes() > 0 {
|
|
acct.newConn = false
|
|
acct.CheckMail()
|
|
}
|
|
}
|
|
|
|
func (acct *AccountView) CheckMailTimer(d time.Duration) {
|
|
acct.ticker = time.NewTicker(d)
|
|
go func() {
|
|
defer log.PanicHandler()
|
|
for range acct.ticker.C {
|
|
if !acct.state.Connected {
|
|
continue
|
|
}
|
|
acct.CheckMail()
|
|
}
|
|
}()
|
|
}
|
|
|
|
func (acct *AccountView) closeSplit() {
|
|
if acct.split != nil {
|
|
acct.split.Close()
|
|
}
|
|
acct.splitSize = 0
|
|
acct.splitDir = config.SPLIT_NONE
|
|
acct.split = nil
|
|
acct.grid = ui.NewGrid().Rows([]ui.GridSpec{
|
|
{Strategy: ui.SIZE_WEIGHT, Size: ui.Const(1)},
|
|
}).Columns([]ui.GridSpec{
|
|
{Strategy: ui.SIZE_EXACT, Size: func() int {
|
|
return acct.UiConfig().SidebarWidth
|
|
}},
|
|
{Strategy: ui.SIZE_WEIGHT, Size: ui.Const(1)},
|
|
})
|
|
|
|
acct.grid.AddChild(ui.NewBordered(acct.dirlist, ui.BORDER_RIGHT, acct.UiConfig()))
|
|
acct.grid.AddChild(acct.msglist).At(0, 1)
|
|
ui.Invalidate()
|
|
}
|
|
|
|
func (acct *AccountView) updateSplitView(msg *models.MessageInfo) {
|
|
uiConf := acct.UiConfig()
|
|
if !acct.splitLoaded {
|
|
switch uiConf.MessageListSplit.Direction {
|
|
case config.SPLIT_HORIZONTAL:
|
|
acct.Split(uiConf.MessageListSplit.Size)
|
|
case config.SPLIT_VERTICAL:
|
|
acct.Vsplit(uiConf.MessageListSplit.Size)
|
|
}
|
|
acct.splitLoaded = true
|
|
}
|
|
if acct.splitSize == 0 || !acct.splitLoaded {
|
|
return
|
|
}
|
|
if acct.splitDebounce != nil {
|
|
acct.splitDebounce.Stop()
|
|
}
|
|
fn := func() {
|
|
if acct.split != nil {
|
|
acct.grid.RemoveChild(acct.split)
|
|
acct.split.Close()
|
|
}
|
|
lib.NewMessageStoreView(msg, false, acct.Store(), CryptoProvider(), DecryptKeys,
|
|
func(view lib.MessageView, err error) {
|
|
if err != nil {
|
|
PushError(err.Error())
|
|
return
|
|
}
|
|
viewer, err := NewMessageViewer(acct, view)
|
|
if err != nil {
|
|
PushError(err.Error())
|
|
return
|
|
}
|
|
acct.split = viewer
|
|
switch acct.splitDir {
|
|
case config.SPLIT_HORIZONTAL:
|
|
acct.grid.AddChild(acct.split).At(1, 1)
|
|
case config.SPLIT_VERTICAL:
|
|
acct.grid.AddChild(acct.split).At(0, 2)
|
|
}
|
|
})
|
|
}
|
|
acct.splitDebounce = time.AfterFunc(100*time.Millisecond, func() {
|
|
ui.QueueFunc(fn)
|
|
})
|
|
}
|
|
|
|
func (acct *AccountView) SplitSize() int {
|
|
return acct.splitSize
|
|
}
|
|
|
|
func (acct *AccountView) SetSplitSize(n int) {
|
|
if n == 0 {
|
|
acct.closeSplit()
|
|
}
|
|
acct.splitSize = n
|
|
}
|
|
|
|
// Split splits the message list view horizontally. The message list will be n
|
|
// rows high. If n is 0, any existing split is removed
|
|
func (acct *AccountView) Split(n int) {
|
|
acct.SetSplitSize(n)
|
|
if acct.splitDir == config.SPLIT_HORIZONTAL || n == 0 {
|
|
return
|
|
}
|
|
acct.splitDir = config.SPLIT_HORIZONTAL
|
|
acct.grid = ui.NewGrid().Rows([]ui.GridSpec{
|
|
// Add 1 so that the splitSize is the number of visible messages
|
|
{Strategy: ui.SIZE_EXACT, Size: func() int { return acct.SplitSize() + 1 }},
|
|
{Strategy: ui.SIZE_WEIGHT, Size: ui.Const(1)},
|
|
}).Columns([]ui.GridSpec{
|
|
{Strategy: ui.SIZE_EXACT, Size: func() int {
|
|
return acct.UiConfig().SidebarWidth
|
|
}},
|
|
{Strategy: ui.SIZE_WEIGHT, Size: ui.Const(1)},
|
|
})
|
|
|
|
acct.grid.AddChild(ui.NewBordered(acct.dirlist, ui.BORDER_RIGHT, acct.UiConfig())).Span(2, 1)
|
|
acct.grid.AddChild(ui.NewBordered(acct.msglist, ui.BORDER_BOTTOM, acct.UiConfig())).At(0, 1)
|
|
acct.split, _ = NewMessageViewer(acct, nil)
|
|
acct.grid.AddChild(acct.split).At(1, 1)
|
|
msg, err := acct.SelectedMessage()
|
|
if err != nil {
|
|
log.Debugf("split: load message error: %v", err)
|
|
}
|
|
acct.updateSplitView(msg)
|
|
}
|
|
|
|
// Vsplit splits the message list view vertically. The message list will be n
|
|
// rows wide. If n is 0, any existing split is removed
|
|
func (acct *AccountView) Vsplit(n int) {
|
|
acct.SetSplitSize(n)
|
|
if acct.splitDir == config.SPLIT_VERTICAL || n == 0 {
|
|
return
|
|
}
|
|
acct.splitDir = config.SPLIT_VERTICAL
|
|
acct.grid = ui.NewGrid().Rows([]ui.GridSpec{
|
|
{Strategy: ui.SIZE_WEIGHT, Size: ui.Const(1)},
|
|
}).Columns([]ui.GridSpec{
|
|
{Strategy: ui.SIZE_EXACT, Size: func() int {
|
|
return acct.UiConfig().SidebarWidth
|
|
}},
|
|
{Strategy: ui.SIZE_EXACT, Size: acct.SplitSize},
|
|
{Strategy: ui.SIZE_WEIGHT, Size: ui.Const(1)},
|
|
})
|
|
|
|
acct.grid.AddChild(ui.NewBordered(acct.dirlist, ui.BORDER_RIGHT, acct.UiConfig())).At(0, 0)
|
|
acct.grid.AddChild(ui.NewBordered(acct.msglist, ui.BORDER_RIGHT, acct.UiConfig())).At(0, 1)
|
|
acct.split, _ = NewMessageViewer(acct, nil)
|
|
acct.grid.AddChild(acct.split).At(0, 2)
|
|
msg, err := acct.SelectedMessage()
|
|
if err != nil {
|
|
log.Debugf("split: load message error: %v", err)
|
|
}
|
|
acct.updateSplitView(msg)
|
|
}
|
|
|
|
// setTitle executes the title template and sets the tab title
|
|
func (acct *AccountView) setTitle() {
|
|
if acct.tab == nil {
|
|
return
|
|
}
|
|
|
|
data := state.NewDataSetter()
|
|
data.SetAccount(acct.acct)
|
|
data.SetFolder(acct.Directories().SelectedDirectory())
|
|
data.SetRUE(acct.dirlist.List(), acct.dirlist.GetRUECount)
|
|
data.SetState(&acct.state)
|
|
|
|
var buf bytes.Buffer
|
|
err := templates.Render(acct.UiConfig().TabTitleAccount, &buf, data.Data())
|
|
if err != nil {
|
|
acct.PushError(err)
|
|
return
|
|
}
|
|
acct.tab.SetTitle(buf.String())
|
|
}
|