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>
This commit is contained in:
parent
c26d08103b
commit
115dabb634
2 changed files with 107 additions and 11 deletions
|
@ -5,6 +5,7 @@ import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"os/exec"
|
"os/exec"
|
||||||
|
"sort"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"git.sr.ht/~rjarry/aerc/commands"
|
"git.sr.ht/~rjarry/aerc/commands"
|
||||||
|
@ -108,22 +109,73 @@ func (Pipe) Execute(aerc *widgets.Aerc, args []string) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
if pipeFull {
|
if pipeFull {
|
||||||
store := provider.Store()
|
var uids []uint32
|
||||||
if store == nil {
|
var title string
|
||||||
return errors.New("Cannot perform action. Messages still loading")
|
|
||||||
}
|
h := newHelper(aerc)
|
||||||
msg, err := provider.SelectedMessage()
|
store, err := h.store()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
store.FetchFull([]uint32{msg.Uid}, func(fm *types.FullMessage) {
|
uids, err = h.markedOrSelectedUids()
|
||||||
if background {
|
if err != nil {
|
||||||
doExec(fm.Content.Reader)
|
return err
|
||||||
} else {
|
}
|
||||||
doTerm(fm.Content.Reader, fmt.Sprintf(
|
|
||||||
"%s <%s", cmd[0], msg.Envelope.Subject))
|
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(reader)
|
||||||
|
} else {
|
||||||
|
doTerm(reader, fmt.Sprintf("%s <%s", cmd[0], title))
|
||||||
|
}
|
||||||
|
}()
|
||||||
} else if pipePart {
|
} else if pipePart {
|
||||||
p := provider.SelectedMessagePart()
|
p := provider.SelectedMessagePart()
|
||||||
if p == nil {
|
if p == nil {
|
||||||
|
@ -143,3 +195,44 @@ func (Pipe) Execute(aerc *widgets.Aerc, args []string) error {
|
||||||
|
|
||||||
return nil
|
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
|
||||||
|
}
|
||||||
|
|
|
@ -146,6 +146,9 @@ message list, the message in the message viewer, etc).
|
||||||
message part is used in the message viewer and the full message is used in
|
message part is used in the message viewer and the full message is used in
|
||||||
the message list.
|
the message list.
|
||||||
|
|
||||||
|
Operates on multiple messages when they are marked. When piping multiple
|
||||||
|
messages, aerc will write them with mbox format separators.
|
||||||
|
|
||||||
*-b*: Run the command in the background instead of opening a terminal tab
|
*-b*: Run the command in the background instead of opening a terminal tab
|
||||||
|
|
||||||
*-m*: Pipe the full message
|
*-m*: Pipe the full message
|
||||||
|
|
Loading…
Reference in a new issue