summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorRobin Jarry <robin@jarry.cc>2022-03-24 09:26:06 +0100
committerRobin Jarry <robin@jarry.cc>2022-03-24 15:30:10 +0100
commitd64ceba2cc8d8d1624348a76c7e7a02e385e6d0a (patch)
tree70adf3aa3b2224a57be48f98905db64f0d6c6c3c
parentd66930749a9f8eaa19acab78d57585a170f17429 (diff)
downloadaerc-d64ceba2cc8d8d1624348a76c7e7a02e385e6d0a.zip
save: add -a option to save all attachments
Allow saving all message parts that have the content disposition "attachment" header to a folder. Suggested-by: Ondřej Synáček <ondrej@synacek.org> Signed-off-by: Robin Jarry <robin@jarry.cc> Acked-by: Koni Marti <koni.marti@gmail.com> Tested-by: Moritz Poldrack <moritz@poldrack.dev>
-rw-r--r--commands/msgview/save.go62
-rw-r--r--doc/aerc.1.scd8
-rw-r--r--widgets/msgviewer.go17
3 files changed, 69 insertions, 18 deletions
diff --git a/commands/msgview/save.go b/commands/msgview/save.go
index 48add98..350739a 100644
--- a/commands/msgview/save.go
+++ b/commands/msgview/save.go
@@ -13,6 +13,7 @@ import (
"github.com/mitchellh/go-homedir"
"git.sr.ht/~rjarry/aerc/commands"
+ "git.sr.ht/~rjarry/aerc/lib"
"git.sr.ht/~rjarry/aerc/logging"
"git.sr.ht/~rjarry/aerc/models"
"git.sr.ht/~rjarry/aerc/widgets"
@@ -33,31 +34,36 @@ func (Save) Complete(aerc *widgets.Aerc, args []string) []string {
return commands.CompletePath(path)
}
+type saveParams struct {
+ force bool
+ createDirs bool
+ trailingSlash bool
+ attachments bool
+}
+
func (Save) Execute(aerc *widgets.Aerc, args []string) error {
- opts, optind, err := getopt.Getopts(args, "fp")
+ opts, optind, err := getopt.Getopts(args, "fpa")
if err != nil {
return err
}
- var (
- force bool
- createDirs bool
- trailingSlash bool
- )
+ var params saveParams
for _, opt := range opts {
switch opt.Option {
case 'f':
- force = true
+ params.force = true
case 'p':
- createDirs = true
+ params.createDirs = true
+ case 'a':
+ params.attachments = true
}
}
defaultPath := aerc.Config().General.DefaultSavePath
// we either need a path or a defaultPath
if defaultPath == "" && len(args) == optind {
- return errors.New("Usage: :save [-fp] <path>")
+ return errors.New("Usage: :save [-fpa] <path>")
}
// as a convenience we join with spaces, so that the user doesn't need to
@@ -68,10 +74,10 @@ func (Save) Execute(aerc *widgets.Aerc, args []string) error {
// it gets stripped by Clean.
// we auto generate a name if a directory was given
if len(path) > 0 {
- trailingSlash = path[len(path)-1] == '/'
+ params.trailingSlash = path[len(path)-1] == '/'
} else if len(defaultPath) > 0 && len(path) == 0 {
// empty path, so we might have a default that ends in a trailingSlash
- trailingSlash = defaultPath[len(defaultPath)-1] == '/'
+ params.trailingSlash = defaultPath[len(defaultPath)-1] == '/'
}
// Absolute paths are taken as is so that the user can override the default
@@ -89,27 +95,53 @@ func (Save) Execute(aerc *widgets.Aerc, args []string) error {
if !ok {
return fmt.Errorf("SelectedTab is not a MessageViewer")
}
+
+ store := mv.Store()
+
+ if params.attachments {
+ parts := mv.AttachmentParts()
+ if len(parts) == 0 {
+ return fmt.Errorf("This message has no attachments")
+ }
+ params.trailingSlash = true
+ for _, pi := range parts {
+ if err := savePart(pi, path, store, aerc, &params); err != nil {
+ return err
+ }
+ }
+ return nil
+ }
+
pi := mv.SelectedMessagePart()
+ return savePart(pi, path, store, aerc, &params)
+}
+
+func savePart(
+ pi *widgets.PartInfo,
+ path string,
+ store *lib.MessageStore,
+ aerc *widgets.Aerc,
+ params *saveParams,
+) error {
- if trailingSlash || isDirExists(path) {
+ if params.trailingSlash || isDirExists(path) {
filename := generateFilename(pi.Part)
path = filepath.Join(path, filename)
}
dir := filepath.Dir(path)
- if createDirs && dir != "" {
+ if params.createDirs && dir != "" {
err := os.MkdirAll(dir, 0755)
if err != nil {
return err
}
}
- if pathExists(path) && !force {
+ if pathExists(path) && !params.force {
return fmt.Errorf("%q already exists and -f not given", path)
}
ch := make(chan error, 1)
- store := mv.Store()
store.FetchBodyPart(pi.Msg.Uid, pi.Index, func(reader io.Reader) {
f, err := os.Create(path)
if err != nil {
diff --git a/doc/aerc.1.scd b/doc/aerc.1.scd
index 27384ad..dda8aed 100644
--- a/doc/aerc.1.scd
+++ b/doc/aerc.1.scd
@@ -337,12 +337,12 @@ message list, the message in the message viewer, etc).
Saves the current message part in a temporary file and opens it
with the system handler. Any given args are forwarded to the open handler
-*save* [-fp] <path>
+*save* [-fpa] <path>
Saves the current message part to the given path.
If the path is not an absolute path, general.default-save-path will be
prepended to the path given.
- If path ends in a trailing slash or if a folder exists on disc,
- aerc assumes it to be a directory.
+ If path ends in a trailing slash or if a folder exists on disc or if -a
+ is specified, aerc assumes it to be a directory.
When passed a directory :save infers the filename from the mail part if
possible, or if that fails, uses "aerc_$DATE".
@@ -350,6 +350,8 @@ message list, the message in the message viewer, etc).
*-p*: Create any directories in the path that do not exist
+ *-a*: Save all attachments. Individual filenames cannot be specified.
+
*mark* [-atv]
Marks messages. Commands will execute on all marked messages instead of the
highlighted one if applicable. The flags below can be combined as needed.
diff --git a/widgets/msgviewer.go b/widgets/msgviewer.go
index 91baf02..cefa9bb 100644
--- a/widgets/msgviewer.go
+++ b/widgets/msgviewer.go
@@ -303,6 +303,23 @@ func (mv *MessageViewer) SelectedMessagePart() *PartInfo {
}
}
+func (mv *MessageViewer) AttachmentParts() []*PartInfo {
+ var attachments []*PartInfo
+
+ for _, p := range mv.switcher.parts {
+ if p.part.Disposition == "attachment" {
+ pi := &PartInfo{
+ Index: p.index,
+ Msg: p.msg.MessageInfo(),
+ Part: p.part,
+ }
+ attachments = append(attachments, pi)
+ }
+ }
+
+ return attachments
+}
+
func (mv *MessageViewer) PreviousPart() {
switcher := mv.switcher
for {