e56ceb099e
This adds new functionality to :save in the message view for specifying directories in the path. A new flag, -p, is also added to :save for automatically creating any directories in the path that do not exist. If the path ends in a / (e.g. "Downloads/mail/") or if the path is an existing directory, the part's file name is the filename from the mail header for the part. Otherwise, it uses the last element in the path as the filename (e.g. 'blah.jpg' is the filename if the path is 'Downloads/mail/blah.jpg')
108 lines
2.3 KiB
Go
108 lines
2.3 KiB
Go
package msgview
|
|
|
|
import (
|
|
"encoding/base64"
|
|
"errors"
|
|
"io"
|
|
"mime/quotedprintable"
|
|
"os"
|
|
"path/filepath"
|
|
"time"
|
|
|
|
"git.sr.ht/~sircmpwn/aerc/widgets"
|
|
"git.sr.ht/~sircmpwn/getopt"
|
|
"github.com/mitchellh/go-homedir"
|
|
)
|
|
|
|
func init() {
|
|
register("save", Save)
|
|
}
|
|
|
|
func Save(aerc *widgets.Aerc, args []string) error {
|
|
opts, optind, err := getopt.Getopts(args, "p")
|
|
if err != nil {
|
|
return err
|
|
}
|
|
var (
|
|
mkdirs bool
|
|
path string
|
|
)
|
|
for _, opt := range opts {
|
|
switch opt.Option {
|
|
case 'p':
|
|
mkdirs = true
|
|
}
|
|
}
|
|
if len(args) <= optind {
|
|
return errors.New("Usage: :save [-p] <path>")
|
|
}
|
|
path = args[optind]
|
|
|
|
mv := aerc.SelectedTab().(*widgets.MessageViewer)
|
|
p := mv.CurrentPart()
|
|
|
|
p.Store.FetchBodyPart(p.Msg.Uid, p.Index, func(reader io.Reader) {
|
|
// email parts are encoded as 7bit (plaintext), quoted-printable, or base64
|
|
switch p.Part.Encoding {
|
|
case "base64":
|
|
reader = base64.NewDecoder(base64.StdEncoding, reader)
|
|
case "quoted-printable":
|
|
reader = quotedprintable.NewReader(reader)
|
|
}
|
|
|
|
var pathIsDir bool
|
|
if path[len(path)-1:] == "/" {
|
|
pathIsDir = true
|
|
}
|
|
// Note: path expansion has to happen after test for trailing /,
|
|
// since it is stripped when path is expanded
|
|
path, err := homedir.Expand(path)
|
|
if err != nil {
|
|
aerc.PushError(" " + err.Error())
|
|
}
|
|
|
|
pathinfo, err := os.Stat(path)
|
|
if err == nil && pathinfo.IsDir() {
|
|
pathIsDir = true
|
|
} else if os.IsExist(err) && pathIsDir {
|
|
aerc.PushError("The given directory is an existing file")
|
|
}
|
|
|
|
// Use attachment name as filename if given path is a directory
|
|
save_file := filepath.Base(path)
|
|
save_dir := filepath.Dir(path)
|
|
if pathIsDir {
|
|
save_dir = path
|
|
if filename, ok := p.Part.DispositionParams["filename"]; ok {
|
|
save_file = filename
|
|
}
|
|
}
|
|
if _, err := os.Stat(save_dir); os.IsNotExist(err) {
|
|
if mkdirs {
|
|
os.MkdirAll(save_dir, 0755)
|
|
} else {
|
|
aerc.PushError("Target directory does not exist, use " +
|
|
":save with the -p option to create it")
|
|
return
|
|
}
|
|
}
|
|
target := filepath.Clean(filepath.Join(save_dir, save_file))
|
|
|
|
f, err := os.Create(target)
|
|
if err != nil {
|
|
aerc.PushError(" " + err.Error())
|
|
return
|
|
}
|
|
defer f.Close()
|
|
|
|
_, err = io.Copy(f, reader)
|
|
if err != nil {
|
|
aerc.PushError(" " + err.Error())
|
|
return
|
|
}
|
|
|
|
aerc.PushStatus("Saved to "+target, 10*time.Second)
|
|
})
|
|
|
|
return nil
|
|
}
|