aboutsummaryrefslogblamecommitdiffstats
path: root/commands/msg/recall.go
blob: 8a70a0a1c1ac47382bed593d24073796141266ec (plain) (tree)
1
2
3
4
5
6
7
8


           
             
            

                   
              





                                                  



                                             
                                    
















                                                                    














                                                       






                                                              
                                                                                

                                                                        











                                                                                  
                                                                       

                                                                               
























                                                                             



                                                                                      
































                                                                                                           









                                                           
                                                                     
         

                                                           
                                            

         
                                                                       






                                                                       
                                                   





                                                 
                                                   




                                               
























                                                                                                                  



                  
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/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 {
		switch opt.Option {
		case 'f':
			force = true
		}
	}

	if len(args) != optind {
		return errors.New("Usage: recall [-f]")
	}

	widget := aerc.SelectedTab().(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")
	}
	acct.Logger().Println("Recalling message " + 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:
						}
						return
					},
				)
				aerc.AddDialog(confirm)
			}

		})
	}

	// find the main body part and add it to the editor
	// TODO: copy all parts of the message over?
	var (
		path []int
		part *models.BodyStructure
	)
	if len(msgInfo.BodyStructure.Parts) != 0 {
		path = lib.FindPlaintext(msgInfo.BodyStructure, path)
	}
	part, err = msgInfo.BodyStructure.PartAtIndex(path)
	if part == nil || err != nil {
		part = msgInfo.BodyStructure
	}

	store.FetchBodyPart(msgInfo.Uid, 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)
		addTab()

		// add attachements if present
		var mu sync.Mutex
		parts := lib.FindAllNonMultipart(msgInfo.BodyStructure, nil, nil)
		for _, p := range parts {
			if lib.EqualParts(p, path) {
				continue
			}
			bs, err := msgInfo.BodyStructure.PartAtIndex(p)
			if err != nil {
				acct.Logger().Println("recall: PartAtIndex:", err)
				continue
			}
			store.FetchBodyPart(msgInfo.Uid, 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
}