Implement :delete-message

This commit is contained in:
Drew DeVault 2019-03-20 23:23:38 -04:00
parent f3d3e0ed4f
commit 312a53e5ff
9 changed files with 134 additions and 4 deletions

View file

@ -0,0 +1,25 @@
package commands
import (
"errors"
"git.sr.ht/~sircmpwn/aerc2/widgets"
)
func init() {
Register("delete-message", DeleteMessage)
}
func DeleteMessage(aerc *widgets.Aerc, args []string) error {
if len(args) != 1 {
return errors.New("Usage: :delete-message")
}
acct := aerc.SelectedAccount()
if acct == nil {
return errors.New("No account selected")
}
store := acct.Messages().Store()
msg := acct.Messages().Selected()
store.Delete([]uint32{msg.Uid})
return nil
}

View file

@ -1,6 +1,8 @@
package lib package lib
import ( import (
"fmt"
"github.com/emersion/go-imap" "github.com/emersion/go-imap"
"git.sr.ht/~sircmpwn/aerc2/worker/types" "git.sr.ht/~sircmpwn/aerc2/worker/types"
@ -53,7 +55,6 @@ func (store *MessageStore) Update(msg types.WorkerMessage) {
case *types.DirectoryInfo: case *types.DirectoryInfo:
store.DirInfo = *msg store.DirInfo = *msg
update = true update = true
break
case *types.DirectoryContents: case *types.DirectoryContents:
newMap := make(map[uint32]*types.MessageInfo) newMap := make(map[uint32]*types.MessageInfo)
for _, uid := range msg.Uids { for _, uid := range msg.Uids {
@ -66,7 +67,6 @@ func (store *MessageStore) Update(msg types.WorkerMessage) {
store.Messages = newMap store.Messages = newMap
store.Uids = msg.Uids store.Uids = msg.Uids
update = true update = true
break
case *types.MessageInfo: case *types.MessageInfo:
// TODO: merge message info into existing record, if applicable // TODO: merge message info into existing record, if applicable
store.Messages[msg.Uid] = msg store.Messages[msg.Uid] = msg
@ -74,7 +74,22 @@ func (store *MessageStore) Update(msg types.WorkerMessage) {
delete(store.pendingHeaders, msg.Uid) delete(store.pendingHeaders, msg.Uid)
} }
update = true update = true
break case *types.MessagesDeleted:
toDelete := make(map[uint32]interface{})
for _, uid := range msg.Uids {
toDelete[uid] = nil
delete(store.Messages, uid)
}
uids := make([]uint32, len(store.Uids)-len(msg.Uids))
j := 0
for i, uid := range store.Uids {
if _, deleted := toDelete[uid]; !deleted {
uids[j] = store.Uids[i]
j += 1
}
}
store.Uids = uids
update = true
} }
if update && store.onUpdate != nil { if update && store.onUpdate != nil {
store.onUpdate(store) store.onUpdate(store)
@ -84,3 +99,11 @@ func (store *MessageStore) Update(msg types.WorkerMessage) {
func (store *MessageStore) OnUpdate(fn func(store *MessageStore)) { func (store *MessageStore) OnUpdate(fn func(store *MessageStore)) {
store.onUpdate = fn store.onUpdate = fn
} }
func (store *MessageStore) Delete(uids []uint32) {
var set imap.SeqSet
for _, uid := range uids {
set.AddNum(uid)
}
store.worker.PostAction(&types.DeleteMessages{Uids: set}, nil)
}

View file

@ -176,6 +176,9 @@ func (acct *AccountView) onMessage(msg types.WorkerMessage) {
case *types.MessageInfo: case *types.MessageInfo:
store := acct.msgStores[acct.dirlist.selected] store := acct.msgStores[acct.dirlist.selected]
store.Update(msg) store.Update(msg)
case *types.MessagesDeleted:
store := acct.msgStores[acct.dirlist.selected]
store.Update(msg)
case *types.Error: case *types.Error:
acct.logger.Printf("%v", msg.Error) acct.logger.Printf("%v", msg.Error)
acct.host.SetStatus(fmt.Sprintf("%v", msg.Error)). acct.host.SetStatus(fmt.Sprintf("%v", msg.Error)).

View file

@ -8,6 +8,7 @@ import (
"git.sr.ht/~sircmpwn/aerc2/config" "git.sr.ht/~sircmpwn/aerc2/config"
"git.sr.ht/~sircmpwn/aerc2/lib" "git.sr.ht/~sircmpwn/aerc2/lib"
"git.sr.ht/~sircmpwn/aerc2/lib/ui" "git.sr.ht/~sircmpwn/aerc2/lib/ui"
"git.sr.ht/~sircmpwn/aerc2/worker/types"
) )
type MessageList struct { type MessageList struct {
@ -98,6 +99,16 @@ func (ml *MessageList) Height() int {
return ml.height return ml.height
} }
func (ml *MessageList) storeUpdate(store *lib.MessageStore) {
if ml.store != store {
return
}
for ml.selected >= len(ml.store.Uids) {
ml.Prev()
}
ml.Invalidate()
}
func (ml *MessageList) SetStore(store *lib.MessageStore) { func (ml *MessageList) SetStore(store *lib.MessageStore) {
if ml.store == store { if ml.store == store {
ml.scroll = 0 ml.scroll = 0
@ -106,12 +117,21 @@ func (ml *MessageList) SetStore(store *lib.MessageStore) {
ml.store = store ml.store = store
if store != nil { if store != nil {
ml.spinner.Stop() ml.spinner.Stop()
ml.store.OnUpdate(ml.storeUpdate)
} else { } else {
ml.spinner.Start() ml.spinner.Start()
} }
ml.Invalidate() ml.Invalidate()
} }
func (ml *MessageList) Store() *lib.MessageStore {
return ml.store
}
func (ml *MessageList) Selected() *types.MessageInfo {
return ml.store.Messages[ml.store.Uids[len(ml.store.Uids)-ml.selected-1]]
}
func (ml *MessageList) Select(index int) { func (ml *MessageList) Select(index int) {
ml.selected = index ml.selected = index
for ; ml.selected < 0; ml.selected = len(ml.store.Uids) + ml.selected { for ; ml.selected < 0; ml.selected = len(ml.store.Uids) + ml.selected {

View file

@ -25,6 +25,7 @@ func (imapw *IMAPWorker) handleFetchMessageHeaders(
}() }()
go func() { go func() {
for msg := range messages { for msg := range messages {
imapw.seqMap[msg.SeqNum-1] = msg.Uid
imapw.worker.PostMessage(&types.MessageInfo{ imapw.worker.PostMessage(&types.MessageInfo{
Envelope: msg.Envelope, Envelope: msg.Envelope,
Flags: msg.Flags, Flags: msg.Flags,

43
worker/imap/flags.go Normal file
View file

@ -0,0 +1,43 @@
package imap
import (
"github.com/emersion/go-imap"
"git.sr.ht/~sircmpwn/aerc2/worker/types"
)
func (imapw *IMAPWorker) handleDeleteMessages(msg *types.DeleteMessages) {
item := imap.FormatFlagsOp(imap.AddFlags, true)
flags := []interface{}{imap.DeletedFlag}
if err := imapw.client.UidStore(&msg.Uids, item, flags, nil); err != nil {
imapw.worker.PostMessage(&types.Error{
Message: types.RespondTo(msg),
Error: err,
}, nil)
return
}
var deleted []uint32
ch := make(chan uint32)
done := make(chan interface{})
go func() {
for seqNum := range ch {
i := seqNum - 1
deleted = append(deleted, imapw.seqMap[i])
imapw.seqMap = append(imapw.seqMap[:i], imapw.seqMap[i+1:]...)
}
done <- nil
}()
if err := imapw.client.Expunge(ch); err != nil {
imapw.worker.PostMessage(&types.Error{
Message: types.RespondTo(msg),
Error: err,
}, nil)
} else {
<-done
imapw.worker.PostMessage(&types.MessagesDeleted{
Message: types.RespondTo(msg),
Uids: deleted,
}, nil)
imapw.worker.PostMessage(&types.Done{types.RespondTo(msg)}, nil)
}
}

View file

@ -39,6 +39,7 @@ func (imapw *IMAPWorker) handleFetchDirectoryContents(
}, nil) }, nil)
} else { } else {
imapw.worker.Logger.Printf("Found %d UIDs", len(uids)) imapw.worker.Logger.Printf("Found %d UIDs", len(uids))
imapw.seqMap = make([]uint32, len(uids))
imapw.worker.PostMessage(&types.DirectoryContents{ imapw.worker.PostMessage(&types.DirectoryContents{
Message: types.RespondTo(msg), Message: types.RespondTo(msg),
Uids: uids, Uids: uids,

View file

@ -33,12 +33,14 @@ type IMAPWorker struct {
selected imap.MailboxStatus selected imap.MailboxStatus
updates chan client.Update updates chan client.Update
worker *types.Worker worker *types.Worker
// Map of sequence numbers to UIDs, index 0 is seq number 1
seqMap []uint32
} }
func NewIMAPWorker(worker *types.Worker) *IMAPWorker { func NewIMAPWorker(worker *types.Worker) *IMAPWorker {
return &IMAPWorker{ return &IMAPWorker{
worker: worker,
updates: make(chan client.Update, 50), updates: make(chan client.Update, 50),
worker: worker,
} }
} }
@ -156,6 +158,8 @@ func (w *IMAPWorker) handleMessage(msg types.WorkerMessage) error {
w.handleFetchDirectoryContents(msg) w.handleFetchDirectoryContents(msg)
case *types.FetchMessageHeaders: case *types.FetchMessageHeaders:
w.handleFetchMessageHeaders(msg) w.handleFetchMessageHeaders(msg)
case *types.DeleteMessages:
w.handleDeleteMessages(msg)
default: default:
return errUnsupported return errUnsupported
} }

View file

@ -86,6 +86,11 @@ type FetchMessageBodies struct {
Uids imap.SeqSet Uids imap.SeqSet
} }
type DeleteMessages struct {
Message
Uids imap.SeqSet
}
// Messages // Messages
type CertificateApprovalRequest struct { type CertificateApprovalRequest struct {
@ -122,3 +127,8 @@ type MessageInfo struct {
Size uint32 Size uint32
Uid uint32 Uid uint32
} }
type MessagesDeleted struct {
Message
Uids []uint32
}