aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--commands/delete-message.go25
-rw-r--r--lib/msgstore.go29
-rw-r--r--widgets/account.go3
-rw-r--r--widgets/msglist.go20
-rw-r--r--worker/imap/fetch.go1
-rw-r--r--worker/imap/flags.go43
-rw-r--r--worker/imap/open.go1
-rw-r--r--worker/imap/worker.go6
-rw-r--r--worker/types/messages.go10
9 files changed, 134 insertions, 4 deletions
diff --git a/commands/delete-message.go b/commands/delete-message.go
new file mode 100644
index 00000000..be56dbbb
--- /dev/null
+++ b/commands/delete-message.go
@@ -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
+}
diff --git a/lib/msgstore.go b/lib/msgstore.go
index d745093f..6830f64c 100644
--- a/lib/msgstore.go
+++ b/lib/msgstore.go
@@ -1,6 +1,8 @@
package lib
import (
+ "fmt"
+
"github.com/emersion/go-imap"
"git.sr.ht/~sircmpwn/aerc2/worker/types"
@@ -53,7 +55,6 @@ func (store *MessageStore) Update(msg types.WorkerMessage) {
case *types.DirectoryInfo:
store.DirInfo = *msg
update = true
- break
case *types.DirectoryContents:
newMap := make(map[uint32]*types.MessageInfo)
for _, uid := range msg.Uids {
@@ -66,7 +67,6 @@ func (store *MessageStore) Update(msg types.WorkerMessage) {
store.Messages = newMap
store.Uids = msg.Uids
update = true
- break
case *types.MessageInfo:
// TODO: merge message info into existing record, if applicable
store.Messages[msg.Uid] = msg
@@ -74,7 +74,22 @@ func (store *MessageStore) Update(msg types.WorkerMessage) {
delete(store.pendingHeaders, msg.Uid)
}
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 {
store.onUpdate(store)
@@ -84,3 +99,11 @@ func (store *MessageStore) Update(msg types.WorkerMessage) {
func (store *MessageStore) OnUpdate(fn func(store *MessageStore)) {
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)
+}
diff --git a/widgets/account.go b/widgets/account.go
index 8a3b989a..f42ff6c4 100644
--- a/widgets/account.go
+++ b/widgets/account.go
@@ -176,6 +176,9 @@ func (acct *AccountView) onMessage(msg types.WorkerMessage) {
case *types.MessageInfo:
store := acct.msgStores[acct.dirlist.selected]
store.Update(msg)
+ case *types.MessagesDeleted:
+ store := acct.msgStores[acct.dirlist.selected]
+ store.Update(msg)
case *types.Error:
acct.logger.Printf("%v", msg.Error)
acct.host.SetStatus(fmt.Sprintf("%v", msg.Error)).
diff --git a/widgets/msglist.go b/widgets/msglist.go
index ab25847f..ac941c8b 100644
--- a/widgets/msglist.go
+++ b/widgets/msglist.go
@@ -8,6 +8,7 @@ import (
"git.sr.ht/~sircmpwn/aerc2/config"
"git.sr.ht/~sircmpwn/aerc2/lib"
"git.sr.ht/~sircmpwn/aerc2/lib/ui"
+ "git.sr.ht/~sircmpwn/aerc2/worker/types"
)
type MessageList struct {
@@ -98,6 +99,16 @@ func (ml *MessageList) Height() int {
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) {
if ml.store == store {
ml.scroll = 0
@@ -106,12 +117,21 @@ func (ml *MessageList) SetStore(store *lib.MessageStore) {
ml.store = store
if store != nil {
ml.spinner.Stop()
+ ml.store.OnUpdate(ml.storeUpdate)
} else {
ml.spinner.Start()
}
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) {
ml.selected = index
for ; ml.selected < 0; ml.selected = len(ml.store.Uids) + ml.selected {
diff --git a/worker/imap/fetch.go b/worker/imap/fetch.go
index 383a8a8f..489dbe4d 100644
--- a/worker/imap/fetch.go
+++ b/worker/imap/fetch.go
@@ -25,6 +25,7 @@ func (imapw *IMAPWorker) handleFetchMessageHeaders(
}()
go func() {
for msg := range messages {
+ imapw.seqMap[msg.SeqNum-1] = msg.Uid
imapw.worker.PostMessage(&types.MessageInfo{
Envelope: msg.Envelope,
Flags: msg.Flags,
diff --git a/worker/imap/flags.go b/worker/imap/flags.go
new file mode 100644
index 00000000..cb9b3b1e
--- /dev/null
+++ b/worker/imap/flags.go
@@ -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)
+ }
+}
diff --git a/worker/imap/open.go b/worker/imap/open.go
index 87c4fb33..3705bc0d 100644
--- a/worker/imap/open.go
+++ b/worker/imap/open.go
@@ -39,6 +39,7 @@ func (imapw *IMAPWorker) handleFetchDirectoryContents(
}, nil)
} else {
imapw.worker.Logger.Printf("Found %d UIDs", len(uids))
+ imapw.seqMap = make([]uint32, len(uids))
imapw.worker.PostMessage(&types.DirectoryContents{
Message: types.RespondTo(msg),
Uids: uids,
diff --git a/worker/imap/worker.go b/worker/imap/worker.go
index 16461651..ea7f317a 100644
--- a/worker/imap/worker.go
+++ b/worker/imap/worker.go
@@ -33,12 +33,14 @@ type IMAPWorker struct {
selected imap.MailboxStatus
updates chan client.Update
worker *types.Worker
+ // Map of sequence numbers to UIDs, index 0 is seq number 1
+ seqMap []uint32
}
func NewIMAPWorker(worker *types.Worker) *IMAPWorker {
return &IMAPWorker{
- worker: worker,
updates: make(chan client.Update, 50),
+ worker: worker,
}
}
@@ -156,6 +158,8 @@ func (w *IMAPWorker) handleMessage(msg types.WorkerMessage) error {
w.handleFetchDirectoryContents(msg)
case *types.FetchMessageHeaders:
w.handleFetchMessageHeaders(msg)
+ case *types.DeleteMessages:
+ w.handleDeleteMessages(msg)
default:
return errUnsupported
}
diff --git a/worker/types/messages.go b/worker/types/messages.go
index 3f1a39f7..ff2c36bb 100644
--- a/worker/types/messages.go
+++ b/worker/types/messages.go
@@ -86,6 +86,11 @@ type FetchMessageBodies struct {
Uids imap.SeqSet
}
+type DeleteMessages struct {
+ Message
+ Uids imap.SeqSet
+}
+
// Messages
type CertificateApprovalRequest struct {
@@ -122,3 +127,8 @@ type MessageInfo struct {
Size uint32
Uid uint32
}
+
+type MessagesDeleted struct {
+ Message
+ Uids []uint32
+}