* mua updates

This commit is contained in:
Dirk-Jan C. Binnema
2011-08-16 00:09:34 +03:00
parent ade551deb9
commit 76c8d21c73
7 changed files with 431 additions and 271 deletions

View File

@ -30,6 +30,8 @@
(eval-when-compile (require 'cl)) (eval-when-compile (require 'cl))
(require 'ido)
(defconst mua/log-buffer-name "*mua-log*" "name of the logging buffer") (defconst mua/log-buffer-name "*mua-log*" "name of the logging buffer")
(defun mua/warn (frm &rest args) (defun mua/warn (frm &rest args)
@ -82,79 +84,5 @@ maildir."
(chosen (ido-completing-read prompt showfolders))) (chosen (ido-completing-read prompt showfolders)))
(concat (if fullpath mua/maildir "") chosen))) (concat (if fullpath mua/maildir "") chosen)))
(defun mua/maildir-flags-from-path (path)
"Get the flags for the message at PATH, which does not have to exist.
The flags are returned as a list consisting of one or more of
DFNPRST, mean resp. Deleted, Flagged, New, Passed Replied, Seen
and Trash, as defined in [1]. See `mua/maildir-string-to-flags'
and `mua/maildir-flags-to-string'.
\[1\] http://cr.yp.to/proto/maildir.html."
(when (string-match ",\\(\[A-Z\]*\\)$" path)
(mua/maildir-string-to-flags (match-string 1 path))))
(defun mua/maildir-from-path (path &optional dont-strip-prefix)
"Get the maildir from path; in this context, 'maildir' is the
part between the `mua/maildir' and the /cur or /new; so
e.g. \"/home/user/Maildir/foo/bar/cur/12345:2,S\" would have
\"/foo/bar\" as its maildir. If DONT-STRIP-PREFIX is non-nil,
function will instead _not_ remove the `mua/maildir' from the
front - so in that case, the example would return
\"/home/user/Maildir/foo/bar/\". If the maildir cannot be
determined, return `nil'."
(when (and (string-match "^\\(.*\\)/\\(cur\\|new\\)/\[^/\]*$" path))
(let ((mdir (match-string 1 path)))
(when (and (< (length mua/maildir) (length mdir))
(string= (substring mdir 0 (length mua/maildir)) mua/maildir))
(if dont-strip-prefix
mdir
(substring mdir (length mua/maildir)))))))
;; TODO: ensure flag string have the chars in ASCII-order (as per maildir spec)
;; TODO: filter-out duplicate flags
(defun mua/maildir-flags-to-string (flags)
"Convert a list of flags into a string as seen in Maildir
message files; flags are symbols draft, flagged, new, passed,
replied, seen, trashed and the string is the concatenation of the
uppercased first letters of these flags, as per [1]. Other flags
than the ones listed here are ignored.
Also see `mua/maildir-string-to-flags'.
\[1\]: http://cr.yp.to/proto/maildir.html"
(when flags
(let ((kar
(case (car flags)
('draft ?D)
('flagged ?F)
('passed ?P)
('replied ?R)
('seen ?S)
('trashed ?T))))
(concat (and kar (string kar))
(mua/maildir-flags-to-string (cdr flags))))))
(defun mua/maildir-string-to-flags (str)
"Convert a string with message flags as seen in Maildir
messages into a list of flags in; flags are symbols draft,
flagged, new, passed, replied, seen, trashed and the string is
the concatenation of the uppercased first letters of these flags,
as per [1]. Other letters than the ones listed here are ignored.
Also see `mua/maildir-flags-to-string'.
\[1\]: http://cr.yp.to/proto/maildir.html"
(when (/= 0 (length str))
(let ((flag
(case (string-to-char str)
(?D 'draft)
(?F 'flagged)
(?P 'passed)
(?R 'replied)
(?S 'seen)
(?T 'trashed))))
(append (when flag (list flag))
(mua/maildir-string-to-flags (substring str 1))))))
(provide 'mua-common) (provide 'mua-common)

View File

@ -51,15 +51,15 @@
(defvar mua/hdrs-hash nil "the bol->path hash") (defvar mua/hdrs-hash nil "the bol->path hash")
(defvar mua/hdrs-marks-hash nil "the hash for marked messages") (defvar mua/hdrs-marks-hash nil "the hash for marked messages")
(defconst mua/eom "\n;;eom\n" "marker for the end of message in (defconst mua/eom "\n;;eom\n" "*internal* Marker for the end of message in
the mu find output") the mu find output.")
(defconst mua/hdrs-buffer-name "*mua-headers*" (defconst mua/hdrs-buffer-name "*mua-headers*"
"name of the mua headers buffer") "*internal* Name of the mua headers buffer.")
(defun mua/hdrs-proc-filter (proc str) (defun mua/hdrs-proc-filter (proc str)
"process-filter for the 'mu find --format=sexp output; it "A process-filter for the 'mu find --format=sexp output; it
accumulates the strings into valid sexps by checking of the accumulates the strings into valid sexps by checking of the
';;eom' end-of-msg marker, and then evaluating them" ';;eom' end-of-msg marker, and then evaluating them."
(let ((procbuf (process-buffer proc))) (let ((procbuf (process-buffer proc)))
(when (buffer-live-p procbuf) (when (buffer-live-p procbuf)
(with-current-buffer procbuf (with-current-buffer procbuf
@ -73,7 +73,7 @@ the mu find output")
(setq eom (string-match mua/eom mua/buf)))))))))) (setq eom (string-match mua/eom mua/buf))))))))))
(defun mua/hdrs-proc-sentinel (proc msg) (defun mua/hdrs-proc-sentinel (proc msg)
"Check the process upon completion" "Check the process upon completion."
(let ((procbuf (process-buffer proc)) (let ((procbuf (process-buffer proc))
(status (process-status proc)) (status (process-status proc))
(exit-status (process-exit-status proc))) (exit-status (process-exit-status proc)))
@ -89,7 +89,7 @@ the mu find output")
(with-current-buffer procbuf (with-current-buffer procbuf
(save-excursion (save-excursion
(goto-char (point-max)) (goto-char (point-max))
(mua/message msg))))))) (mua/message "%s" msg)))))))
(defun mua/hdrs-search-execute (expr buf) (defun mua/hdrs-search-execute (expr buf)
"search in the mu database; output the results in buffer BUF" "search in the mu database; output the results in buffer BUF"
@ -100,8 +100,7 @@ the mu find output")
(add-to-list args (concat "--sortfield=" mua/hdrs-sortfield))) (add-to-list args (concat "--sortfield=" mua/hdrs-sortfield)))
(when mua/hdrs-sort-descending (when mua/hdrs-sort-descending
(add-to-list args "--descending")) (add-to-list args "--descending"))
(mua/log (concat mua/mu-binary " find " expr (mua/log (concat mua/mu-binary " " (mapconcat 'identity args " ")))
(mapconcat 'identity args " ")))
;; now, do it! ;; now, do it!
(let ((proc (apply 'start-process "*mua-headers*" buf mua/mu-binary args))) (let ((proc (apply 'start-process "*mua-headers*" buf mua/mu-binary args)))
(setq (setq
@ -147,7 +146,7 @@ the mu find output")
(make-local-variable 'mua/hdrs-marks-hash) (make-local-variable 'mua/hdrs-marks-hash)
(setq (setq
major-mode 'mua/mua-hdrs-mode mode-name "*mua-headers*" major-mode 'mua/mua/hdrs-mode mode-name "*mua-headers*"
truncate-lines t buffer-read-only t truncate-lines t buffer-read-only t
overwrite-mode 'overwrite-mode-binary)) overwrite-mode 'overwrite-mode-binary))
@ -185,12 +184,21 @@ the mu find output")
;; ;;
(defun mua/hdrs-set-path (path) (defun mua/hdrs-set-path (path)
"map the bol of the current header to a path" "Map the bol of the current header to an entry in
(puthash (line-beginning-position 1) path mua/hdrs-hash)) `mua/msg-file-map', and return the uid"
(let ((uid (mua/msg-file-register path)))
(puthash (line-beginning-position 1) uid mua/hdrs-hash)
uid))
(defun mua/hdrs-get-uid ()
"Get the uid for the message header at point."
(gethash (line-beginning-position 1) mua/hdrs-hash))
(defun mua/hdrs-get-path () (defun mua/hdrs-get-path ()
"get the path for the header at point" "Get the current path for the header at point."
(gethash (line-beginning-position 1) mua/hdrs-hash)) (let ((uid (mua/hdrs-get-uid)))
(mua/msg-file-get-path uid)))
(defun mua/hdrs-append-message (msg) (defun mua/hdrs-append-message (msg)
"append a message line to the buffer and register the message" "append a message line to the buffer and register the message"
@ -309,9 +317,9 @@ fitting in WIDTH"
(defun mua/hdrs-view () (defun mua/hdrs-view ()
(interactive) (interactive)
(let ((path (mua/hdrs-get-path))) (let ((uid (mua/hdrs-get-uid)))
(if path (if uid
(mua/view path (current-buffer)) (mua/view uid (current-buffer))
(mua/warn "No message at point")))) (mua/warn "No message at point"))))
(defun mua/hdrs-jump-to-maildir () (defun mua/hdrs-jump-to-maildir ()
@ -365,12 +373,12 @@ if the search process is not already running"
;;; functions for marking ;;; functions for marking
(defun mua/hdrs-add-marked (src &optional dst) (defun mua/hdrs-add-marked (uid &optional dst)
"Add the message at point to the markings hash" "Add the message at point to the markings hash"
(let ((bol (line-beginning-position 1))) (let ((bol (line-beginning-position 1)))
(if (gethash bol mua/hdrs-marks-hash) (if (gethash bol mua/hdrs-marks-hash)
(mua/warn "Message is already marked") (mua/warn "Message is already marked")
(progn (puthash bol (cons src dst) mua/hdrs-marks-hash) t)))) (progn (puthash bol (cons uid dst) mua/hdrs-marks-hash) t))))
(defun mua/hdrs-remove-marked () (defun mua/hdrs-remove-marked ()
"Remove the message at point from the markings hash" "Remove the message at point from the markings hash"
@ -390,19 +398,19 @@ if the search process is not already running"
"Mark the message at point with one of the symbols: move, "Mark the message at point with one of the symbols: move,
delete, trash, unmark, unmark-all; the latter two are delete, trash, unmark, unmark-all; the latter two are
pseudo-markings." pseudo-markings."
(let ((target) (src (mua/hdrs-get-path))) (let ((uid (mua/hdrs-get-uid)))
(when src (when uid
(case action (case action
(move (move
(when (mua/hdrs-add-marked src (when (mua/hdrs-add-marked uid
(mua/ask-maildir "Target maildir: " t)) ;; t->return fullpath (mua/ask-maildir "Target maildir: " t)) ;; t->return fullpath
(mua/hdrs-set-marker ?m))) (mua/hdrs-set-marker ?m)))
(trash (trash
(when (mua/hdrs-add-marked src (when (mua/hdrs-add-marked uid
(concat mua/maildir mua/trash-folder)) (concat mua/maildir mua/trash-folder))
(mua/hdrs-set-marker ?d))) (mua/hdrs-set-marker ?d)))
(delete (delete
(when (mua/hdrs-add-marked src "/dev/null") (when (mua/hdrs-add-marked uid "/dev/null")
(mua/hdrs-set-marker ?D))) (mua/hdrs-set-marker ?D)))
(unmark (unmark
(when (mua/hdrs-remove-marked) (when (mua/hdrs-remove-marked)
@ -427,12 +435,10 @@ pseudo-markings."
(save-excursion (save-excursion
(maphash (maphash
(lambda(bol v) (lambda(bol v)
(let* ((src (car v)) (target (cdr v)) (inhibit-read-only t) (let* ((uid (car v)) (target (cdr v)) (inhibit-read-only t))
(newpath (mua/msg-move src target))) (when (mua/msg-file-move-uid uid target)
(when newpath
;; remember the updated path -- for now not too useful ;; remember the updated path -- for now not too useful
;; as we're hiding the header, but... ;; as we're hiding the header, but...
(mua/hdrs-set-path newpath)
(goto-char bol) (goto-char bol)
(mua/hdrs-remove-marked) (mua/hdrs-remove-marked)
(put-text-property (line-beginning-position 1) (put-text-property (line-beginning-position 1)
@ -446,20 +452,23 @@ pseudo-markings."
(defun mua/hdrs-reply () (defun mua/hdrs-reply ()
"Reply to message at point." "Reply to message at point."
(interactive) (interactive)
(let* ((path (mua/hdrs-get-path)) (let* ((uid (mua/hdrs-get-uid))
(path (mua/hdrs-get-path))
(str (when path (mua/mu-view-sexp path))) (str (when path (mua/mu-view-sexp path)))
(msg (and str (mua/msg-from-string str)))) (msg (and str (mua/msg-from-string str))))
(if msg (if msg
(mua/msg-reply msg) (mua/msg-reply msg uid)
(mua/warn "No message at point")))) (mua/warn "No message at point"))))
(defun mua/hdrs-forward () (defun mua/hdrs-for ()
"Forward the message at point." "Forward the message at point."
(interactive) (interactive)
(let* ((path (mua/hdrs-get-path)) (let* ((uid (mua/hdrs-get-uid))
(msg (when path (mua/msg-from-path path)))) (path (mua/hdrs-get-path))
(str (when path (mua/mu-view-sexp path)))
(msg (and str (mua/msg-from-string str))))
(if msg (if msg
(mua/msg-forward msg) (mua/msg-reply msg uid)
(mua/warn "No message at point")))) (mua/warn "No message at point"))))
(defun mua/hdrs-compose () (defun mua/hdrs-compose ()

211
toys/mua/mua-msg-file.el Normal file
View File

@ -0,0 +1,211 @@
;;; mua-msg.el -- part of mua, the mu mail user agent
;;
;; Copyright (C) 2011 Dirk-Jan C. Binnema
;; Author: Dirk-Jan C. Binnema <djcb@djcbsoftware.nl>
;; Maintainer: Dirk-Jan C. Binnema <djcb@djcbsoftware.nl>
;; Keywords: email
;; Version: 0.0
;; This file is not part of GNU Emacs.
;;
;; GNU Emacs is free software: you can redistribute it and/or modify
;; it under the terms of the GNU General Public License as published by
;; the Free Software Foundation, either version 3 of the License, or
;; (at your option) any later version.
;; GNU Emacs is distributed in the hope that it will be useful,
;; but WITHOUT ANY WARRANTY; without even the implied warranty of
;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
;; GNU General Public License for more details.
;; You should have received a copy of the GNU General Public License
;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>.
;;; Commentary:
;; mua
;;; Code:
(eval-when-compile (require 'cl))
(defvar mua/msg-file-map nil
"*internal* a map of uid->message.
This map adds a level of indirection for message files; many
actions (such moving, responding to or even reading a message)
cause the file names to change. Here we map the initial file to a
uid, the latter which stays constant over the lifetime of a
message in the system (in practice, the lifetime of a particular
headers buffer).
When creating the headers buffer, the file names are registered
with `mua/msg-file-register'.
All operation that change file names ultimately (should) end up
in `mua/msg-file-move', which will update the map after the
moving (using `mua/msg-file-update')
Other places of the code can use the uid to get the *current*
path of the file using `mua/msg-file-get-path'.
")
(defun mua/msg-file-register (path)
"Register a message PATH in the `mua/msg-file-map', and return
the uid for it."
(unless mua/msg-file-map
(setq mua/msg-file-map (make-hash-table :size 256 :rehash-size 2)))
(let ((uid (sha1 path)))
(puthash uid path mua/msg-file-map)
uid))
(defun mua/msg-file-update (uid path)
"Set the new path for the message identified by UID to
PATH."
(if (gethash uid mua/msg-file-map)
(puthash uid path mua/msg-file-map)
(mua/warn "No message file registered for uid")))
(defun mua/msg-file-get-path (uid)
"Get the current path for the message identified by UID."
(gethash uid mua/msg-file-map))
(defun mua/msg-file-move-uid (uid targetdir &optional flags)
"Move message identified by UID to TARGETDIR using 'mu mv', and
update the database with the new situation. SRC must be the full,
absolute path to a message file, while TARGETDIR must be a
maildir - that is, the part _without_ cur/ or new/. 'mu mv' will
calculate the target directory and the exact file name. See
`mua/msg-file-map' for a discussion about UID.
After the file system move (rename) has been done, 'mu remove'
and/or 'mu add' are invoked asynchronously to update the database
with the changes.
Optionally, you can specify the FLAGS for the new file; this must
be a list consisting of one or more of DFNPRST, mean
resp. Deleted, Flagged, New, Passed Replied, Seen and g, as
defined in [1]. See `mua/msg-file-string-to-flags' and
`mua/msg-file-flags-to-string'.
If TARGETDIR is '/dev/null', remove SRC. After the file system
move, the database will be updated as well, using the 'mu add'
and 'mu remove' commands.
Function returns t the move succeeds, in other cases, it returns
`nil'.
\[1\] http://cr.yp.to/proto/maildir.html."
(let ((src (mua/msg-file-get-path uid)))
(unless src (error "Source path not registered."))
(let ((fulltarget (mua/mu-mv src targetdir flags)))
(when (and fulltarget (not (string= src fulltarget)))
(mua/msg-file-update uid fulltarget) ;; update the path
(mua/mu-remove-async src)
(unless (string= targetdir "/dev/null")
(mua/mu-add-async fulltarget)))))
t)
(defun mua/msg-file-mark-as-read (uid)
"Mark the message identified by UID as read if it is not so
already. In Maildir terms, this means moving the message from
\"new/\" to \"cur/\" (if it's not yet there), and setting the
\"S\" flag."
(let* ((path (mua/msg-file-get-path uid))
(flags (and path (mua/msg-file-flags-from-path path))))
(when (or (member 'new flags) (not (member 'seen flags)))
(let* ((newflags (delq 'new (cons 'seen flags)))
(target (mua/msg-file-maildir-from-path path t)))
(unless (mua/msg-file-move-uid uid target newflags)
(mua/warn "Failed to mark message as read"))))))
(defun mua/msg-file-flags-from-path (path)
"Get the flags for the message at PATH, which does not have to exist.
The flags are returned as a list consisting of one or more of
DFNPRST, mean resp. Deleted, Flagged, New, Passed Replied, Seen
and Trash, as defined in [1]. See `mua/msg-file-string-to-flags'
and `mua/msg-file-flags-to-string'.
\[1\] http://cr.yp.to/proto/maildir.html."
(when (string-match ",\\(\[A-Z\]*\\)$" path)
(mua/msg-file-string-to-flags (match-string 1 path))))
(defun mua/msg-file-maildir-from-path (path &optional dont-strip-prefix)
"Get the maildir from PATH; in this context, 'maildir' is the
part between the `mua/maildir' and the /cur or /new; so
e.g. \"/home/user/Maildir/foo/bar/cur/12345:2,S\" would have
\"/foo/bar\" as its maildir. If DONT-STRIP-PREFIX is non-nil,
function will instead _not_ remove the `mua/maildir' from the
front - so in that case, the example would return
\"/home/user/Maildir/foo/bar/\". If the maildir cannot be
determined, return `nil'."
(when (and (string-match "^\\(.*\\)/\\(cur\\|new\\)/\[^/\]*$" path))
(let ((mdir (match-string 1 path)))
(when (and (< (length mua/maildir) (length mdir))
(string= (substring mdir 0 (length mua/maildir)) mua/maildir))
(if dont-strip-prefix
mdir
(substring mdir (length mua/maildir)))))))
(defun mua/msg-file-flags-to-string (flags)
"Remove duplicates and sort the output of `mua/msg-file-flags-to-string-1'"
(concat
(sort
(remove-duplicates
(append (mua/msg-file-flags-to-string-1 flags) nil)) '>)))
(defun mua/msg-file-flags-to-string-1 (flags)
"Convert a list of flags into a string as seen in Maildir
message files; flags are symbols draft, flagged, new, passed,
replied, seen, trashed and the string is the concatenation of the
uppercased first letters of these flags, as per [1]. Other flags
than the ones listed here are ignored.
Also see `mua/msg-file-string-to-flags'.
\[1\]: http://cr.yp.to/proto/maildir.html"
(when flags
(let ((kar
(case (car flags)
('draft ?D)
('flagged ?F)
('passed ?P)
('replied ?R)
('seen ?S)
('trashed ?T))))
(concat (and kar (string kar))
(mua/msg-file-flags-to-string-1 (cdr flags))))))
(defun mua/msg-file-string-to-flags (str)
"Remove duplicates from the output of `mua/msg-file-string-to-flags-1'"
(remove-duplicates (mua/msg-file-string-to-flags-1 str)))
(defun mua/msg-file-string-to-flags-1 (str)
"Convert a string with message flags as seen in Maildir
messages into a list of flags in; flags are symbols draft,
flagged, new, passed, replied, seen, trashed and the string is
the concatenation of the uppercased first letters of these flags,
as per [1]. Other letters than the ones listed here are ignored.
Also see `mua/msg-file-flags-to-string'.
\[1\]: http://cr.yp.to/proto/maildir.html"
(when (/= 0 (length str))
(let ((flag
(case (string-to-char str)
(?D 'draft)
(?F 'flagged)
(?P 'passed)
(?R 'replied)
(?S 'seen)
(?T 'trashed))))
(append (when flag (list flag))
(mua/msg-file-string-to-flags-1 (substring str 1))))))
(provide 'mua-msg-file)

View File

@ -94,38 +94,9 @@ or if not available, :body-html converted to text)."
(mua/msg-body-txt-or-html msg)) (mua/msg-body-txt-or-html msg))
(:maildir ;; messages gotten from mu-view don't have their maildir set... (:maildir ;; messages gotten from mu-view don't have their maildir set...
(or (plist-get msg :maildir) (or (plist-get msg :maildir)
(mua/maildir-from-path (mua/msg-field msg :path)))) (mua/msg-file-maildir-from-path (mua/msg-field msg :path))))
(t (plist-get msg field)))) (t (plist-get msg field))))
(defun mua/msg-move (src targetdir &optional flags)
"Move message at SRC to TARGETDIR using 'mu mv'; SRC must be
the full, absolute path to a message file, while TARGETDIR must
be a maildir - that is, the part _without_ cur/ or new/. 'mu mv'
will calculate the target directory and the exact file name.
Optionally, you can specify the FLAGS for the new file; this must
be a list consisting of one or more of DFNPRST, mean
resp. Deleted, Flagged, New, Passed Replied, Seen and g, as
defined in [1]. See `mua/maildir-string-to-flags' and
`mua/maildir-flags-to-string'.
If TARGETDIR is '/dev/null', remove SRC. After the file system
move, the database will be updated as well, using the 'mu add'
and 'mu remove' commands.
Function returns the target filename if the move succeeds, or
/dev/null if TARGETDIR was /dev/null; in other cases, it returns
`nil'.
\[1\] http://cr.yp.to/proto/maildir.html."
(let ((fulltarget (mua/mu-mv src targetdir flags)))
(when fulltarget
(mua/mu-remove-async src)
(unless (string= targetdir "/dev/null")
(mua/mu-add-async fulltarget)))
fulltarget))
;; functions for composing new messages (forward, reply and new) ;; functions for composing new messages (forward, reply and new)
@ -378,10 +349,13 @@ message.
(random t) (random t)
(replace-regexp-in-string "[:/]" "_" (system-name)))) (replace-regexp-in-string "[:/]" "_" (system-name))))
(defvar mua/msg-reply-uid nil "UID of the message this is a reply to.")
(defvar mua/msg-forward-uid nil "UID of the message being forwarded.")
(defun mua/msg-compose (str) (defun mua/msg-compose (str)
"Create a new draft message in the drafts folder with STR as "Create a new draft message in the drafts folder with STR as
its contents, and open this message file for editing its contents, and open this message file for editing. Optionally
specify PARENT-UID,
The name of the draft folder is constructed from the concatenation of The name of the draft folder is constructed from the concatenation of
`mua/maildir' and `mua/drafts-folder' (therefore, these must be set). `mua/maildir' and `mua/drafts-folder' (therefore, these must be set).
@ -399,14 +373,15 @@ using Gnus' `message-mode'."
(let ((draftfile (concat mua/maildir "/" mua/drafts-folder "/cur/" (let ((draftfile (concat mua/maildir "/" mua/drafts-folder "/cur/"
(mua/msg-draft-file-name)))) (mua/msg-draft-file-name))))
(with-temp-file draftfile (insert str)) (with-temp-file draftfile (insert str))
(find-file draftfile) (find-file draftfile) (rename-buffer mua/msg-draft-name t)
(rename-buffer mua/msg-draft-name t)
(message-mode) (message-mode)
(make-local-variable 'mua/msg-forward-uid)
(message-goto-body))) (message-goto-body)))
(defun mua/msg-reply (msg) (defun mua/msg-reply (msg &optional reply-uid)
"Create a draft reply to MSG, and swith to an edit buffer with "Create a draft reply to MSG, and swith to an edit buffer with
the draft message." the draft message. PARENT-UID refers to the UID of the message wer"
(let* ((recipnum (+ (length (mua/msg-field msg :to)) (let* ((recipnum (+ (length (mua/msg-field msg :to))
(length (mua/msg-field msg :cc)))) (length (mua/msg-field msg :cc))))
(replyall (when (> recipnum 1) (replyall (when (> recipnum 1)
@ -414,12 +389,14 @@ the draft message."
(+ recipnum)))))) (+ recipnum))))))
;; exact num depends on some more things ;; exact num depends on some more things
(when (mua/msg-compose (mua/msg-create-reply msg replyall)) (when (mua/msg-compose (mua/msg-create-reply msg replyall))
(when reply-uid (setq mua/msg-reply-uid reply-uid))
(message-goto-body)))) (message-goto-body))))
(defun mua/msg-forward (msg) (defun mua/msg-forward (msg &optional forward-uid)
"Create a draft forward for MSG, and swith to an edit buffer with "Create a draft forward for MSG, and swith to an edit buffer with
the draft message." the draft message."
(when (mua/msg-compose (mua/msg-create-forward msg)) (when (mua/msg-compose (mua/msg-create-forward msg))
(when forward-uid (setq mua/msg-forward-uid forward-uid))
(message-goto-to))) (message-goto-to)))
(defun mua/msg-compose-new () (defun mua/msg-compose-new ()
@ -430,16 +407,6 @@ draft message."
(defun mua/msg-is-mua-message ()
"Check whether the current buffer refers a mua-message based on
the buffer file name; this is used in hooks we install on
message-mode to ensure we only do things with mua-generated
messages (mua is not the only user of `message-mode' after all)"
(let* ((fname (buffer-file-name))
(match (and fname (string-match mua/msg-file-prefix fname))))
(and (numberp match) (= 0 match))))
;; we simply check if file starts with `mu-msg-file-prefix'
(defun mua/msg-save-to-sent () (defun mua/msg-save-to-sent ()
"Move the message in this buffer to the sent folder. This is "Move the message in this buffer to the sent folder. This is
meant to be called from message mode's `message-sent-hook'." meant to be called from message mode's `message-sent-hook'."
@ -447,33 +414,43 @@ meant to be called from message mode's `message-sent-hook'."
(unless mua/sent-folder (error "mua/sent-folder not set")) (unless mua/sent-folder (error "mua/sent-folder not set"))
(let* ;; TODO: remove duplicate flags (let* ;; TODO: remove duplicate flags
((newflags ;; remove Draft; maybe set 'Seen' as well? ((newflags ;; remove Draft; maybe set 'Seen' as well?
(delq 'draft (mua/maildir-flags-from-path (buffer-file-name)))) (delq 'draft (mua/msg-file-flags-from-path (buffer-file-name))))
(sent-msg ;; so, we register path => uid, then we move uid, then check the name
(mua/msg-move (buffer-file-name) ;; uid is referring to
(concat mua/maildir mua/sent-folder) ;; mua-sent-folder is only eg. "/sent" (uid (mua/msg-file-register (buffer-file-name)))
(mua/maildir-flags-to-string newflags)))) (if (mua/msg-move uid
(if sent-msg ;; change our buffer file-name (concat mua/maildir mua/sent-folder)
(set-visited-file-name sent-msg t t) (mua/msg-file-flags-to-string newflags))
(mua/warn "Failed to save message to the Sent-folder"))))) (set-visited-file-name (mua/msg-file-get-path uid) t t)
(mua/warn "Failed to save message to the Sent-folder"))))))
(defun mua/msg-set-replied-flag () (defun mua/msg-set-replied-or-passed-flag ()
"Find the message we replied to, and set its 'Replied' "Set the 'replied' flag on messages we replied to, and the
flag. This is meant to be called from message mode's 'passed' flag on message we have forwarded. This uses
`mua/msg-reply-uid' and `mua/msg-forward-uid', repectively.
NOTE: This does not handle the case yet of message which are
edited from drafts. That case could be solved by searching for
the In-Reply-To message-id for replies.
This is meant to be called from message mode's
`message-sent-hook'." `message-sent-hook'."
(if (mua/msg-is-mua-message) ;; only if we are mua ;; handle the replied-to message
(let ((msgid (mail-header-parse-addresses (when mua/msg-reply-uid
(message-field-value "In-Reply-To"))) (let* ((oldflags (mua/msg-file-flags-from-path (mua/msg-file-get-path uid)))
(path (and msgid (mua/mu-run ;; TODO: check we only get one msgid back (newflags (cons 'replied oldflags)))
"find" (concat "msgid:" msgid) "--exec=echo")))) (mua/msg-file-move uid nil newflags)))
(if path ;; handle the forwarded message
(let ((newflags (cons 'replied (mua/maildir-flags-from-path path)))) (when mua/msg-forward-uid
(mua/msg-move path (mua/maildir-from-path path t) newflags)))))) (let* ((oldflags (mua/msg-file-flags-from-path (mua/msg-file-get-path uid)))
(newflags (cons 'passed oldflags)))
(mua/msg-file-move uid nil newflags))))
;; hook our functions up with sending of the message ;; hook our functions up with sending of the message
(add-hook 'message-sent-hook 'mua/msg-save-to-sent) (add-hook 'message-sent-hook 'mua/msg-save-to-sent)
(add-hook 'message-sent-hook 'mua/msg-set-replied-flag) (add-hook 'message-sent-hook 'mua/msg-set-replied-or-passed-flag)
(provide 'mua-msg) (provide 'mua-msg)

View File

@ -59,36 +59,53 @@ to get it"
(match-string 1 (cdr rv)) (match-string 1 (cdr rv))
(mua/warn "Failed to get version string")))) (mua/warn "Failed to get version string"))))
(defun mua/mu-mv (src target &optional flags) (defun mua/mu-mv (src target flags)
"Move a message at PATH to TARGET using 'mu mv'. SRC must be "Move a message at PATH to TARGET using 'mu mv'. SRC must be
the full, absolute path to a message file, while TARGET must the full, absolute path to a message file, while TARGET must be a
be a maildir - that is, the part _without_ cur/ or new/. 'mu mv' maildir - that is, the part _without_ cur/ or new/. FLAGS sets
will calculate the target directory and the exact file name. the flags of the message.
Optionally, you can specify the FLAGS for the new file; this must TARGET can be nil, in which case only the flags are
be a list consisting of one or more of DFNPRST, mean changed (which on the file-system level still implies a rename or
resp. Deleted, Flagged, New, Passed Replied, Seen and Trash, as even a move if directory if the 'new' flags is added or
defined in [1]. See `mua/maildir-string-to-flags' and removed). FLAGS can also be nil, in which they are not changed.
`mua/maildir-flags-to-string'. If both TARGET and FLAGS are nil, nothing happens.
'mu mv' will calculate the full path to target directory and file
based on SRC, TARGET and FLAGS.
FLAGS must be either nil or a list consisting of one or more of
DFNPRST, mean resp. Deleted, Flagged, New, Passed Replied, Seen
and Trash, as defined in [1]. See `mua/msg-file-string-to-flags'
and `mua/msg-file-flags-to-string'.
Function returns the target filename if the move succeeds, or Function returns the target filename if the move succeeds, or
/dev/null if TARGETDIR was /dev/null; in other cases, it returns /dev/null if TARGETDIR was /dev/null; in other cases, it returns
`nil'. `nil'.
\[1\] http://cr.yp.to/proto/maildir.html." \[1\] http://cr.yp.to/proto/maildir.html."
(let ((flagstr
(and flags (mua/maildir-flags-to-string flags)))) ;; precondition
(if (not (file-readable-p src)) (unless (or target flags) (error "Either target or flags must
(mua/warn "Cannot move unreadable file %s" src) be provided."))
(let* ((rv (if flagstr
(mua/mu-run "mv" "--printtarget" (if (not (file-readable-p src))
(concat "--flags=" flagstr) src target) (mua/warn "Cannot move unreadable file %s" src)
(mua/mu-run "mv" "--printtarget" src target))) (let ((argl '("mv" "--printtarget")))
(when flags (add-to-list 'argl (concat "--flags="
(mua/msg-file-flags-to-string flags)) t))
(add-to-list 'argl src t)
(when target (add-to-list 'argl target t))
(let* ((rv (apply 'mua/mu-run argl))
(code (car rv)) (output (cdr rv))) (code (car rv)) (output (cdr rv)))
(if (/= 0 code) ;; we ignore the error where the target file already exists, as it is
;; likely due to the database not being fully up-to-date and/or sync'ed
;; with what we have on the screen
(if (not (member code `(0 ,mu-error-file-target-equals-source)))
(mua/warn "Moving message file failed: %s" (if output output "error")) (mua/warn "Moving message file failed: %s" (if output output "error"))
(substring output 0 -1)))))) ;; the full target path, minus the \n (substring output 0 -1)))))) ;; the full target path, minus the \n
(defun mua/mu-view-sexp (path) (defun mua/mu-view-sexp (path)
"Return a string with an s-expression representing the message "Return a string with an s-expression representing the message
at PATH; the format is described in `mua/msg-from-string', and at PATH; the format is described in `mua/msg-from-string', and
@ -198,36 +215,38 @@ them."
(defconst mu-error-file-stat-failed 77) (defconst mu-error-file-stat-failed 77)
(defconst mu-error-file-readdir-failed 78) (defconst mu-error-file-readdir-failed 78)
(defconst mu-error-file-invalid-source 79) (defconst mu-error-file-invalid-source 79)
(defconst mu-error-file-target-equals-source 80)
(defun mua/mu-error (err) (defun mua/mu-error (err)
"Convert an exit code from mu into a string." "Convert an exit code from mu into a string."
(case err (cond
(mu-error "General error") ((eql err mu-error) "General error")
(mu-error-in-parameters "Error in parameters") ((eql err mu-error-in-parameters) "Error in parameters")
(mu-error-internal "Internal error") ((eql err mu-error-internal) "Internal error")
(mu-error-no-matches "No matches") ((eql err mu-error-no-matches) "No matches")
(mu-error-xapian "Xapian error") ((eql err mu-error-xapian) "Xapian error")
(mu-error-xapian-query "Error in query") ((eql err mu-error-xapian-query) "Error in query")
(mu-error-xapian-dir-not-accessible "Database dir is not accessible") ((eql err mu-error-xapian-dir-not-accessible) "Database dir not accessible")
(mu-error-xapian-not-up-to-date "Database is not up-to-date") ((eql err mu-error-xapian-not-up-to-date) "Database is not up-to-date")
(mu-error-xapian-missing-data "Missing data") ((eql err mu-error-xapian-missing-data) "Missing data")
(mu-error-xapian-corruption "Database seems to be corrupted") ((eql err mu-error-xapian-corruption) "Database seems to be corrupted")
(mu-error-xapian-cannot-get-writelock "Database is locked") ((eql err mu-error-xapian-cannot-get-writelock) "Database is locked")
(mu-error-gmime "GMime-related error") ((eql err mu-error-gmime) "GMime-related error")
(mu-error-contacts "Contacts-related error") ((eql err mu-error-contacts) "Contacts-related error")
(mu-error-contacts-cannot-retrieve "Failed to retrieve contacts-cache") ((eql err mu-error-contacts-cannot-retrieve) "Failed to retrieve contacts")
(mu-error-file "File error") ((eql err mu-error-file) "File error")
(mu-error-file-invalid-name "Invalid file name") ((eql err mu-error-file-invalid-name) "Invalid file name")
(mu-error-file-cannot-link "Failed to link file") ((eql err mu-error-file-cannot-link) "Failed to link file")
(mu-error-file-cannot-open "Cannot open file") ((eql err mu-error-file-cannot-open) "Cannot open file")
(mu-error-file-cannot-read "Cannot read file") ((eql err mu-error-file-cannot-read) "Cannot read file")
(mu-error-file-cannot-create "Cannot create file") ((eql err mu-error-file-cannot-create) "Cannot create file")
(mu-error-file-cannot-mkdir "mu-mkdir failed") ((eql err mu-error-file-cannot-mkdir) "mu-mkdir failed")
(mu-error-file-stat-failed "stat(2) failed") ((eql err mu-error-file-stat-failed) "stat(2) failed")
(mu-error-file-readdir-failed "readdir failed") ((eql err mu-error-file-readdir-failed) "readdir failed")
(mu-error-file-invalid-source "Invalid source file") ((eql err mu-error-file-invalid-source) "Invalid source file")
(t "Unknown error"))) ((eql err mu-error-file-target-equals-source) "Source is same as target")
(t (format "Unknown error (%d)" err))))
(provide 'mua-mu) (provide 'mua-mu)

View File

@ -38,53 +38,49 @@
"buffer name for mua/view buffers") "buffer name for mua/view buffers")
(defvar mua/view-headers (defvar mua/view-headers
'(:from :to :cc :subject :flags :date :maildir :attachments) '(:from :to :cc :subject :flags :date :maildir :path :attachments)
"fields to display in the message view") "Fields to display in the message view buffer.")
(defvar mua/hdrs-buffer nil (defvar mua/hdrs-buffer nil
"headers buffer for the view") "Headers buffer for the view in this buffer.")
(defun mua/view (path headersbuf) (defvar mua/view-uid nil
"display message at PATH in a new buffer; note that the action "The UID for the message being viewed in this buffer.")
of viewing a message may cause it to be moved/renamed; this
function returns the resulting name. PARENTBUF refers to the
(defun mua/view (uid headersbuf)
"display message identified by UID in a new buffer. Note that
the action of viewing a message may cause it to be moved/renamed;
this function returns the resulting name. PARENTBUF refers to the
buffer who invoked this view; this allows us to return there when buffer who invoked this view; this allows us to return there when
we quit from this view. Also, if PARENTBUF is a find buffer (ie., we quit from this view. Also, if PARENTBUF is a find buffer (ie.,
has mu-headers-mode as its major mode), this allows various has mu-headers-mode as its major mode), this allows various
commands (navigation, marking etc.) to be applied to this commands (navigation, marking etc.) to be applied to this
buffer." buffer.
(let* ((sexp (mua/mu-view-sexp path))
For the reasoning to use UID here instead of just the path, see
`mua/msg-file-map'.
"
(let* ((path (mua/msg-file-get-path uid))
(sexp (and path (mua/mu-view-sexp path)))
(msg (and sexp (mua/msg-from-string sexp)))) (msg (and sexp (mua/msg-from-string sexp))))
(when msg (if (not msg)
(switch-to-buffer (get-buffer-create mua/view-buffer-name)) (mua/warn "Cannot view message %S %S" uid path)
(let ((inhibit-read-only t)) (progn
(erase-buffer) (switch-to-buffer (get-buffer-create mua/view-buffer-name))
(insert (mua/view-message msg))) (let ((inhibit-read-only t))
(erase-buffer)
(insert (mua/view-message msg)))
(mua/view-mode) (mua/view-mode)
(setq ;; these are buffer-local (setq ;; these are buffer-local
mua/hdrs-buffer headersbuf mua/view-uid uid
mua/parent-buffer headersbuf) mua/hdrs-buffer headersbuf
(goto-char (point-min)) mua/parent-buffer headersbuf)
(mua/view-mark-as-read path))))
(defun mua/view-mark-as-read (path) (goto-char (point-min))
"Mark the currently viewed as read if it is not so already. In (mua/msg-file-mark-as-read uid)))))
Maildir terms, this means moving the message from \"new/\" to
\"cur/\" (if it's not yet there), and setting the \"S\" flag."
(let ((flags (mua/maildir-flags-from-path path)))
(unless (member 'seen flags) ;; do we need to do something?
(let* ((newflags (delq 'new (cons 'seen flags)))
(target (mua/maildir-from-path path t))
(newpath (mua/msg-move path target flags)))
;; now, attempt to update our parent header list...
(if newpath
(mua/with-hdrs-buffer
(if (string= (mua/hdrs-get-path) path) ;; doublecheck we have the right one
(mua/hdrs-set-path newpath)
(mua/warn "Headers buffer not point at correct message")))
(mua/warn "Failed to mark message as read"))))))
(defun mua/view-message (msg) (defun mua/view-message (msg)
"construct a display string for the message" "construct a display string for the message"
@ -157,7 +153,6 @@ buffer."
) )
(defvar mua/view-mode-map (defvar mua/view-mode-map
(let ((map (make-sparse-keymap))) (let ((map (make-sparse-keymap)))
(define-key map "q" 'mua/quit-buffer) (define-key map "q" 'mua/quit-buffer)
@ -172,10 +167,10 @@ buffer."
(define-key map "p" 'mua/view-prev) (define-key map "p" 'mua/view-prev)
;; marking/unmarking ;; marking/unmarking
(define-key map "d" '(lambda (mua/view-mark 'trash))) (define-key map "d" '(lambda() (mua/view-mark 'trash)))
(define-key map "D" '(lambda (mua/view-mark 'delete))) (define-key map "D" '(lambda() (mua/view-mark 'delete)))
(define-key map "m" '(lambda (mua/view-mark 'move))) (define-key map "m" '(lambda() (mua/view-mark 'move)))
(define-key map "u" '(lambda (mua/view-mark 'unmark))) (define-key map "u" '(lambda() (mua/view-mark 'unmark)))
(define-key map "x" 'mua/view-marked-execute) (define-key map "x" 'mua/view-marked-execute)
map) map)
"Keymap for \"*mua-view*\" buffers.") "Keymap for \"*mua-view*\" buffers.")
@ -189,7 +184,7 @@ buffer."
(make-local-variable 'mua/parent-buffer) (make-local-variable 'mua/parent-buffer)
(make-local-variable 'mua/hdrs-buffer) (make-local-variable 'mua/hdrs-buffer)
(make-local-variable 'mua/path) (make-local-variable 'mua/view-uid)
(setq major-mode 'mua/view-mode mode-name "*mu-view*") (setq major-mode 'mua/view-mode mode-name "*mu-view*")
(setq truncate-lines t buffer-read-only t)) (setq truncate-lines t buffer-read-only t))
@ -208,6 +203,25 @@ etc. persist."
(set-buffer oldbuf)) (set-buffer oldbuf))
(mua/warn "hdrs buffer is dead")))) (mua/warn "hdrs buffer is dead"))))
(defun mua/view-mark (action)
"Set/unset marks for the current message."
(interactive)
(mua/with-hdrs-buffer (mua/hdrs-mark action)))
(defun mua/view-marked-execute ()
"Warn user that marks cannot be executed from here (for his/her
own safety)."
(interactive)
(mua/warn "You cannot execute marks from here"))
(defun mua/view-search()
"Start a new search."
(interactive)
(mua/with-hdrs-buffer
(call-interactively 'mua/hdrs-search)))
(defun mua/view-next () (defun mua/view-next ()
"move to the next message; note, this will replace the current "move to the next message; note, this will replace the current
buffer" buffer"

View File

@ -35,6 +35,8 @@
(require 'mua-msg) (require 'mua-msg)
(require 'mua-hdrs) (require 'mua-hdrs)
(require 'mua-view) (require 'mua-view)
(require 'mua-msg-file)
(defvar mua/mu-home nil "location of the mu homedir, or nil for (defvar mua/mu-home nil "location of the mu homedir, or nil for
the default") the default")
@ -82,6 +84,7 @@ quitted, it switches back to its parent buffer")
(defface mua/body-face '((t (:foreground "#8cd0d3"))) "") (defface mua/body-face '((t (:foreground "#8cd0d3"))) "")
(setq mua/hdrs-mode-map (setq mua/hdrs-mode-map
(let ((map (make-sparse-keymap))) (let ((map (make-sparse-keymap)))
@ -146,7 +149,6 @@ quitted, it switches back to its parent buffer")
(switch-to-buffer buf) (switch-to-buffer buf)
(mua/mua-mode))) (mua/mua-mode)))
(defvar mua/mua-mode-map (defvar mua/mua-mode-map
(let ((map (make-sparse-keymap))) (let ((map (make-sparse-keymap)))