about summary refs log tree commit diff stats
diff options
context:
space:
mode:
authorJeffas <dev@jeffas.io>2020-04-24 11:42:21 +0200
committerDrew DeVault <sir@cmpwn.com>2020-04-24 12:59:21 -0400
commit7f033278eb3afc3b9ae2dca28efe8d4a3514d14a (patch)
treeee02c27cb1dfadd54be0178e91d6f2d5f0cbf4dc
parent447e662057c663f47f5c8a490543b1a52b26bc86 (diff)
downloadaerc-7f033278eb3afc3b9ae2dca28efe8d4a3514d14a.tar.gz
Add postpone command
This command uses the Postpone folder from the account config to save
messages to. Messages are saved as though they were sent so have a valid
'to' recipient address and should be able to be read back in for later
editing.
-rw-r--r--commands/account/compose.go2
-rw-r--r--commands/compose/postpone.go119
-rw-r--r--commands/msg/forward.go1
-rw-r--r--commands/msg/reply.go2
-rw-r--r--commands/msg/unsubscribe.go1
-rw-r--r--config/binds.conf1
-rw-r--r--config/config.go10
-rw-r--r--doc/aerc-config.5.scd5
-rw-r--r--doc/aerc.1.scd4
-rw-r--r--widgets/aerc.go4
-rw-r--r--widgets/compose.go44
11 files changed, 166 insertions, 27 deletions
diff --git a/commands/account/compose.go b/commands/account/compose.go
index 8115faa..42d51d5 100644
--- a/commands/account/compose.go
+++ b/commands/account/compose.go
@@ -31,7 +31,7 @@ func (Compose) Execute(aerc *widgets.Aerc, args []string) error {
 	}
 	acct := aerc.SelectedAccount()
 
-	composer, err := widgets.NewComposer(aerc,
+	composer, err := widgets.NewComposer(aerc, acct
 		aerc.Config(), acct.AccountConfig(), acct.Worker(),
 		template, nil, models.OriginalMail{})
 	if err != nil {
diff --git a/commands/compose/postpone.go b/commands/compose/postpone.go
new file mode 100644
index 0000000..44aa411
--- /dev/null
+++ b/commands/compose/postpone.go
@@ -0,0 +1,119 @@
+package compose
+
+import (
+	"io"
+	"io/ioutil"
+	"time"
+
+	"github.com/emersion/go-imap"
+	"github.com/miolini/datacounter"
+	"github.com/pkg/errors"
+
+	"git.sr.ht/~sircmpwn/aerc/widgets"
+	"git.sr.ht/~sircmpwn/aerc/worker/types"
+)
+
+type Postpone struct{}
+
+func init() {
+	register(Postpone{})
+}
+
+func (Postpone) Aliases() []string {
+	return []string{"postpone"}
+}
+
+func (Postpone) Complete(aerc *widgets.Aerc, args []string) []string {
+	return nil
+}
+
+func (Postpone) Execute(aerc *widgets.Aerc, args []string) error {
+	if len(args) != 1 {
+		return errors.New("Usage: postpone")
+	}
+	composer, _ := aerc.SelectedTab().(*widgets.Composer)
+	config := composer.Config()
+
+	if config.Postpone == "" {
+		return errors.New("No Postpone location configured")
+	}
+
+	aerc.Logger().Println("Postponing mail")
+
+	header, _, err := composer.PrepareHeader()
+	if err != nil {
+		return errors.Wrap(err, "PrepareHeader")
+	}
+	header.SetContentType("text/plain", map[string]string{"charset": "UTF-8"})
+	header.Set("Content-Transfer-Encoding", "quoted-printable")
+	worker := composer.Worker()
+	dirs := aerc.SelectedAccount().Directories().List()
+	alreadyCreated := false
+	for _, dir := range dirs {
+		if dir == config.Postpone {
+			alreadyCreated = true
+			break
+		}
+	}
+
+	errChan := make(chan string)
+
+	// run this as a goroutine so we can make other progress. The message
+	// will be saved once the directory is created.
+	go func() {
+		errStr := <-errChan
+		if errStr != "" {
+			aerc.PushError(" " + errStr)
+			return
+		}
+
+		aerc.RemoveTab(composer)
+		ctr := datacounter.NewWriterCounter(ioutil.Discard)
+		err = composer.WriteMessage(header, ctr)
+		if err != nil {
+			aerc.PushError(errors.Wrap(err, "WriteMessage").Error())
+			composer.Close()
+			return
+		}
+		nbytes := int(ctr.Count())
+		r, w := io.Pipe()
+		worker.PostAction(&types.AppendMessage{
+			Destination: config.Postpone,
+			Flags:       []string{imap.SeenFlag},
+			Date:        time.Now(),
+			Reader:      r,
+			Length:      int(nbytes),
+		}, func(msg types.WorkerMessage) {
+			switch msg := msg.(type) {
+			case *types.Done:
+				aerc.PushStatus("Message postponed.", 10*time.Second)
+				r.Close()
+				composer.Close()
+			case *types.Error:
+				aerc.PushError(" " + msg.Error.Error())
+				r.Close()
+				composer.Close()
+			}
+		})
+		composer.WriteMessage(header, w)
+		w.Close()
+	}()
+
+	if !alreadyCreated {
+		// to synchronise the creating of the directory
+		worker.PostAction(&types.CreateDirectory{
+			Directory: config.Postpone,
+		}, func(msg types.WorkerMessage) {
+			switch msg := msg.(type) {
+			case *types.Done:
+				errChan <- ""
+			case *types.Error:
+				errChan <- msg.Error.Error()
+			}
+		})
+	} else {
+		errChan <- ""
+	}
+
+	return nil
+}
diff --git a/commands/msg/forward.go b/commands/msg/forward.go
index c51949e..833eb9f 100644
--- a/commands/msg/forward.go
+++ b/commands/msg/forward.go
@@ -70,7 +70,6 @@ func (forward) Execute(aerc *widgets.Aerc, args []string) error {
 		"To":      to,
 		"Subject": subject,
 	}
-
 	original := models.OriginalMail{}
 
 	addTab := func() (*widgets.Composer, error) {
diff --git a/commands/msg/reply.go b/commands/msg/reply.go
index c5ae1b6..291fc4b 100644
--- a/commands/msg/reply.go
+++ b/commands/msg/reply.go
@@ -124,7 +124,7 @@ func (reply) Execute(aerc *widgets.Aerc, args []string) error {
 			original.Date = msg.Envelope.Date.Format("Mon Jan 2, 2006 at 3:04 PM")
 		}
 
-		composer, err := widgets.NewComposer(aerc, aerc.Config(),
+		composer, err := widgets.NewComposer(aerc, acct, aerc.Config(),
 			acct.AccountConfig(), acct.Worker(), template, defaults, original)
 		if err != nil {
 			aerc.PushError("Error: " + err.Error())
diff --git a/commands/msg/unsubscribe.go b/commands/msg/unsubscribe.go
index 1bf3a83..1a2dd37 100644
--- a/commands/msg/unsubscribe.go
+++ b/commands/msg/unsubscribe.go
@@ -90,6 +90,7 @@ func unsubscribeMailto(aerc *widgets.Aerc, u *url.URL) error {
 	}
 	composer, err := widgets.NewComposer(
 		aerc,
+		acct,
 		aerc.Config(),
 		acct.AccountConfig(),
 		acct.Worker(),
diff --git a/config/binds.conf b/config/binds.conf
index 5887203..abfd77e 100644
--- a/config/binds.conf
+++ b/config/binds.conf
@@ -90,6 +90,7 @@ $ex = <C-x>
 # Keybindings used when reviewing a message to be sent
 y = :send<Enter>
 n = :abort<Enter>
+p = :postpone<Enter>
 q = :abort<Enter>
 e = :edit<Enter>
 a = :attach<space>
diff --git a/config/config.go b/config/config.go
index 5794388..e00518c 100644
--- a/config/config.go
+++ b/config/config.go
@@ -70,6 +70,7 @@ type AccountConfig struct {
 	Archive         string
 	CopyTo          string
 	Default         string
+	Postpone        string
 	From            string
 	Name            string
 	Source          string
@@ -171,10 +172,11 @@ func loadAccountConfig(path string) ([]AccountConfig, error) {
 		}
 		sec := file.Section(_sec)
 		account := AccountConfig{
-			Archive: "Archive",
-			Default: "INBOX",
-			Name:    _sec,
-			Params:  make(map[string]string),
+			Archive:  "Archive",
+			Default:  "INBOX",
+			Postpone: "Drafts",
+			Name:     _sec,
+			Params:   make(map[string]string),
 		}
 		if err = sec.MapTo(&account); err != nil {
 			return nil, err
diff --git a/doc/aerc-config.5.scd b/doc/aerc-config.5.scd
index 36ac9c6..c309f2f 100644
--- a/doc/aerc-config.5.scd
+++ b/doc/aerc-config.5.scd
@@ -402,6 +402,11 @@ Note that many of these configuration options are written for you, such as
 
 	Default: none
 
+*postpone*
+	Specifies the folder to save *postpone*d messages to.
+
+	Default: Drafts
+
 *source*
 	Specifies the source for reading incoming emails on this account. This key
 	is required for all accounts. It should be a connection string, and the
diff --git a/doc/aerc.1.scd b/doc/aerc.1.scd
index d2e52a9..230361d 100644
--- a/doc/aerc.1.scd
+++ b/doc/aerc.1.scd
@@ -299,6 +299,10 @@ message list, the message in the message viewer, etc).
 *next-field*, *prev-field*
 	Cycles between input fields in the compose window.
 
+*postpone*
+	Saves the current state of the message to the *postpone* folder for the
+	current account.
+
 *save* [-p] <path>
 	Saves the selected message part to the specified path. If -p is selected,
 	aerc will create any missing directories in the specified path. If the path
diff --git a/widgets/aerc.go b/widgets/aerc.go
index 8307bd0..779e386 100644
--- a/widgets/aerc.go
+++ b/widgets/aerc.go
@@ -293,6 +293,8 @@ func (aerc *Aerc) SelectedAccount() *AccountView {
 		return tab
 	case *MessageViewer:
 		return tab.SelectedAccount()
+	case *Composer:
+		return tab.Account()
 	}
 	return nil
 }
@@ -494,7 +496,7 @@ func (aerc *Aerc) Mailto(addr *url.URL) error {
 			defaults[header] = strings.Join(vals, ",")
 		}
 	}
-	composer, err := NewComposer(aerc, aerc.Config(),
+	composer, err := NewComposer(aerc, acct, aerc.Config(),
 		acct.AccountConfig(), acct.Worker(), "", defaults, models.OriginalMail{})
 	if err != nil {
 		return nil
diff --git a/widgets/compose.go b/widgets/compose.go
index 9ceabf6..66877cc 100644
--- a/widgets/compose.go
+++ b/widgets/compose.go
@@ -32,9 +32,10 @@ import (
 type Composer struct {
 	editors map[string]*headerEditor
 
-	acct   *config.AccountConfig
-	config *config.AercConfig
-	aerc   *Aerc
+	acctConfig *config.AccountConfig
+	config     *config.AercConfig
+	acct       *AccountView
+	aerc       *Aerc
 
 	attachments []string
 	date        time.Time
@@ -57,7 +58,7 @@ type Composer struct {
 	width int
 }
 
-func NewComposer(aerc *Aerc, conf *config.AercConfig,
+func NewComposer(aerc *Aerc, acct *AccountView, conf *config.AercConfig,
 	acct *config.AccountConfig, worker *types.Worker, template string,
 	defaults map[string]string, original models.OriginalMail) (*Composer, error) {
 
@@ -65,7 +66,7 @@ func NewComposer(aerc *Aerc, conf *config.AercConfig,
 		defaults = make(map[string]string)
 	}
 	if from := defaults["From"]; from == "" {
-		defaults["From"] = acct.From
+		defaults["From"] = acctConfig.From
 	}
 
 	templateData := templates.ParseTemplateData(defaults, original)
@@ -82,16 +83,17 @@ func NewComposer(aerc *Aerc, conf *config.AercConfig,
 	}
 
 	c := &Composer{
-		acct:     acct,
-		aerc:     aerc,
-		config:   conf,
-		date:     time.Now(),
-		defaults: defaults,
-		editors:  editors,
-		email:    email,
-		layout:   layout,
-		msgId:    mail.GenerateMessageID(),
-		worker:   worker,
+		acct:       acct,
+		acctConfig: acctConfig,
+		aerc:       aerc,
+		config:     conf,
+		date:       time.Now(),
+		defaults:   defaults,
+		editors:    editors,
+		email:      email,
+		layout:     layout,
+		msgId:      mail.GenerateMessageID(),
+		worker:     worker,
 		// You have to backtab to get to "From", since you usually don't edit it
 		focused:   1,
 		focusable: focusable,
@@ -215,7 +217,7 @@ func (c *Composer) AddTemplate(template string, data interface{}) error {
 
 func (c *Composer) AddSignature() {
 	var signature []byte
-	if c.acct.SignatureCmd != "" {
+	if c.acctConfig.SignatureCmd != "" {
 		var err error
 		signature, err = c.readSignatureFromCmd()
 		if err != nil {
@@ -228,7 +230,7 @@ func (c *Composer) AddSignature() {
 }
 
 func (c *Composer) readSignatureFromCmd() ([]byte, error) {
-	sigCmd := c.acct.SignatureCmd
+	sigCmd := c.acctConfig.SignatureCmd
 	cmd := exec.Command("sh", "-c", sigCmd)
 	signature, err := cmd.Output()
 	if err != nil {
@@ -238,7 +240,7 @@ func (c *Composer) readSignatureFromCmd() ([]byte, error) {
 }
 
 func (c *Composer) readSignatureFromFile() []byte {
-	sigFile := c.acct.SignatureFile
+	sigFile := c.acctConfig.SignatureFile
 	if sigFile == "" {
 		return nil
 	}
@@ -354,6 +356,10 @@ func (c *Composer) Focus(focus bool) {
 }
 
 func (c *Composer) Config() *config.AccountConfig {
+	return c.acctConfig
+}
+
+func (c *Composer) Account() *AccountView {
 	return c.acct
 }
 
@@ -771,7 +777,7 @@ func newReviewMessage(composer *Composer, err error) *reviewMessage {
 	} else {
 		// TODO: source this from actual keybindings?
 		grid.AddChild(ui.NewText(
-			"Send this email? [y]es/[n]o/[e]dit/[a]ttach")).At(0, 0)
+			"Send this email? [y]es/[n]o/[p]ostpone/[e]dit/[a]ttach")).At(0, 0)
 		grid.AddChild(ui.NewText("Attachments:").
 			Reverse(true)).At(1, 0)
 		if len(composer.attachments) == 0 {