about summary refs log tree commit diff stats
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 0000000..be56dbb
--- /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 d745093..6830f64 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 8a3b989..f42ff6c 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 ab25847..ac941c8 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 383a8a8..489dbe4 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 0000000..cb9b3b1
--- /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 87c4fb3..3705bc0 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 1646165..ea7f317 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 3f1a39f..ff2c36b 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
+}