diff options
author | Robin Jarry <robin@jarry.cc> | 2022-02-24 21:10:30 +0100 |
---|---|---|
committer | Robin Jarry <robin@jarry.cc> | 2022-02-25 13:56:53 +0100 |
commit | 115dabb6346383c88525586c2ec75d60df6f25d3 (patch) | |
tree | 9a1aca40ef3ce61ff3c9173ff92ef6cca45938d0 /commands/msg/pipe.go | |
parent | c26d08103b327bd3f2470e542aa55ab354483347 (diff) | |
download | aerc-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.go | 111 |
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 +} |