2020-04-24 11:42:22 +02:00
|
|
|
package msg
|
|
|
|
|
|
|
|
import (
|
2022-07-05 21:42:44 +02:00
|
|
|
"fmt"
|
2020-04-24 11:42:22 +02:00
|
|
|
"io"
|
2022-07-05 21:42:44 +02:00
|
|
|
"math/rand"
|
|
|
|
"sync"
|
2022-07-05 21:42:43 +02:00
|
|
|
"time"
|
2020-04-24 11:42:22 +02:00
|
|
|
|
|
|
|
"github.com/emersion/go-message"
|
|
|
|
_ "github.com/emersion/go-message/charset"
|
|
|
|
"github.com/emersion/go-message/mail"
|
|
|
|
"github.com/pkg/errors"
|
|
|
|
|
2021-11-05 10:19:46 +01:00
|
|
|
"git.sr.ht/~rjarry/aerc/lib"
|
2022-07-19 22:31:51 +02:00
|
|
|
"git.sr.ht/~rjarry/aerc/logging"
|
2021-11-05 10:19:46 +01:00
|
|
|
"git.sr.ht/~rjarry/aerc/models"
|
|
|
|
"git.sr.ht/~rjarry/aerc/widgets"
|
|
|
|
"git.sr.ht/~rjarry/aerc/worker/types"
|
recall: allow recalling messages from any folder
Recall fails when called outside of the "postpone" folder (usually
"Drafts"). This makes sense for postponed messages. However, sometimes
the user would like to re-edit and re-send an old, possibly sent,
message, which would serve as a basis for the new one.
This patch allows recall to work outside the postpone folder, thus
allowing for re-edition of any message.
In the original recall function, if the recalled message is found in the
"postpone" folder, once the message has been recalled, re-edited and
sent, the original draft is deleted. With this patch, when the message
is not in the "postpone" folder, the original message is not deleted.
Signed-off-by: inwit <inwit@sindominio.net>
2021-12-13 11:28:01 +01:00
|
|
|
"git.sr.ht/~sircmpwn/getopt"
|
2020-04-24 11:42:22 +02:00
|
|
|
)
|
|
|
|
|
|
|
|
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 {
|
recall: allow recalling messages from any folder
Recall fails when called outside of the "postpone" folder (usually
"Drafts"). This makes sense for postponed messages. However, sometimes
the user would like to re-edit and re-send an old, possibly sent,
message, which would serve as a basis for the new one.
This patch allows recall to work outside the postpone folder, thus
allowing for re-edition of any message.
In the original recall function, if the recalled message is found in the
"postpone" folder, once the message has been recalled, re-edited and
sent, the original draft is deleted. With this patch, when the message
is not in the "postpone" folder, the original message is not deleted.
Signed-off-by: inwit <inwit@sindominio.net>
2021-12-13 11:28:01 +01:00
|
|
|
force := false
|
|
|
|
|
|
|
|
opts, optind, err := getopt.Getopts(args, "f")
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
for _, opt := range opts {
|
2022-07-31 14:32:48 +02:00
|
|
|
if opt.Option == 'f' {
|
recall: allow recalling messages from any folder
Recall fails when called outside of the "postpone" folder (usually
"Drafts"). This makes sense for postponed messages. However, sometimes
the user would like to re-edit and re-send an old, possibly sent,
message, which would serve as a basis for the new one.
This patch allows recall to work outside the postpone folder, thus
allowing for re-edition of any message.
In the original recall function, if the recalled message is found in the
"postpone" folder, once the message has been recalled, re-edited and
sent, the original draft is deleted. With this patch, when the message
is not in the "postpone" folder, the original message is not deleted.
Signed-off-by: inwit <inwit@sindominio.net>
2021-12-13 11:28:01 +01:00
|
|
|
force = true
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if len(args) != optind {
|
|
|
|
return errors.New("Usage: recall [-f]")
|
2020-04-24 11:42:22 +02:00
|
|
|
}
|
|
|
|
|
2022-07-18 12:54:55 +02:00
|
|
|
widget := aerc.SelectedTabContent().(widgets.ProvidesMessage)
|
2020-04-24 11:42:22 +02:00
|
|
|
acct := widget.SelectedAccount()
|
|
|
|
if acct == nil {
|
|
|
|
return errors.New("No account selected")
|
|
|
|
}
|
recall: allow recalling messages from any folder
Recall fails when called outside of the "postpone" folder (usually
"Drafts"). This makes sense for postponed messages. However, sometimes
the user would like to re-edit and re-send an old, possibly sent,
message, which would serve as a basis for the new one.
This patch allows recall to work outside the postpone folder, thus
allowing for re-edition of any message.
In the original recall function, if the recalled message is found in the
"postpone" folder, once the message has been recalled, re-edited and
sent, the original draft is deleted. With this patch, when the message
is not in the "postpone" folder, the original message is not deleted.
Signed-off-by: inwit <inwit@sindominio.net>
2021-12-13 11:28:01 +01:00
|
|
|
if acct.SelectedDirectory() != acct.AccountConfig().Postpone && !force {
|
2022-04-29 14:05:01 +02:00
|
|
|
return errors.New("Use -f to recall from outside the " +
|
|
|
|
acct.AccountConfig().Postpone + " directory.")
|
2020-04-24 11:42:22 +02:00
|
|
|
}
|
|
|
|
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")
|
|
|
|
}
|
2022-07-19 22:31:51 +02:00
|
|
|
logging.Infof("Recalling message %s", msgInfo.Envelope.MessageId)
|
2020-04-24 11:42:22 +02:00
|
|
|
|
|
|
|
composer, err := widgets.NewComposer(aerc, acct, aerc.Config(),
|
2020-11-10 20:27:30 +01:00
|
|
|
acct.AccountConfig(), acct.Worker(), "", msgInfo.RFC822Headers,
|
|
|
|
models.OriginalMail{})
|
2020-04-24 11:42:22 +02:00
|
|
|
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}
|
|
|
|
|
recall: allow recalling messages from any folder
Recall fails when called outside of the "postpone" folder (usually
"Drafts"). This makes sense for postponed messages. However, sometimes
the user would like to re-edit and re-send an old, possibly sent,
message, which would serve as a basis for the new one.
This patch allows recall to work outside the postpone folder, thus
allowing for re-edition of any message.
In the original recall function, if the recalled message is found in the
"postpone" folder, once the message has been recalled, re-edited and
sent, the original draft is deleted. With this patch, when the message
is not in the "postpone" folder, the original message is not deleted.
Signed-off-by: inwit <inwit@sindominio.net>
2021-12-13 11:28:01 +01:00
|
|
|
if acct.SelectedDirectory() != acct.AccountConfig().Postpone {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2022-07-05 21:42:43 +02:00
|
|
|
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)
|
|
|
|
}
|
2020-04-24 11:42:22 +02:00
|
|
|
})
|
|
|
|
}
|
|
|
|
|
2022-10-03 23:56:06 +02:00
|
|
|
lib.NewMessageStoreView(msgInfo, acct.UiConfig().AutoMarkRead,
|
|
|
|
store, aerc.Crypto, aerc.DecryptKeys,
|
2022-07-05 21:42:45 +02:00
|
|
|
func(msg lib.MessageView, err error) {
|
|
|
|
if err != nil {
|
|
|
|
aerc.PushError(err.Error())
|
|
|
|
return
|
|
|
|
}
|
2020-04-24 11:42:22 +02:00
|
|
|
|
2022-07-05 21:42:45 +02:00
|
|
|
var (
|
|
|
|
path []int
|
|
|
|
part *models.BodyStructure
|
|
|
|
)
|
|
|
|
if len(msg.BodyStructure().Parts) != 0 {
|
|
|
|
path = lib.FindPlaintext(msg.BodyStructure(), path)
|
2022-07-05 21:42:44 +02:00
|
|
|
}
|
2022-07-05 21:42:45 +02:00
|
|
|
part, err = msg.BodyStructure().PartAtIndex(path)
|
|
|
|
if part == nil || err != nil {
|
|
|
|
part = msg.BodyStructure()
|
2022-07-05 21:42:44 +02:00
|
|
|
}
|
2022-07-05 21:42:45 +02:00
|
|
|
|
|
|
|
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
|
2022-07-05 21:42:44 +02:00
|
|
|
}
|
2022-07-05 21:42:45 +02:00
|
|
|
composer.SetContents(part.Body)
|
|
|
|
if md := msg.MessageDetails(); md != nil {
|
|
|
|
if md.IsEncrypted {
|
|
|
|
composer.SetEncrypt(md.IsEncrypted)
|
|
|
|
}
|
|
|
|
if md.IsSigned {
|
2022-07-29 22:31:54 +02:00
|
|
|
err = composer.SetSign(md.IsSigned)
|
|
|
|
if err != nil {
|
|
|
|
logging.Warnf("failed to set signed state: %v", err)
|
|
|
|
}
|
2022-07-05 21:42:45 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
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 {
|
2022-07-19 22:31:51 +02:00
|
|
|
logging.Infof("cannot get PartAtIndex %v: %v", p, err)
|
2022-07-05 21:42:45 +02:00
|
|
|
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()
|
|
|
|
})
|
|
|
|
}
|
2022-07-05 21:42:44 +02:00
|
|
|
})
|
2022-07-05 21:42:45 +02:00
|
|
|
})
|
2020-04-24 11:42:22 +02:00
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|