aboutsummaryrefslogtreecommitdiffstats
path: root/commands/msg/pipe.go
diff options
context:
space:
mode:
authorRobin Jarry <robin@jarry.cc>2022-02-24 21:10:30 +0100
committerRobin Jarry <robin@jarry.cc>2022-02-25 13:56:53 +0100
commit115dabb6346383c88525586c2ec75d60df6f25d3 (patch)
tree9a1aca40ef3ce61ff3c9173ff92ef6cca45938d0 /commands/msg/pipe.go
parentc26d08103b327bd3f2470e542aa55ab354483347 (diff)
downloadaerc-115dabb6346383c88525586c2ec75d60df6f25d3.tar.gz
pipe: allow piping multiple marked messages
When messages are marked, pipe their contents into the specified command. The messages are ordered according to their respective Message-Id headers. This allows applying complete patch series with a single command. When piping more than one message, make sure to write them in the mbox format as git am expects them to be. Link: https://en.wikipedia.org/wiki/Mbox Link: https://github.com/git/git/blob/v2.35.1/builtin/mailsplit.c#L15-L44 Signed-off-by: Robin Jarry <robin@jarry.cc> Reviewed-by: Koni Marti <koni.marti@gmail.com> Tested-by: akspecs <akspecs@gmail.com>
Diffstat (limited to 'commands/msg/pipe.go')
-rw-r--r--commands/msg/pipe.go111
1 files changed, 102 insertions, 9 deletions
diff --git a/commands/msg/pipe.go b/commands/msg/pipe.go
index 58764fbf..5d8a0424 100644
--- a/commands/msg/pipe.go
+++ b/commands/msg/pipe.go
@@ -5,6 +5,7 @@ import (
"fmt"
"io"
"os/exec"
+ "sort"
"time"
"git.sr.ht/~rjarry/aerc/commands"
@@ -108,22 +109,73 @@ func (Pipe) Execute(aerc *widgets.Aerc, args []string) error {
}
if pipeFull {
- store := provider.Store()
- if store == nil {
- return errors.New("Cannot perform action. Messages still loading")
+ var uids []uint32
+ var title string
+
+ h := newHelper(aerc)
+ store, err := h.store()
+ if err != nil {
+ return err
}
- msg, err := provider.SelectedMessage()
+ uids, err = h.markedOrSelectedUids()
if err != nil {
return err
}
- store.FetchFull([]uint32{msg.Uid}, func(fm *types.FullMessage) {
+
+ if len(uids) == 1 {
+ info := store.Messages[uids[0]]
+ if info != nil {
+ envelope := info.Envelope
+ if envelope != nil {
+ title = envelope.Subject
+ }
+ }
+ }
+ if title == "" {
+ title = fmt.Sprintf("%d messages", len(uids))
+ }
+
+ var messages []*types.FullMessage
+ done := make(chan bool, 1)
+
+ store.FetchFull(uids, func(fm *types.FullMessage) {
+ messages = append(messages, fm)
+ if len(messages) == len(uids) {
+ done <- true
+ }
+ })
+
+ go func() {
+ select {
+ case <-done:
+ break
+ case <-time.After(30 * time.Second):
+ // TODO: find a better way to determine if store.FetchFull()
+ // has finished with some errors.
+ aerc.PushError("Failed to fetch all messages")
+ if len(messages) == 0 {
+ return
+ }
+ }
+
+ // Sort all messages by increasing Message-Id header.
+ // This will ensure that patch series are applied in order.
+ sort.Slice(messages, func(i, j int) bool {
+ infoi := store.Messages[messages[i].Content.Uid]
+ infoj := store.Messages[messages[j].Content.Uid]
+ if infoi == nil || infoj == nil {
+ return false
+ }
+ return infoi.Envelope.MessageId < infoj.Envelope.MessageId
+ })
+
+ reader := newMessagesReader(messages)
if background {
- doExec(fm.Content.Reader)
+ doExec(reader)
} else {
- doTerm(fm.Content.Reader, fmt.Sprintf(
- "%s <%s", cmd[0], msg.Envelope.Subject))
+ doTerm(reader, fmt.Sprintf("%s <%s", cmd[0], title))
}
- })
+ }()
} else if pipePart {
p := provider.SelectedMessagePart()
if p == nil {
@@ -143,3 +195,44 @@ func (Pipe) Execute(aerc *widgets.Aerc, args []string) error {
return nil
}
+
+// The actual sender address does not matter, nor does the date. This is mostly indended
+// for git am which requires separators to look like something valid.
+// https://github.com/git/git/blame/v2.35.1/builtin/mailsplit.c#L15-L44
+var mboxSeparator []byte = []byte("From ???@??? Tue Jun 23 16:32:49 1981\n")
+
+type messagesReader struct {
+ messages []*types.FullMessage
+ mbox bool
+ separatorNeeded bool
+}
+
+func newMessagesReader(messages []*types.FullMessage) io.Reader {
+ needMboxSeparator := len(messages) > 1
+ return &messagesReader{messages, needMboxSeparator, needMboxSeparator}
+}
+
+func (mr *messagesReader) Read(p []byte) (n int, err error) {
+ for len(mr.messages) > 0 {
+ if mr.separatorNeeded {
+ offset := copy(p, mboxSeparator)
+ n, err = mr.messages[0].Content.Reader.Read(p[offset:])
+ n += offset
+ mr.separatorNeeded = false
+ } else {
+ n, err = mr.messages[0].Content.Reader.Read(p)
+ }
+ if err == io.EOF {
+ mr.messages = mr.messages[1:]
+ mr.separatorNeeded = mr.mbox
+ }
+ if n > 0 || err != io.EOF {
+ if err == io.EOF && len(mr.messages) > 0 {
+ // Don't return EOF yet. More messages remain.
+ err = nil
+ }
+ return n, err
+ }
+ }
+ return 0, io.EOF
+}