Mu4E - Save attachments faster with ivy
EDIT - 2021-08-16: See the Edits section further down. EDIT - 2024-11-09: See the newer post Mu4e - Update - save attachments faster.
Issue I want to fix
The default way of saving my attachments is to hit a keybinding for mu4e-view-attachment-action
.
I then get asked to select between save or save multi. After the selection, the chosen completion framework takes over and I can then save my attachments(s) in the directory dictated by the variable mu4e-attachment-dir
. I cannot change the directory in which to save the attachments.
This grinds my gear to no end. Why you ask? Well, most of the time i can see the number of the attachments in my email. Sometime I want to save all of them and sometime I want to save just one or a range. The worst thing though is that I get prompted for save or save multi even if I only have one attachment 😠.
I want two different functions (two different keybindings) for single and for multi. Then to be able to complete (for me with ivy) to the destination directory I want.
My Solution to the issue
The crucial part upfront. The variable mu4e-save-multiple-attachments-without-asking
needs to be set to true. From the documentation:
If non-nil, saving multiple attachments asks once for a directory and saves all attachments in the chosen directory.
Let us set it then.
(setq mu4e-save-multiple-attachments-without-asking t)
Setting the keybindings should be trivial.
Now to the functions.
case #1 - I want to save all attachments in the email
I took the default mu4e-view-save-attachment-multi
and hacked on it till I got a custom function that preselects all (attachments). Plus, That way I can skip the prompt since I skipped the mu4e-view-attachment-action
function. This is particularly sexy since I can use the function if I only have one attachment in the email as well.
(defun my/mu4e-view-save-attachments (&optional msg)
"Save All Attachements in a selected directory using `ivy'.
This is a modified version of `mu4e-view-save-attachment-multi'."
(interactive)
(let* ((msg (or msg (mu4e-message-at-point)))
(attachstr "a") ;; this is the part that preselects all
(count (hash-table-count mu4e~view-attach-map))
(attachnums (mu4e-split-ranges-to-numbers attachstr count)))
(if mu4e-save-multiple-attachments-without-asking
(let* ((path (concat (mu4e~get-attachment-dir) "/"))
(attachdir (mu4e~view-request-attachments-dir path)))
(dolist (num attachnums)
(let* ((att (mu4e~view-get-attach msg num))
(fname (plist-get att :name))
(index (plist-get att :index))
(retry t)
fpath)
(while retry
(setq fpath (expand-file-name (concat attachdir fname) path))
(setq retry
(and (file-exists-p fpath)
(not (y-or-n-p
(mu4e-format "Overwrite '%s'?" fpath))))))
(mu4e~proc-extract
'save (mu4e-message-field msg :docid)
index mu4e-decryption-policy fpath))))
(dolist (num attachnums)
(mu4e-view-save-attachment-single msg num)))))
case #2 - I want to select a range or a single attachment in the email
This is rather the defaults function. Just copied it and renamed it in case I want to hack on it some more
(defun my/mu4e-view-save-attachment (&optional msg)
"Save All Attachements in a selected directory using `ivy'.
This is a modified version of `mu4e-view-save-attachment-multi'."
(interactive)
(let* ((msg (or msg (mu4e-message-at-point)))
(attachstr (mu4e~view-get-attach-num
"Attachment number range (or 'a' for 'all')" msg t))
(count (hash-table-count mu4e~view-attach-map))
(attachnums (mu4e-split-ranges-to-numbers attachstr count)))
(if mu4e-save-multiple-attachments-without-asking
(let* ((path (concat (mu4e~get-attachment-dir) "/"))
(attachdir (mu4e~view-request-attachments-dir path)))
(dolist (num attachnums)
(let* ((att (mu4e~view-get-attach msg num))
(fname (plist-get att :name))
(index (plist-get att :index))
(retry t)
fpath)
(while retry
(setq fpath (expand-file-name (concat attachdir fname) path))
(setq retry
(and (file-exists-p fpath)
(not (y-or-n-p
(mu4e-format "Overwrite '%s'?" fpath))))))
(mu4e~proc-extract
'save (mu4e-message-field msg :docid)
index mu4e-decryption-policy fpath))))
(dolist (num attachnums)
(mu4e-view-save-attachment-single msg num)))))
Edits
2021-08-16 - Fixing breaking mu/mu4e 1.6 updates:
As of version 1.6 mu/mu4e implemented new functionalities – including the new gnus-article-mode
– and deprecated others. See the release notes. In the snippets above I use mu4e-view-save-attachment-multi
, which was carved out in the process.
I had to rewrite my functions to use mu4e-view-save-attachments
. Below is the result.
<span style=“color:#d08770;">BONUS: This works with any completion framework if you set mu4e-completing-read-function
correctly.</span>
for case #1:
(defun timu/mu4e-view-save-attachments ()
"Save All Attachements in a selected directory using completion.
This is a modified version of `mu4e-view-save-attachments'."
(interactive)
(cl-assert (and (eq major-mode 'mu4e-view-mode)
(derived-mode-p 'gnus-article-mode)))
(let* ((parts (mu4e~view-gather-mime-parts))
(handles '())
(files '())
dir)
(dolist (part parts)
(let ((fname (cdr (assoc 'filename (assoc "attachment" (cdr part))))))
(when fname
(push `(,fname . ,(cdr part)) handles)
(push fname files))))
(if files
(progn
(setq dir (read-directory-name "Save to directory: "))
(cl-loop for (f . h) in handles
when (member f files)
do (mm-save-part-to-file h (expand-file-name f dir))))
(mu4e-message "No attached files found"))))
for case #2:
(defun timu/mu4e-view-save-attachment ()
"Save one attachements in a selected directory using completion.
This is a modified version of `mu4e-view-save-attachments'."
(interactive)
(cl-assert (and (eq major-mode 'mu4e-view-mode)
(derived-mode-p 'gnus-article-mode)))
(let* ((parts (mu4e~view-gather-mime-parts))
(handles '())
(files '())
dir)
(dolist (part parts)
(let ((fname (cdr (assoc 'filename (assoc "attachment" (cdr part))))))
(when fname
(push `(,fname . ,(cdr part)) handles)
(push fname files))))
(if files
(progn
(setq files (completing-read-multiple "Save part(s): " files)
dir (read-directory-name "Save to directory: "))
(cl-loop for (f . h) in handles
when (member f files)
do (mm-save-part-to-file h (expand-file-name f dir))))
(mu4e-message "No attached files found"))))