e4d418eed1
Add option to open a message in the message viewer without setting the seen flag. Enables the message viewer to be used as a preview pane without changing the message flags unintentionally. Before, the message viewer would set the seen flag by default. The IMAP backend will now always fetch the message body with the peek option enabled (same as we fetch the headers). An "auto-mark-read" option is added to the ui config which is set to true by default. If set the false, the seen flag is not set by the message viewer. Co-authored-by: "James Cook" <falsifian@falsifian.org> Signed-off-by: Koni Marti <koni.marti@gmail.com> Acked-by: Robin Jarry <robin@jarry.cc>
220 lines
5.3 KiB
Go
220 lines
5.3 KiB
Go
package msg
|
|
|
|
import (
|
|
"fmt"
|
|
"io"
|
|
"math/rand"
|
|
"sync"
|
|
"time"
|
|
|
|
"github.com/emersion/go-message"
|
|
_ "github.com/emersion/go-message/charset"
|
|
"github.com/emersion/go-message/mail"
|
|
"github.com/pkg/errors"
|
|
|
|
"git.sr.ht/~rjarry/aerc/lib"
|
|
"git.sr.ht/~rjarry/aerc/logging"
|
|
"git.sr.ht/~rjarry/aerc/models"
|
|
"git.sr.ht/~rjarry/aerc/widgets"
|
|
"git.sr.ht/~rjarry/aerc/worker/types"
|
|
"git.sr.ht/~sircmpwn/getopt"
|
|
)
|
|
|
|
type Recall struct{}
|
|
|
|
func init() {
|
|
register(Recall{})
|
|
}
|
|
|
|
func (Recall) Aliases() []string {
|
|
return []string{"recall"}
|
|
}
|
|
|
|
func (Recall) Complete(aerc *widgets.Aerc, args []string) []string {
|
|
return nil
|
|
}
|
|
|
|
func (Recall) Execute(aerc *widgets.Aerc, args []string) error {
|
|
force := false
|
|
|
|
opts, optind, err := getopt.Getopts(args, "f")
|
|
if err != nil {
|
|
return err
|
|
}
|
|
for _, opt := range opts {
|
|
if opt.Option == 'f' {
|
|
force = true
|
|
}
|
|
}
|
|
|
|
if len(args) != optind {
|
|
return errors.New("Usage: recall [-f]")
|
|
}
|
|
|
|
widget := aerc.SelectedTabContent().(widgets.ProvidesMessage)
|
|
acct := widget.SelectedAccount()
|
|
if acct == nil {
|
|
return errors.New("No account selected")
|
|
}
|
|
if acct.SelectedDirectory() != acct.AccountConfig().Postpone && !force {
|
|
return errors.New("Use -f to recall from outside the " +
|
|
acct.AccountConfig().Postpone + " directory.")
|
|
}
|
|
store := widget.Store()
|
|
if store == nil {
|
|
return errors.New("Cannot perform action. Messages still loading")
|
|
}
|
|
|
|
msgInfo, err := widget.SelectedMessage()
|
|
if err != nil {
|
|
return errors.Wrap(err, "Recall failed")
|
|
}
|
|
logging.Infof("Recalling message %s", msgInfo.Envelope.MessageId)
|
|
|
|
composer, err := widgets.NewComposer(aerc, acct, aerc.Config(),
|
|
acct.AccountConfig(), acct.Worker(), "", msgInfo.RFC822Headers,
|
|
models.OriginalMail{})
|
|
if err != nil {
|
|
return errors.Wrap(err, "Cannot open a new composer")
|
|
}
|
|
|
|
// focus the terminal since the header fields are likely already done
|
|
composer.FocusTerminal()
|
|
|
|
addTab := func() {
|
|
subject := msgInfo.Envelope.Subject
|
|
if subject == "" {
|
|
subject = "Recalled email"
|
|
}
|
|
tab := aerc.NewTab(composer, subject)
|
|
composer.OnHeaderChange("Subject", func(subject string) {
|
|
if subject == "" {
|
|
tab.Name = "New email"
|
|
} else {
|
|
tab.Name = subject
|
|
}
|
|
tab.Content.Invalidate()
|
|
})
|
|
composer.OnClose(func(composer *widgets.Composer) {
|
|
worker := composer.Worker()
|
|
uids := []uint32{msgInfo.Uid}
|
|
|
|
if acct.SelectedDirectory() != acct.AccountConfig().Postpone {
|
|
return
|
|
}
|
|
|
|
deleteMessage := func() {
|
|
worker.PostAction(&types.DeleteMessages{
|
|
Uids: uids,
|
|
}, func(msg types.WorkerMessage) {
|
|
switch msg := msg.(type) {
|
|
case *types.Done:
|
|
aerc.PushStatus("Recalled message deleted", 10*time.Second)
|
|
case *types.Error:
|
|
aerc.PushError(msg.Error.Error())
|
|
}
|
|
})
|
|
}
|
|
|
|
if composer.Sent() {
|
|
deleteMessage()
|
|
} else {
|
|
confirm := widgets.NewSelectorDialog(
|
|
"Delete recalled message?",
|
|
"If you proceed, the recalled message will be deleted.",
|
|
[]string{"Cancel", "Proceed"}, 0, aerc.SelectedAccountUiConfig(),
|
|
func(option string, err error) {
|
|
aerc.CloseDialog()
|
|
switch option {
|
|
case "Proceed":
|
|
deleteMessage()
|
|
default:
|
|
}
|
|
},
|
|
)
|
|
aerc.AddDialog(confirm)
|
|
}
|
|
})
|
|
}
|
|
|
|
lib.NewMessageStoreView(msgInfo, acct.UiConfig().AutoMarkRead,
|
|
store, aerc.Crypto, aerc.DecryptKeys,
|
|
func(msg lib.MessageView, err error) {
|
|
if err != nil {
|
|
aerc.PushError(err.Error())
|
|
return
|
|
}
|
|
|
|
var (
|
|
path []int
|
|
part *models.BodyStructure
|
|
)
|
|
if len(msg.BodyStructure().Parts) != 0 {
|
|
path = lib.FindPlaintext(msg.BodyStructure(), path)
|
|
}
|
|
part, err = msg.BodyStructure().PartAtIndex(path)
|
|
if part == nil || err != nil {
|
|
part = msg.BodyStructure()
|
|
}
|
|
|
|
msg.FetchBodyPart(path, func(reader io.Reader) {
|
|
header := message.Header{}
|
|
header.SetText(
|
|
"Content-Transfer-Encoding", part.Encoding)
|
|
header.SetContentType(part.MIMEType, part.Params)
|
|
header.SetText("Content-Description", part.Description)
|
|
entity, err := message.New(header, reader)
|
|
if err != nil {
|
|
aerc.PushError(err.Error())
|
|
addTab()
|
|
return
|
|
}
|
|
mreader := mail.NewReader(entity)
|
|
part, err := mreader.NextPart()
|
|
if err != nil {
|
|
aerc.PushError(err.Error())
|
|
addTab()
|
|
return
|
|
}
|
|
composer.SetContents(part.Body)
|
|
if md := msg.MessageDetails(); md != nil {
|
|
if md.IsEncrypted {
|
|
composer.SetEncrypt(md.IsEncrypted)
|
|
}
|
|
if md.IsSigned {
|
|
err = composer.SetSign(md.IsSigned)
|
|
if err != nil {
|
|
logging.Warnf("failed to set signed state: %v", err)
|
|
}
|
|
}
|
|
}
|
|
addTab()
|
|
|
|
// add attachements if present
|
|
var mu sync.Mutex
|
|
parts := lib.FindAllNonMultipart(msg.BodyStructure(), nil, nil)
|
|
for _, p := range parts {
|
|
if lib.EqualParts(p, path) {
|
|
continue
|
|
}
|
|
bs, err := msg.BodyStructure().PartAtIndex(p)
|
|
if err != nil {
|
|
logging.Infof("cannot get PartAtIndex %v: %v", p, err)
|
|
continue
|
|
}
|
|
msg.FetchBodyPart(p, func(reader io.Reader) {
|
|
mime := fmt.Sprintf("%s/%s", bs.MIMEType, bs.MIMESubType)
|
|
name, ok := bs.Params["name"]
|
|
if !ok {
|
|
name = fmt.Sprintf("%s_%s_%d", bs.MIMEType, bs.MIMESubType, rand.Uint64())
|
|
}
|
|
mu.Lock()
|
|
composer.AddPartAttachment(name, mime, bs.Params, reader)
|
|
mu.Unlock()
|
|
})
|
|
}
|
|
})
|
|
})
|
|
|
|
return nil
|
|
}
|