From d64ceba2cc8d8d1624348a76c7e7a02e385e6d0a Mon Sep 17 00:00:00 2001 From: Robin Jarry Date: Thu, 24 Mar 2022 09:26:06 +0100 Subject: save: add -a option to save all attachments MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Allow saving all message parts that have the content disposition "attachment" header to a folder. Suggested-by: Ondřej Synáček Signed-off-by: Robin Jarry Acked-by: Koni Marti Tested-by: Moritz Poldrack --- commands/msgview/save.go | 62 ++++++++++++++++++++++++++++++++++++------------ 1 file changed, 47 insertions(+), 15 deletions(-) (limited to 'commands/msgview') diff --git a/commands/msgview/save.go b/commands/msgview/save.go index 48add989..350739ab 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] ") + return errors.New("Usage: :save [-fpa] ") } // 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, ¶ms); err != nil { + return err + } + } + return nil + } + pi := mv.SelectedMessagePart() + return savePart(pi, path, store, aerc, ¶ms) +} + +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 { -- cgit