aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorGnus developers2012-09-05 22:35:32 +0000
committerKatsumi Yamaoka2012-09-05 22:35:32 +0000
commit350a188850c4eeceab0220ba1f6fdf98f0f57e93 (patch)
treeef47da20a533cc6bb63e46b6a6d1b08f28d0fc1e
parent20ef56dbc88f517ebf60d89577fc89870d9fe888 (diff)
downloademacs-350a188850c4eeceab0220ba1f6fdf98f0f57e93.tar.gz
emacs-350a188850c4eeceab0220ba1f6fdf98f0f57e93.zip
Merge changes made in Gnus master
2012-09-05 Julien Danjou <julien@danjou.info> * gnus-srvr.el (gnus-server-open-server): Don't message on failure: this hide the real reason with a message giving absolutely no hint. 2012-09-05 Lars Ingebrigtsen <larsi@gnus.org> * gnus-group.el (gnus-group-mark-article-read): Propagate the read mark to the backend (bug#11804). * message.el (message-insert-newsgroups): Don't insert newsgroup duplicates (bug#12275). 2012-09-05 John Wiegley <johnw@newartisans.com> * gnus.el (gnus-expand-group-parameters): Allow regexp substitutions in sieve rules. 2012-09-05 Jan Tatarik <jan.tatarik@gmail.com> * gnus-score.el (gnus-score-decode-text-parts): Use #' for the local function. * gnus-logic.el (gnus-advanced-body): Allow scoring on decoded bodies. * gnus-score.el (gnus-score-decode-text-parts): Ditto. 2012-09-05 Magnus Henoch <magnus.henoch@gmail.com> * nnmaildir.el: Make nnmaildir understand and write maildir flags. That is, rename files from "unique:2," to "unique:2,S" for "seen", etc. This should make nnmaildir more usable with offlineimap.
-rw-r--r--lisp/gnus/ChangeLog33
-rw-r--r--lisp/gnus/gnus-group.el2
-rw-r--r--lisp/gnus/gnus-logic.el73
-rw-r--r--lisp/gnus/gnus-score.el231
-rw-r--r--lisp/gnus/gnus-srvr.el3
-rw-r--r--lisp/gnus/gnus.el45
-rw-r--r--lisp/gnus/message.el32
-rw-r--r--lisp/gnus/nnmaildir.el286
8 files changed, 487 insertions, 218 deletions
diff --git a/lisp/gnus/ChangeLog b/lisp/gnus/ChangeLog
index 0587469a08e..c6220204511 100644
--- a/lisp/gnus/ChangeLog
+++ b/lisp/gnus/ChangeLog
@@ -1,3 +1,36 @@
12012-09-05 Julien Danjou <julien@danjou.info>
2
3 * gnus-srvr.el (gnus-server-open-server): Don't message on failure:
4 this hide the real reason with a message giving absolutely no hint.
5
62012-09-05 Lars Ingebrigtsen <larsi@gnus.org>
7
8 * gnus-group.el (gnus-group-mark-article-read): Propagate the read mark
9 to the backend (bug#11804).
10
11 * message.el (message-insert-newsgroups): Don't insert newsgroup
12 duplicates (bug#12275).
13
142012-09-05 John Wiegley <johnw@newartisans.com>
15
16 * gnus.el (gnus-expand-group-parameters): Allow regexp substitutions in
17 sieve rules.
18
192012-09-05 Jan Tatarik <jan.tatarik@gmail.com>
20
21 * gnus-score.el (gnus-score-decode-text-parts): Use #' for the local
22 function.
23
24 * gnus-logic.el (gnus-advanced-body): Allow scoring on decoded bodies.
25
26 * gnus-score.el (gnus-score-decode-text-parts): Ditto.
27
282012-09-05 Magnus Henoch <magnus.henoch@gmail.com>
29
30 * nnmaildir.el: Make nnmaildir understand and write maildir flags.
31 That is, rename files from "unique:2," to "unique:2,S" for "seen", etc.
32 This should make nnmaildir more usable with offlineimap.
33
12012-09-03 Lars Ingebrigtsen <larsi@gnus.org> 342012-09-03 Lars Ingebrigtsen <larsi@gnus.org>
2 35
3 * gnus-notifications.el (gnus-notifications-notify): Use it. 36 * gnus-notifications.el (gnus-notifications-notify): Use it.
diff --git a/lisp/gnus/gnus-group.el b/lisp/gnus/gnus-group.el
index 2f6fc0ccd19..37e4470239e 100644
--- a/lisp/gnus/gnus-group.el
+++ b/lisp/gnus/gnus-group.el
@@ -4670,6 +4670,8 @@ you the groups that have both dormant articles and cached articles."
4670 (setq mark gnus-expirable-mark)) 4670 (setq mark gnus-expirable-mark))
4671 (setq mark (gnus-request-update-mark 4671 (setq mark (gnus-request-update-mark
4672 group article mark)) 4672 group article mark))
4673 (gnus-request-set-mark
4674 group (list (list (list article) 'add '(read))))
4673 (gnus-mark-article-as-read article mark) 4675 (gnus-mark-article-as-read article mark)
4674 (setq gnus-newsgroup-active (gnus-active group)) 4676 (setq gnus-newsgroup-active (gnus-active group))
4675 (when active 4677 (when active
diff --git a/lisp/gnus/gnus-logic.el b/lisp/gnus/gnus-logic.el
index 954295438c9..a440b779930 100644
--- a/lisp/gnus/gnus-logic.el
+++ b/lisp/gnus/gnus-logic.el
@@ -180,46 +180,51 @@
180 (setq header "article")) 180 (setq header "article"))
181 (with-current-buffer nntp-server-buffer 181 (with-current-buffer nntp-server-buffer
182 (let* ((request-func (cond ((string= "head" header) 182 (let* ((request-func (cond ((string= "head" header)
183 'gnus-request-head) 183 'gnus-request-head)
184 ((string= "body" header) 184 ;; We need to peek at the headers to detect the
185 'gnus-request-body) 185 ;; content encoding
186 (t 'gnus-request-article))) 186 ((string= "body" header)
187 ofunc article) 187 'gnus-request-article)
188 (t 'gnus-request-article)))
189 ofunc article handles)
188 ;; Not all backends support partial fetching. In that case, we 190 ;; Not all backends support partial fetching. In that case, we
189 ;; just fetch the entire article. 191 ;; just fetch the entire article.
190 (unless (gnus-check-backend-function 192 (unless (gnus-check-backend-function
191 (intern (concat "request-" header)) 193 (intern (concat "request-" header))
192 gnus-newsgroup-name) 194 gnus-newsgroup-name)
193 (setq ofunc request-func) 195 (setq ofunc request-func)
194 (setq request-func 'gnus-request-article)) 196 (setq request-func 'gnus-request-article))
195 (setq article (mail-header-number gnus-advanced-headers)) 197 (setq article (mail-header-number gnus-advanced-headers))
196 (gnus-message 7 "Scoring article %s..." article) 198 (gnus-message 7 "Scoring article %s..." article)
197 (when (funcall request-func article gnus-newsgroup-name) 199 (when (funcall request-func article gnus-newsgroup-name)
198 (goto-char (point-min)) 200 (when (string= "body" header)
199 ;; If just parts of the article is to be searched and the 201 (setq handles (gnus-score-decode-text-parts)))
200 ;; backend didn't support partial fetching, we just narrow to 202 (goto-char (point-min))
201 ;; the relevant parts. 203 ;; If just parts of the article is to be searched and the
202 (when ofunc 204 ;; backend didn't support partial fetching, we just narrow to
203 (if (eq ofunc 'gnus-request-head) 205 ;; the relevant parts.
204 (narrow-to-region 206 (when ofunc
205 (point) 207 (if (eq ofunc 'gnus-request-head)
206 (or (search-forward "\n\n" nil t) (point-max))) 208 (narrow-to-region
207 (narrow-to-region 209 (point)
208 (or (search-forward "\n\n" nil t) (point)) 210 (or (search-forward "\n\n" nil t) (point-max)))
209 (point-max)))) 211 (narrow-to-region
210 (let* ((case-fold-search (not (eq (downcase (symbol-name type)) 212 (or (search-forward "\n\n" nil t) (point))
211 (symbol-name type)))) 213 (point-max))))
212 (search-func 214 (let* ((case-fold-search (not (eq (downcase (symbol-name type))
213 (cond ((memq type '(r R regexp Regexp)) 215 (symbol-name type))))
214 're-search-forward) 216 (search-func
215 ((memq type '(s S string String)) 217 (cond ((memq type '(r R regexp Regexp))
216 'search-forward) 218 're-search-forward)
217 (t 219 ((memq type '(s S string String))
218 (error "Invalid match type: %s" type))))) 220 'search-forward)
219 (goto-char (point-min)) 221 (t
220 (prog1 222 (error "Invalid match type: %s" type)))))
221 (funcall search-func match nil t) 223 (goto-char (point-min))
222 (widen))))))) 224 (prog1
225 (funcall search-func match nil t)
226 (widen)))
227 (when handles (mm-destroy-parts handles))))))
223 228
224(provide 'gnus-logic) 229(provide 'gnus-logic)
225 230
diff --git a/lisp/gnus/gnus-score.el b/lisp/gnus/gnus-score.el
index f24d889216e..bc35cf3dea5 100644
--- a/lisp/gnus/gnus-score.el
+++ b/lisp/gnus/gnus-score.el
@@ -1717,105 +1717,140 @@ score in `gnus-newsgroup-scored' by SCORE."
1717 (setq entries rest))))) 1717 (setq entries rest)))))
1718 nil) 1718 nil)
1719 1719
1720(defun gnus-score-decode-text-parts ()
1721 (labels ((mm-text-parts (handle)
1722 (cond ((stringp (car handle))
1723 (let ((parts (mapcan 'mm-text-parts (cdr handle))))
1724 (if (equal "multipart/alternative" (car handle))
1725 ;; pick the first supported alternative
1726 (list (car parts))
1727 parts)))
1728
1729 ((bufferp (car handle))
1730 (when (string-match "^text/" (mm-handle-media-type handle))
1731 (list handle)))
1732
1733 (t (mapcan 'mm-text-parts handle))))
1734 (my-mm-display-part (handle)
1735 (when handle
1736 (save-restriction
1737 (narrow-to-region (point) (point))
1738 (mm-display-inline handle)
1739 (goto-char (point-max))))))
1740
1741 (let (;(mm-text-html-renderer 'w3m-standalone)
1742 (handles (mm-dissect-buffer t)))
1743 (save-excursion
1744 (article-goto-body)
1745 (delete-region (point) (point-max))
1746 (mapc #'my-mm-display-part (mm-text-parts handles))
1747 handles))))
1748
1720(defun gnus-score-body (scores header now expire &optional trace) 1749(defun gnus-score-body (scores header now expire &optional trace)
1721 (if gnus-agent-fetching 1750 (if gnus-agent-fetching
1722 nil 1751 nil
1723 (save-excursion 1752 (save-excursion
1724 (setq gnus-scores-articles 1753 (setq gnus-scores-articles
1725 (sort gnus-scores-articles 1754 (sort gnus-scores-articles
1726 (lambda (a1 a2) 1755 (lambda (a1 a2)
1727 (< (mail-header-number (car a1)) 1756 (< (mail-header-number (car a1))
1728 (mail-header-number (car a2)))))) 1757 (mail-header-number (car a2))))))
1729 (set-buffer nntp-server-buffer) 1758 (set-buffer nntp-server-buffer)
1730 (save-restriction 1759 (save-restriction
1731 (let* ((buffer-read-only nil) 1760 (let* ((buffer-read-only nil)
1732 (articles gnus-scores-articles) 1761 (articles gnus-scores-articles)
1733 (all-scores scores) 1762 (all-scores scores)
1734 (request-func (cond ((string= "head" header) 1763 (request-func (cond ((string= "head" header)
1735 'gnus-request-head) 1764 'gnus-request-head)
1736 ((string= "body" header) 1765 ;; We need to peek at the headers to detect
1737 'gnus-request-body) 1766 ;; the content encoding
1738 (t 'gnus-request-article))) 1767 ((string= "body" header)
1739 entries alist ofunc article last) 1768 'gnus-request-article)
1740 (when articles 1769 (t 'gnus-request-article)))
1741 (setq last (mail-header-number (caar (last articles)))) 1770 entries alist ofunc article last)
1742 ;; Not all backends support partial fetching. In that case, 1771 (when articles
1743 ;; we just fetch the entire article. 1772 (setq last (mail-header-number (caar (last articles))))
1744 (unless (gnus-check-backend-function 1773 ;; Not all backends support partial fetching. In that case,
1745 (and (string-match "^gnus-" (symbol-name request-func)) 1774 ;; we just fetch the entire article.
1746 (intern (substring (symbol-name request-func) 1775 (unless (gnus-check-backend-function
1747 (match-end 0)))) 1776 (and (string-match "^gnus-" (symbol-name request-func))
1748 gnus-newsgroup-name) 1777 (intern (substring (symbol-name request-func)
1749 (setq ofunc request-func) 1778 (match-end 0))))
1750 (setq request-func 'gnus-request-article)) 1779 gnus-newsgroup-name)
1751 (while articles 1780 (setq ofunc request-func)
1752 (setq article (mail-header-number (caar articles))) 1781 (setq request-func 'gnus-request-article))
1753 (gnus-message 7 "Scoring article %s of %s..." article last) 1782 (while articles
1754 (widen) 1783 (setq article (mail-header-number (caar articles)))
1755 (when (funcall request-func article gnus-newsgroup-name) 1784 (gnus-message 7 "Scoring article %s of %s..." article last)
1756 (goto-char (point-min)) 1785 (widen)
1757 ;; If just parts of the article is to be searched, but the 1786 (let (handles)
1758 ;; backend didn't support partial fetching, we just narrow 1787 (when (funcall request-func article gnus-newsgroup-name)
1759 ;; to the relevant parts. 1788 (when (string= "body" header)
1760 (when ofunc 1789 (setq handles (gnus-score-decode-text-parts)))
1761 (if (eq ofunc 'gnus-request-head) 1790 (goto-char (point-min))
1762 (narrow-to-region 1791 ;; If just parts of the article is to be searched, but the
1763 (point) 1792 ;; backend didn't support partial fetching, we just narrow
1764 (or (search-forward "\n\n" nil t) (point-max))) 1793 ;; to the relevant parts.
1765 (narrow-to-region 1794 (when ofunc
1766 (or (search-forward "\n\n" nil t) (point)) 1795 (if (eq ofunc 'gnus-request-head)
1767 (point-max)))) 1796 (narrow-to-region
1768 (setq scores all-scores) 1797 (point)
1769 ;; Find matches. 1798 (or (search-forward "\n\n" nil t) (point-max)))
1770 (while scores 1799 (narrow-to-region
1771 (setq alist (pop scores) 1800 (or (search-forward "\n\n" nil t) (point))
1772 entries (assoc header alist)) 1801 (point-max))))
1773 (while (cdr entries) ;First entry is the header index. 1802 (setq scores all-scores)
1774 (let* ((rest (cdr entries)) 1803 ;; Find matches.
1775 (kill (car rest)) 1804 (while scores
1776 (match (nth 0 kill)) 1805 (setq alist (pop scores)
1777 (type (or (nth 3 kill) 's)) 1806 entries (assoc header alist))
1778 (score (or (nth 1 kill) 1807 (while (cdr entries) ;First entry is the header index.
1779 gnus-score-interactive-default-score)) 1808 (let* ((rest (cdr entries))
1780 (date (nth 2 kill)) 1809 (kill (car rest))
1781 (found nil) 1810 (match (nth 0 kill))
1782 (case-fold-search 1811 (type (or (nth 3 kill) 's))
1783 (not (or (eq type 'R) (eq type 'S) 1812 (score (or (nth 1 kill)
1784 (eq type 'Regexp) (eq type 'String)))) 1813 gnus-score-interactive-default-score))
1785 (search-func 1814 (date (nth 2 kill))
1786 (cond ((or (eq type 'r) (eq type 'R) 1815 (found nil)
1787 (eq type 'regexp) (eq type 'Regexp)) 1816 (case-fold-search
1788 're-search-forward) 1817 (not (or (eq type 'R) (eq type 'S)
1789 ((or (eq type 's) (eq type 'S) 1818 (eq type 'Regexp) (eq type 'String))))
1790 (eq type 'string) (eq type 'String)) 1819 (search-func
1791 'search-forward) 1820 (cond ((or (eq type 'r) (eq type 'R)
1792 (t 1821 (eq type 'regexp) (eq type 'Regexp))
1793 (error "Invalid match type: %s" type))))) 1822 're-search-forward)
1794 (goto-char (point-min)) 1823 ((or (eq type 's) (eq type 'S)
1795 (when (funcall search-func match nil t) 1824 (eq type 'string) (eq type 'String))
1796 ;; Found a match, update scores. 1825 'search-forward)
1797 (setcdr (car articles) (+ score (cdar articles))) 1826 (t
1798 (setq found t) 1827 (error "Invalid match type: %s" type)))))
1799 (when trace 1828 (goto-char (point-min))
1800 (push 1829 (when (funcall search-func match nil t)
1801 (cons (car-safe (rassq alist gnus-score-cache)) 1830 ;; Found a match, update scores.
1802 kill) 1831 (setcdr (car articles) (+ score (cdar articles)))
1803 gnus-score-trace))) 1832 (setq found t)
1804 ;; Update expire date 1833 (when trace
1805 (unless trace 1834 (push
1806 (cond 1835 (cons (car-safe (rassq alist gnus-score-cache))
1807 ((null date)) ;Permanent entry. 1836 kill)
1808 ((and found gnus-update-score-entry-dates) 1837 gnus-score-trace)))
1809 ;; Match, update date. 1838 ;; Update expire date
1810 (gnus-score-set 'touched '(t) alist) 1839 (unless trace
1811 (setcar (nthcdr 2 kill) now)) 1840 (cond
1812 ((and expire (< date expire)) ;Old entry, remove. 1841 ((null date)) ;Permanent entry.
1813 (gnus-score-set 'touched '(t) alist) 1842 ((and found gnus-update-score-entry-dates)
1814 (setcdr entries (cdr rest)) 1843 ;; Match, update date.
1815 (setq rest entries)))) 1844 (gnus-score-set 'touched '(t) alist)
1816 (setq entries rest))))) 1845 (setcar (nthcdr 2 kill) now))
1817 (setq articles (cdr articles))))))) 1846 ((and expire (< date expire)) ;Old entry, remove.
1818 nil)) 1847 (gnus-score-set 'touched '(t) alist)
1848 (setcdr entries (cdr rest))
1849 (setq rest entries))))
1850 (setq entries rest))))
1851 (when handles (mm-destroy-parts handles))))
1852 (setq articles (cdr articles)))))))
1853 nil))
1819 1854
1820(defun gnus-score-thread (scores header now expire &optional trace) 1855(defun gnus-score-thread (scores header now expire &optional trace)
1821 (gnus-score-followup scores header now expire trace t)) 1856 (gnus-score-followup scores header now expire trace t))
diff --git a/lisp/gnus/gnus-srvr.el b/lisp/gnus/gnus-srvr.el
index 66509c939dc..f58cb80311a 100644
--- a/lisp/gnus/gnus-srvr.el
+++ b/lisp/gnus/gnus-srvr.el
@@ -490,8 +490,7 @@ The following commands are available:
490 (error "No such server: %s" server)) 490 (error "No such server: %s" server))
491 (gnus-server-set-status method 'ok) 491 (gnus-server-set-status method 'ok)
492 (prog1 492 (prog1
493 (or (gnus-open-server method) 493 (gnus-open-server method)
494 (progn (message "Couldn't open %s" server) nil))
495 (gnus-server-update-server server) 494 (gnus-server-update-server server)
496 (gnus-server-position-point)))) 495 (gnus-server-position-point))))
497 496
diff --git a/lisp/gnus/gnus.el b/lisp/gnus/gnus.el
index 5862e7807a2..8fbde5c8ecc 100644
--- a/lisp/gnus/gnus.el
+++ b/lisp/gnus/gnus.el
@@ -3824,12 +3824,28 @@ You should probably use `gnus-find-method-for-group' instead."
3824 "Go through PARAMETERS and expand them according to the match data." 3824 "Go through PARAMETERS and expand them according to the match data."
3825 (let (new) 3825 (let (new)
3826 (dolist (elem parameters) 3826 (dolist (elem parameters)
3827 (if (and (stringp (cdr elem)) 3827 (cond
3828 (string-match "\\\\[0-9&]" (cdr elem))) 3828 ((and (stringp (cdr elem))
3829 (push (cons (car elem) 3829 (string-match "\\\\[0-9&]" (cdr elem)))
3830 (gnus-expand-group-parameter match (cdr elem) group)) 3830 (push (cons (car elem)
3831 new) 3831 (gnus-expand-group-parameter match (cdr elem) group))
3832 (push elem new))) 3832 new))
3833 ;; For `sieve' group parameters, perform substitutions for every
3834 ;; string within the match rule. This allows for parameters such
3835 ;; as:
3836 ;; ("list\\.\\(.*\\)"
3837 ;; (sieve header :is "list-id" "<\\1.domain.org>"))
3838 ((eq 'sieve (car elem))
3839 (push (mapcar (lambda (sieve-elem)
3840 (if (and (stringp sieve-elem)
3841 (string-match "\\\\[0-9&]" sieve-elem))
3842 (gnus-expand-group-parameter match sieve-elem
3843 group)
3844 sieve-elem))
3845 (cdr elem))
3846 new))
3847 (t
3848 (push elem new))))
3833 new)) 3849 new))
3834 3850
3835(defun gnus-group-fast-parameter (group symbol &optional allow-list) 3851(defun gnus-group-fast-parameter (group symbol &optional allow-list)
@@ -3861,9 +3877,20 @@ The function `gnus-group-find-parameter' will do that for you."
3861 (when this-result 3877 (when this-result
3862 (setq result (car this-result)) 3878 (setq result (car this-result))
3863 ;; Expand if necessary. 3879 ;; Expand if necessary.
3864 (if (and (stringp result) (string-match "\\\\[0-9&]" result)) 3880 (cond
3865 (setq result (gnus-expand-group-parameter 3881 ((and (stringp result) (string-match "\\\\[0-9&]" result))
3866 (car head) result group))))))) 3882 (setq result (gnus-expand-group-parameter
3883 (car head) result group)))
3884 ;; For `sieve' group parameters, perform substitutions
3885 ;; for every string within the match rule (see above).
3886 ((eq symbol 'sieve)
3887 (setq result
3888 (mapcar (lambda (elem)
3889 (if (stringp elem)
3890 (gnus-expand-group-parameter (car head)
3891 elem group)
3892 elem))
3893 result))))))))
3867 ;; Done. 3894 ;; Done.
3868 result)))) 3895 result))))
3869 3896
diff --git a/lisp/gnus/message.el b/lisp/gnus/message.el
index 18088423eb0..42911ce0648 100644
--- a/lisp/gnus/message.el
+++ b/lisp/gnus/message.el
@@ -3292,11 +3292,33 @@ or in the synonym headers, defined by `message-header-synonyms'."
3292(defun message-insert-newsgroups () 3292(defun message-insert-newsgroups ()
3293 "Insert the Newsgroups header from the article being replied to." 3293 "Insert the Newsgroups header from the article being replied to."
3294 (interactive) 3294 (interactive)
3295 (when (and (message-position-on-field "Newsgroups") 3295 (let ((old-newsgroups (mail-fetch-field "newsgroups"))
3296 (mail-fetch-field "newsgroups") 3296 (new-newsgroups (message-fetch-reply-field "newsgroups"))
3297 (not (string-match "\\` *\\'" (mail-fetch-field "newsgroups")))) 3297 (first t)
3298 (insert ",")) 3298 insert-newsgroups)
3299 (insert (or (message-fetch-reply-field "newsgroups") ""))) 3299 (message-position-on-field "Newsgroups")
3300 (cond
3301 ((not new-newsgroups)
3302 (error "No Newsgroups to insert"))
3303 ((not old-newsgroups)
3304 (insert new-newsgroups))
3305 (t
3306 (setq new-newsgroups (split-string new-newsgroups "[, ]+")
3307 old-newsgroups (split-string old-newsgroups "[, ]+"))
3308 (dolist (group new-newsgroups)
3309 (unless (member group old-newsgroups)
3310 (push group insert-newsgroups)))
3311 (if (null insert-newsgroups)
3312 (error "Newgroup%s already in the header"
3313 (if (> (length new-newsgroups) 1)
3314 "s" ""))
3315 (when old-newsgroups
3316 (setq first nil))
3317 (dolist (group insert-newsgroups)
3318 (unless first
3319 (insert ","))
3320 (setq first nil)
3321 (insert group)))))))
3300 3322
3301 3323
3302 3324
diff --git a/lisp/gnus/nnmaildir.el b/lisp/gnus/nnmaildir.el
index 7139a528e11..caf28202f04 100644
--- a/lisp/gnus/nnmaildir.el
+++ b/lisp/gnus/nnmaildir.el
@@ -77,6 +77,66 @@
77 77
78(defconst nnmaildir-version "Gnus") 78(defconst nnmaildir-version "Gnus")
79 79
80(defconst nnmaildir-flag-mark-mapping
81 '((?F . tick)
82 (?R . reply)
83 (?S . read))
84 "Alist mapping Maildir filename flags to Gnus marks.
85Maildir filenames are of the form \"unique-id:2,FLAGS\",
86where FLAGS are a string of characters in ASCII order.
87Some of the FLAGS correspond to Gnus marks.")
88
89(defsubst nnmaildir--mark-to-flag (mark)
90 "Find the Maildir flag that corresponds to MARK (an atom).
91Return a character, or `nil' if not found.
92See `nnmaildir-flag-mark-mapping'."
93 (car (rassq mark nnmaildir-flag-mark-mapping)))
94
95(defsubst nnmaildir--flag-to-mark (flag)
96 "Find the Gnus mark that corresponds to FLAG (a character).
97Return an atom, or `nil' if not found.
98See `nnmaildir-flag-mark-mapping'."
99 (cdr (assq flag nnmaildir-flag-mark-mapping)))
100
101(defun nnmaildir--ensure-suffix (filename)
102 "Ensure that FILENAME contains the suffix \":2,\"."
103 (if (string-match-p ":2," filename)
104 filename
105 (concat filename ":2,")))
106
107(defun nnmaildir--add-flag (flag suffix)
108 "Return a copy of SUFFIX where FLAG is set.
109SUFFIX should start with \":2,\"."
110 (unless (string-match-p "^:2," suffix)
111 (error "Invalid suffix `%s'" suffix))
112 (let* ((flags (substring suffix 3))
113 (flags-as-list (append flags nil))
114 (new-flags
115 (concat (gnus-delete-duplicates
116 ;; maildir flags must be sorted
117 (sort (cons flag flags-as-list) '<)))))
118 (concat ":2," new-flags)))
119
120(defun nnmaildir--remove-flag (flag suffix)
121 "Return a copy of SUFFIX where FLAG is cleared.
122SUFFIX should start with \":2,\"."
123 (unless (string-match-p "^:2," suffix)
124 (error "Invalid suffix `%s'" suffix))
125 (let* ((flags (substring suffix 3))
126 (flags-as-list (append flags nil))
127 (new-flags (concat (delq flag flags-as-list))))
128 (concat ":2," new-flags)))
129
130(defun nnmaildir--article-set-flags (article new-suffix curdir)
131 (let* ((prefix (nnmaildir--art-prefix article))
132 (suffix (nnmaildir--art-suffix article))
133 (article-file (concat curdir prefix suffix))
134 (new-name (concat curdir prefix new-suffix)))
135 (unless (file-exists-p article-file)
136 (error "Couldn't find article file %s" article-file))
137 (rename-file article-file new-name 'replace)
138 (setf (nnmaildir--art-suffix article) new-suffix)))
139
80(defvar nnmaildir-article-file-name nil 140(defvar nnmaildir-article-file-name nil
81 "*The filename of the most recently requested article. This variable is set 141 "*The filename of the most recently requested article. This variable is set
82by nnmaildir-request-article.") 142by nnmaildir-request-article.")
@@ -208,29 +268,33 @@ by nnmaildir-request-article.")
208 (eval param)) 268 (eval param))
209 269
210(defmacro nnmaildir--with-nntp-buffer (&rest body) 270(defmacro nnmaildir--with-nntp-buffer (&rest body)
271 (declare (debug (body)))
211 `(with-current-buffer nntp-server-buffer 272 `(with-current-buffer nntp-server-buffer
212 ,@body)) 273 ,@body))
213(defmacro nnmaildir--with-work-buffer (&rest body) 274(defmacro nnmaildir--with-work-buffer (&rest body)
275 (declare (debug (body)))
214 `(with-current-buffer (get-buffer-create " *nnmaildir work*") 276 `(with-current-buffer (get-buffer-create " *nnmaildir work*")
215 ,@body)) 277 ,@body))
216(defmacro nnmaildir--with-nov-buffer (&rest body) 278(defmacro nnmaildir--with-nov-buffer (&rest body)
279 (declare (debug (body)))
217 `(with-current-buffer (get-buffer-create " *nnmaildir nov*") 280 `(with-current-buffer (get-buffer-create " *nnmaildir nov*")
218 ,@body)) 281 ,@body))
219(defmacro nnmaildir--with-move-buffer (&rest body) 282(defmacro nnmaildir--with-move-buffer (&rest body)
283 (declare (debug (body)))
220 `(with-current-buffer (get-buffer-create " *nnmaildir move*") 284 `(with-current-buffer (get-buffer-create " *nnmaildir move*")
221 ,@body)) 285 ,@body))
222 286
223(defmacro nnmaildir--subdir (dir subdir) 287(defsubst nnmaildir--subdir (dir subdir)
224 `(file-name-as-directory (concat ,dir ,subdir))) 288 (file-name-as-directory (concat dir subdir)))
225(defmacro nnmaildir--srvgrp-dir (srv-dir gname) 289(defsubst nnmaildir--srvgrp-dir (srv-dir gname)
226 `(nnmaildir--subdir ,srv-dir ,gname)) 290 (nnmaildir--subdir srv-dir gname))
227(defmacro nnmaildir--tmp (dir) `(nnmaildir--subdir ,dir "tmp")) 291(defsubst nnmaildir--tmp (dir) (nnmaildir--subdir dir "tmp"))
228(defmacro nnmaildir--new (dir) `(nnmaildir--subdir ,dir "new")) 292(defsubst nnmaildir--new (dir) (nnmaildir--subdir dir "new"))
229(defmacro nnmaildir--cur (dir) `(nnmaildir--subdir ,dir "cur")) 293(defsubst nnmaildir--cur (dir) (nnmaildir--subdir dir "cur"))
230(defmacro nnmaildir--nndir (dir) `(nnmaildir--subdir ,dir ".nnmaildir")) 294(defsubst nnmaildir--nndir (dir) (nnmaildir--subdir dir ".nnmaildir"))
231(defmacro nnmaildir--nov-dir (dir) `(nnmaildir--subdir ,dir "nov")) 295(defsubst nnmaildir--nov-dir (dir) (nnmaildir--subdir dir "nov"))
232(defmacro nnmaildir--marks-dir (dir) `(nnmaildir--subdir ,dir "marks")) 296(defsubst nnmaildir--marks-dir (dir) (nnmaildir--subdir dir "marks"))
233(defmacro nnmaildir--num-dir (dir) `(nnmaildir--subdir ,dir "num")) 297(defsubst nnmaildir--num-dir (dir) (nnmaildir--subdir dir "num"))
234 298
235(defmacro nnmaildir--unlink (file-arg) 299(defmacro nnmaildir--unlink (file-arg)
236 `(let ((file ,file-arg)) 300 `(let ((file ,file-arg))
@@ -305,6 +369,7 @@ by nnmaildir-request-article.")
305 string) 369 string)
306 370
307(defmacro nnmaildir--condcase (errsym body &rest handler) 371(defmacro nnmaildir--condcase (errsym body &rest handler)
372 (declare (debug (sexp form body)))
308 `(condition-case ,errsym 373 `(condition-case ,errsym
309 (let ((system-messages-locale "C")) ,body) 374 (let ((system-messages-locale "C")) ,body)
310 (error . ,handler))) 375 (error . ,handler)))
@@ -759,7 +824,7 @@ by nnmaildir-request-article.")
759 (dolist (file (funcall ls ndir nil "\\`[^.]" 'nosort)) 824 (dolist (file (funcall ls ndir nil "\\`[^.]" 'nosort))
760 (setq x (concat ndir file)) 825 (setq x (concat ndir file))
761 (and (time-less-p (nth 5 (file-attributes x)) (current-time)) 826 (and (time-less-p (nth 5 (file-attributes x)) (current-time))
762 (rename-file x (concat cdir file ":2,")))) 827 (rename-file x (concat cdir (nnmaildir--ensure-suffix file)))))
763 (setf (nnmaildir--grp-new group) nattr)) 828 (setf (nnmaildir--grp-new group) nattr))
764 (setq cattr (nth 5 (file-attributes cdir))) 829 (setq cattr (nth 5 (file-attributes cdir)))
765 (if (equal cattr (nnmaildir--grp-cur group)) 830 (if (equal cattr (nnmaildir--grp-cur group))
@@ -784,11 +849,23 @@ by nnmaildir-request-article.")
784 cdir (nnmaildir--marks-dir nndir) 849 cdir (nnmaildir--marks-dir nndir)
785 ndir (nnmaildir--subdir cdir "tick") 850 ndir (nnmaildir--subdir cdir "tick")
786 cdir (nnmaildir--subdir cdir "read")) 851 cdir (nnmaildir--subdir cdir "read"))
787 (dolist (file files) 852 (dolist (prefix-suffix files)
788 (setq file (car file)) 853 (let ((prefix (car prefix-suffix))
789 (if (or (not (file-exists-p (concat cdir file))) 854 (suffix (cdr prefix-suffix)))
790 (file-exists-p (concat ndir file))) 855 ;; increase num for each unread or ticked article
791 (setq num (1+ num))))) 856 (when (or
857 ;; first look for marks in suffix, if it's valid...
858 (when (and (stringp suffix)
859 (string-prefix-p ":2," suffix))
860 (or
861 (not (string-match-p
862 (string (nnmaildir--mark-to-flag 'read)) suffix))
863 (string-match-p
864 (string (nnmaildir--mark-to-flag 'tick)) suffix)))
865 ;; then look in marks directories
866 (not (file-exists-p (concat cdir prefix)))
867 (file-exists-p (concat ndir prefix)))
868 (incf num)))))
792 (setf (nnmaildir--grp-cache group) (make-vector num nil)) 869 (setf (nnmaildir--grp-cache group) (make-vector num nil))
793 (let ((inhibit-quit t)) 870 (let ((inhibit-quit t))
794 (set (intern gname groups) group)) 871 (set (intern gname groups) group))
@@ -916,12 +993,15 @@ by nnmaildir-request-article.")
916 "\n"))))) 993 "\n")))))
917 'group) 994 'group)
918 995
919(defun nnmaildir-request-marks (gname info &optional server) 996(defun nnmaildir-request-update-info (gname info &optional server)
920 (let ((group (nnmaildir--prepare server gname)) 997 (let* ((group (nnmaildir--prepare server gname))
921 pgname flist always-marks never-marks old-marks dotfile num dir 998 (curdir (nnmaildir--cur
922 markdirs marks mark ranges markdir article read end new-marks ls 999 (nnmaildir--srvgrp-dir
923 old-mmth new-mmth mtime mark-sym existing missing deactivate-mark 1000 (nnmaildir--srv-dir nnmaildir--cur-server) gname)))
924 article-list) 1001 (curdir-mtime (nth 5 (file-attributes curdir)))
1002 pgname flist always-marks never-marks old-marks dotfile num dir
1003 all-marks marks mark ranges markdir read end new-marks ls
1004 old-mmth new-mmth mtime mark-sym existing missing deactivate-mark)
925 (catch 'return 1005 (catch 'return
926 (unless group 1006 (unless group
927 (setf (nnmaildir--srv-error nnmaildir--cur-server) 1007 (setf (nnmaildir--srv-error nnmaildir--cur-server)
@@ -950,34 +1030,71 @@ by nnmaildir-request-article.")
950 dir (nnmaildir--nndir dir) 1030 dir (nnmaildir--nndir dir)
951 dir (nnmaildir--marks-dir dir) 1031 dir (nnmaildir--marks-dir dir)
952 ls (nnmaildir--group-ls nnmaildir--cur-server pgname) 1032 ls (nnmaildir--group-ls nnmaildir--cur-server pgname)
953 markdirs (funcall ls dir nil "\\`[^.]" 'nosort) 1033 all-marks (gnus-delete-duplicates
954 new-mmth (nnmaildir--up2-1 (length markdirs)) 1034 ;; get mark names from mark dirs and from flag
1035 ;; mappings
1036 (append
1037 (mapcar 'cdr nnmaildir-flag-mark-mapping)
1038 (mapcar 'intern (funcall ls dir nil "\\`[^.]" 'nosort))))
1039 new-mmth (nnmaildir--up2-1 (length all-marks))
955 new-mmth (make-vector new-mmth 0) 1040 new-mmth (make-vector new-mmth 0)
956 old-mmth (nnmaildir--grp-mmth group)) 1041 old-mmth (nnmaildir--grp-mmth group))
957 (dolist (mark markdirs) 1042 (dolist (mark all-marks)
958 (setq markdir (nnmaildir--subdir dir mark) 1043 (setq markdir (nnmaildir--subdir dir (symbol-name mark))
959 mark-sym (intern mark)
960 ranges nil) 1044 ranges nil)
961 (catch 'got-ranges 1045 (catch 'got-ranges
962 (if (memq mark-sym never-marks) (throw 'got-ranges nil)) 1046 (if (memq mark never-marks) (throw 'got-ranges nil))
963 (when (memq mark-sym always-marks) 1047 (when (memq mark always-marks)
964 (setq ranges existing) 1048 (setq ranges existing)
965 (throw 'got-ranges nil)) 1049 (throw 'got-ranges nil))
966 (setq mtime (nth 5 (file-attributes markdir))) 1050 ;; Find the mtime for this mark. If this mark can be expressed as
967 (set (intern mark new-mmth) mtime) 1051 ;; a filename flag, get the later of the mtimes for markdir and
968 (when (equal mtime (symbol-value (intern-soft mark old-mmth))) 1052 ;; curdir, otherwise only the markdir counts.
969 (setq ranges (assq mark-sym old-marks)) 1053 (setq mtime
1054 (let ((markdir-mtime (nth 5 (file-attributes markdir))))
1055 (cond
1056 ((null (nnmaildir--mark-to-flag mark))
1057 markdir-mtime)
1058 ((null markdir-mtime)
1059 curdir-mtime)
1060 ((null curdir-mtime)
1061 ;; this should never happen...
1062 markdir-mtime)
1063 ((time-less-p markdir-mtime curdir-mtime)
1064 curdir-mtime)
1065 (t
1066 markdir-mtime))))
1067 (set (intern (symbol-name mark) new-mmth) mtime)
1068 (when (equal mtime (symbol-value (intern-soft (symbol-name mark) old-mmth)))
1069 (setq ranges (assq mark old-marks))
970 (if ranges (setq ranges (cdr ranges))) 1070 (if ranges (setq ranges (cdr ranges)))
971 (throw 'got-ranges nil)) 1071 (throw 'got-ranges nil))
972 (setq article-list nil) 1072 (let ((article-list nil))
973 (dolist (prefix (funcall ls markdir nil "\\`[^.]" 'nosort)) 1073 ;; Consider the article marked if it either has the flag in the
974 (setq article (nnmaildir--flist-art flist prefix)) 1074 ;; filename, or is in the markdir. As you'd rarely remove a
975 (if article 1075 ;; flag/mark, this should avoid losing information in the most
976 (setq article-list 1076 ;; common usage pattern.
977 (cons (nnmaildir--art-num article) article-list)))) 1077 (or
978 (setq ranges (gnus-add-to-range ranges (sort article-list '<)))) 1078 (let ((flag (nnmaildir--mark-to-flag mark)))
979 (if (eq mark-sym 'read) (setq read ranges) 1079 ;; If this mark has a corresponding maildir flag...
980 (if ranges (setq marks (cons (cons mark-sym ranges) marks))))) 1080 (when flag
1081 (let ((regexp
1082 (concat "\\`[^.].*:2,[A-Z]*" (string flag))))
1083 ;; ...then find all files with that flag.
1084 (dolist (filename (funcall ls curdir nil regexp 'nosort))
1085 (let* ((prefix (car (split-string filename ":2,")))
1086 (article (nnmaildir--flist-art flist prefix)))
1087 (when article
1088 (push (nnmaildir--art-num article) article-list)))))))
1089 ;; Also check Gnus-specific mark directory, if it exists.
1090 (when (file-directory-p markdir)
1091 (dolist (prefix (funcall ls markdir nil "\\`[^.]" 'nosort))
1092 (let ((article (nnmaildir--flist-art flist prefix)))
1093 (when article
1094 (push (nnmaildir--art-num article) article-list))))))
1095 (setq ranges (gnus-add-to-range ranges (sort article-list '<)))))
1096 (if (eq mark 'read) (setq read ranges)
1097 (if ranges (setq marks (cons (cons mark ranges) marks)))))
981 (gnus-info-set-read info (gnus-range-add read missing)) 1098 (gnus-info-set-read info (gnus-range-add read missing))
982 (gnus-info-set-marks info marks 'extend) 1099 (gnus-info-set-marks info marks 'extend)
983 (setf (nnmaildir--grp-mmth group) new-mmth) 1100 (setf (nnmaildir--grp-mmth group) new-mmth)
@@ -1525,39 +1642,63 @@ by nnmaildir-request-article.")
1525 didnt))) 1642 didnt)))
1526 1643
1527(defun nnmaildir-request-set-mark (gname actions &optional server) 1644(defun nnmaildir-request-set-mark (gname actions &optional server)
1528 (let ((group (nnmaildir--prepare server gname)) 1645 (let* ((group (nnmaildir--prepare server gname))
1529 (coding-system-for-write nnheader-file-coding-system) 1646 (curdir (nnmaildir--cur
1530 (buffer-file-coding-system nil) 1647 (nnmaildir--srvgrp-dir
1531 (file-coding-system-alist nil) 1648 (nnmaildir--srv-dir nnmaildir--cur-server)
1532 del-mark del-action add-action set-action marksdir nlist 1649 gname)))
1533 ranges begin end article all-marks todo-marks mdir mfile 1650 (coding-system-for-write nnheader-file-coding-system)
1534 pgname ls permarkfile deactivate-mark) 1651 (buffer-file-coding-system nil)
1652 (file-coding-system-alist nil)
1653 del-mark del-action add-action set-action marksdir nlist
1654 ranges begin end article all-marks todo-marks mdir mfile
1655 pgname ls permarkfile deactivate-mark)
1535 (setq del-mark 1656 (setq del-mark
1536 (lambda (mark) 1657 (lambda (mark)
1537 (setq mfile (nnmaildir--subdir marksdir (symbol-name mark)) 1658 (let ((prefix (nnmaildir--art-prefix article))
1538 mfile (concat mfile (nnmaildir--art-prefix article))) 1659 (suffix (nnmaildir--art-suffix article))
1539 (nnmaildir--unlink mfile)) 1660 (flag (nnmaildir--mark-to-flag mark)))
1661 (when flag
1662 ;; If this mark corresponds to a flag, remove the flag from
1663 ;; the file name.
1664 (nnmaildir--article-set-flags
1665 article (nnmaildir--remove-flag flag suffix) curdir))
1666 ;; We still want to delete the hardlink in the marks dir if
1667 ;; present, regardless of whether this mark has a maildir flag or
1668 ;; not, to avoid getting out of sync.
1669 (setq mfile (nnmaildir--subdir marksdir (symbol-name mark))
1670 mfile (concat mfile prefix))
1671 (nnmaildir--unlink mfile)))
1540 del-action (lambda (article) (mapcar del-mark todo-marks)) 1672 del-action (lambda (article) (mapcar del-mark todo-marks))
1541 add-action 1673 add-action
1542 (lambda (article) 1674 (lambda (article)
1543 (mapcar 1675 (mapcar
1544 (lambda (mark) 1676 (lambda (mark)
1545 (setq mdir (nnmaildir--subdir marksdir (symbol-name mark)) 1677 (let ((prefix (nnmaildir--art-prefix article))
1546 permarkfile (concat mdir ":") 1678 (suffix (nnmaildir--art-suffix article))
1547 mfile (concat mdir (nnmaildir--art-prefix article))) 1679 (flag (nnmaildir--mark-to-flag mark)))
1548 (nnmaildir--condcase err (add-name-to-file permarkfile mfile) 1680 (if flag
1549 (cond 1681 ;; If there is a corresponding maildir flag, just rename
1550 ((nnmaildir--eexist-p err)) 1682 ;; the file.
1551 ((nnmaildir--enoent-p err) 1683 (nnmaildir--article-set-flags
1552 (nnmaildir--mkdir mdir) 1684 article (nnmaildir--add-flag flag suffix) curdir)
1553 (nnmaildir--mkfile permarkfile) 1685 ;; Otherwise, use nnmaildir-specific marks dir.
1554 (add-name-to-file permarkfile mfile)) 1686 (setq mdir (nnmaildir--subdir marksdir (symbol-name mark))
1555 ((nnmaildir--emlink-p err) 1687 permarkfile (concat mdir ":")
1556 (let ((permarkfilenew (concat permarkfile "{new}"))) 1688 mfile (concat mdir prefix))
1557 (nnmaildir--mkfile permarkfilenew) 1689 (nnmaildir--condcase err (add-name-to-file permarkfile mfile)
1558 (rename-file permarkfilenew permarkfile 'replace) 1690 (cond
1559 (add-name-to-file permarkfile mfile))) 1691 ((nnmaildir--eexist-p err))
1560 (t (signal (car err) (cdr err)))))) 1692 ((nnmaildir--enoent-p err)
1693 (nnmaildir--mkdir mdir)
1694 (nnmaildir--mkfile permarkfile)
1695 (add-name-to-file permarkfile mfile))
1696 ((nnmaildir--emlink-p err)
1697 (let ((permarkfilenew (concat permarkfile "{new}")))
1698 (nnmaildir--mkfile permarkfilenew)
1699 (rename-file permarkfilenew permarkfile 'replace)
1700 (add-name-to-file permarkfile mfile)))
1701 (t (signal (car err) (cdr err))))))))
1561 todo-marks)) 1702 todo-marks))
1562 set-action (lambda (article) 1703 set-action (lambda (article)
1563 (funcall add-action article) 1704 (funcall add-action article)
@@ -1581,7 +1722,12 @@ by nnmaildir-request-article.")
1581 pgname (nnmaildir--pgname nnmaildir--cur-server gname) 1722 pgname (nnmaildir--pgname nnmaildir--cur-server gname)
1582 ls (nnmaildir--group-ls nnmaildir--cur-server pgname) 1723 ls (nnmaildir--group-ls nnmaildir--cur-server pgname)
1583 all-marks (funcall ls marksdir nil "\\`[^.]" 'nosort) 1724 all-marks (funcall ls marksdir nil "\\`[^.]" 'nosort)
1584 all-marks (mapcar 'intern all-marks)) 1725 all-marks (gnus-delete-duplicates
1726 ;; get mark names from mark dirs and from flag
1727 ;; mappings
1728 (append
1729 (mapcar 'cdr nnmaildir-flag-mark-mapping)
1730 (mapcar 'intern all-marks))))
1585 (dolist (action actions) 1731 (dolist (action actions)
1586 (setq ranges (car action) 1732 (setq ranges (car action)
1587 todo-marks (caddr action)) 1733 todo-marks (caddr action))