1 ;;; nnmaildir.el --- maildir backend for Gnus
3 ;; This file is in the public domain.
5 ;; Author: Paul Jarc <prj@po.cwru.edu>
7 ;; This file is part of GNU Emacs.
9 ;; GNU Emacs is free software: you can redistribute it and/or modify
10 ;; it under the terms of the GNU General Public License as published by
11 ;; the Free Software Foundation, either version 3 of the License, or
12 ;; (at your option) any later version.
14 ;; GNU Emacs is distributed in the hope that it will be useful,
15 ;; but WITHOUT ANY WARRANTY; without even the implied warranty of
16 ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17 ;; GNU General Public License for more details.
19 ;; You should have received a copy of the GNU General Public License
20 ;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>.
24 ;; Maildir format is documented at <URL:http://cr.yp.to/proto/maildir.html>
25 ;; and in the maildir(5) man page from qmail (available at
26 ;; <URL:http://www.qmail.org/man/man5/maildir.html>). nnmaildir also stores
27 ;; extra information in the .nnmaildir/ directory within a maildir.
29 ;; Some goals of nnmaildir:
30 ;; * Everything Just Works, and correctly. E.g., NOV data is automatically
31 ;; regenerated when stale; no need for manually running
32 ;; *-generate-nov-databases.
33 ;; * Perfect reliability: [C-g] will never corrupt its data in memory, and
34 ;; SIGKILL will never corrupt its data in the filesystem.
35 ;; * Allow concurrent operation as much as possible. If files change out
36 ;; from under us, adapt to the changes or degrade gracefully.
37 ;; * We use the filesystem as a database, so that, e.g., it's easy to
38 ;; manipulate marks from outside Gnus.
39 ;; * All information about a group is stored in the maildir, for easy backup,
40 ;; copying, restoring, etc.
43 ;; * When moving an article for expiry, copy all the marks except 'expire
44 ;; from the original article.
45 ;; * Add a hook for when moving messages from new/ to cur/, to support
46 ;; nnmail's duplicate detection.
47 ;; * Improve generated Xrefs, so crossposts are detectable.
48 ;; * Improve code readability.
52 ;; eval this before editing
54 (put 'nnmaildir--with-nntp-buffer
'lisp-indent-function
0)
55 (put 'nnmaildir--with-work-buffer
'lisp-indent-function
0)
56 (put 'nnmaildir--with-nov-buffer
'lisp-indent-function
0)
57 (put 'nnmaildir--with-move-buffer
'lisp-indent-function
0)
58 (put 'nnmaildir--condcase
'lisp-indent-function
2)
62 ;; For Emacs <22.2 and XEmacs.
64 (unless (fboundp 'declare-function
) (defmacro declare-function
(&rest r
))))
78 (defconst nnmaildir-version
"Gnus")
80 (defvar nnmaildir-article-file-name nil
81 "*The filename of the most recently requested article. This variable is set
82 by nnmaildir-request-article.")
84 ;; The filename of the article being moved/copied:
85 (defvar nnmaildir--file nil
)
87 ;; Variables to generate filenames of messages being delivered:
88 (defvar nnmaildir--delivery-time
"")
89 (defconst nnmaildir--delivery-pid
(concat "P" (number-to-string (emacs-pid))))
90 (defvar nnmaildir--delivery-count nil
)
92 ;; An obarry containing symbols whose names are server names and whose values
94 (defvar nnmaildir--servers
(make-vector 3 0))
95 ;; The current server:
96 (defvar nnmaildir--cur-server nil
)
98 ;; A copy of nnmail-extra-headers
99 (defvar nnmaildir--extra nil
)
101 ;; A NOV structure looks like this (must be prin1-able, so no defstruct):
102 ["subject\tfrom\tdate"
103 "references\tchars\lines"
104 "To: you\tIn-Reply-To: <your.mess@ge>"
105 (12345 67890) ;; modtime of the corresponding article file
106 (to in-reply-to
)] ;; contemporary value of nnmail-extra-headers
107 (defconst nnmaildir--novlen
5)
108 (defmacro nnmaildir--nov-new
(beg mid end mtime extra
)
109 `(vector ,beg
,mid
,end
,mtime
,extra
))
110 (defmacro nnmaildir--nov-get-beg
(nov) `(aref ,nov
0))
111 (defmacro nnmaildir--nov-get-mid
(nov) `(aref ,nov
1))
112 (defmacro nnmaildir--nov-get-end
(nov) `(aref ,nov
2))
113 (defmacro nnmaildir--nov-get-mtime
(nov) `(aref ,nov
3))
114 (defmacro nnmaildir--nov-get-extra
(nov) `(aref ,nov
4))
115 (defmacro nnmaildir--nov-set-beg
(nov value
) `(aset ,nov
0 ,value
))
116 (defmacro nnmaildir--nov-set-mid
(nov value
) `(aset ,nov
1 ,value
))
117 (defmacro nnmaildir--nov-set-end
(nov value
) `(aset ,nov
2 ,value
))
118 (defmacro nnmaildir--nov-set-mtime
(nov value
) `(aset ,nov
3 ,value
))
119 (defmacro nnmaildir--nov-set-extra
(nov value
) `(aset ,nov
4 ,value
))
121 (defstruct nnmaildir--art
122 (prefix nil
:type string
) ;; "time.pid.host"
123 (suffix nil
:type string
) ;; ":2,flags"
124 (num nil
:type natnum
) ;; article number
125 (msgid nil
:type string
) ;; "<mess.age@id>"
126 (nov nil
:type vector
)) ;; cached nov structure, or nil
128 (defstruct nnmaildir--grp
129 (name nil
:type string
) ;; "group.name"
130 (new nil
:type list
) ;; new/ modtime
131 (cur nil
:type list
) ;; cur/ modtime
132 (min 1 :type natnum
) ;; minimum article number
133 (count 0 :type natnum
) ;; count of articles
134 (nlist nil
:type list
) ;; list of articles, ordered descending by number
135 (flist nil
:type vector
) ;; obarray mapping filename prefix->article
136 (mlist nil
:type vector
) ;; obarray mapping message-id->article
137 (cache nil
:type vector
) ;; nov cache
138 (index nil
:type natnum
) ;; index of next cache entry to replace
139 (mmth nil
:type vector
)) ;; obarray mapping mark name->dir modtime
140 ; ("Mark Mod Time Hash")
142 (defstruct nnmaildir--srv
143 (address nil
:type string
) ;; server address string
144 (method nil
:type list
) ;; (nnmaildir "address" ...)
145 (prefix nil
:type string
) ;; "nnmaildir+address:"
146 (dir nil
:type string
) ;; "/expanded/path/to/server/dir/"
147 (ls nil
:type function
) ;; directory-files function
148 (groups nil
:type vector
) ;; obarray mapping group name->group
149 (curgrp nil
:type nnmaildir--grp
) ;; current group, or nil
150 (error nil
:type string
) ;; last error message, or nil
151 (mtime nil
:type list
) ;; modtime of dir
152 (gnm nil
) ;; flag: split from mail-sources?
153 (target-prefix nil
:type string
)) ;; symlink target prefix
155 (defun nnmaildir--expired-article (group article
)
156 (setf (nnmaildir--art-nov article
) nil
)
157 (let ((flist (nnmaildir--grp-flist group
))
158 (mlist (nnmaildir--grp-mlist group
))
159 (min (nnmaildir--grp-min group
))
160 (count (1- (nnmaildir--grp-count group
)))
161 (prefix (nnmaildir--art-prefix article
))
162 (msgid (nnmaildir--art-msgid article
))
164 (nlist-pre '(nil . nil
))
166 (unless (zerop count
)
167 (setq nlist-post
(nnmaildir--grp-nlist group
)
168 num
(nnmaildir--art-num article
))
169 (if (eq num
(caar nlist-post
))
170 (setq new-nlist
(cdr nlist-post
))
171 (setq new-nlist nlist-post
173 nlist-post
(cdr nlist-post
))
174 (while (/= num
(caar nlist-post
))
175 (setq nlist-pre nlist-post
176 nlist-post
(cdr nlist-post
)))
177 (setq nlist-post
(cdr nlist-post
))
179 (setq min
(caar nlist-pre
)))))
180 (let ((inhibit-quit t
))
181 (setf (nnmaildir--grp-min group
) min
)
182 (setf (nnmaildir--grp-count group
) count
)
183 (setf (nnmaildir--grp-nlist group
) new-nlist
)
184 (setcdr nlist-pre nlist-post
)
185 (unintern prefix flist
)
186 (unintern msgid mlist
))))
188 (defun nnmaildir--nlist-art (group num
)
189 (let ((entry (assq num
(nnmaildir--grp-nlist group
))))
192 (defmacro nnmaildir--flist-art
(list file
)
193 `(symbol-value (intern-soft ,file
,list
)))
194 (defmacro nnmaildir--mlist-art
(list msgid
)
195 `(symbol-value (intern-soft ,msgid
,list
)))
197 (defun nnmaildir--pgname (server gname
)
198 (let ((prefix (nnmaildir--srv-prefix server
)))
199 (if prefix
(concat prefix gname
)
200 (setq gname
(gnus-group-prefixed-name gname
201 (nnmaildir--srv-method server
)))
202 (setf (nnmaildir--srv-prefix server
) (gnus-group-real-prefix gname
))
205 (defun nnmaildir--param (pgname param
)
206 (setq param
(gnus-group-find-parameter pgname param
'allow-list
))
207 (if (vectorp param
) (setq param
(aref param
0)))
210 (defmacro nnmaildir--with-nntp-buffer
(&rest body
)
211 `(with-current-buffer nntp-server-buffer
213 (defmacro nnmaildir--with-work-buffer
(&rest body
)
214 `(with-current-buffer (get-buffer-create " *nnmaildir work*")
216 (defmacro nnmaildir--with-nov-buffer
(&rest body
)
217 `(with-current-buffer (get-buffer-create " *nnmaildir nov*")
219 (defmacro nnmaildir--with-move-buffer
(&rest body
)
220 `(with-current-buffer (get-buffer-create " *nnmaildir move*")
223 (defmacro nnmaildir--subdir
(dir subdir
)
224 `(file-name-as-directory (concat ,dir
,subdir
)))
225 (defmacro nnmaildir--srvgrp-dir
(srv-dir gname
)
226 `(nnmaildir--subdir ,srv-dir
,gname
))
227 (defmacro nnmaildir--tmp
(dir) `(nnmaildir--subdir ,dir
"tmp"))
228 (defmacro nnmaildir--new
(dir) `(nnmaildir--subdir ,dir
"new"))
229 (defmacro nnmaildir--cur
(dir) `(nnmaildir--subdir ,dir
"cur"))
230 (defmacro nnmaildir--nndir
(dir) `(nnmaildir--subdir ,dir
".nnmaildir"))
231 (defmacro nnmaildir--nov-dir
(dir) `(nnmaildir--subdir ,dir
"nov"))
232 (defmacro nnmaildir--marks-dir
(dir) `(nnmaildir--subdir ,dir
"marks"))
233 (defmacro nnmaildir--num-dir
(dir) `(nnmaildir--subdir ,dir
"num"))
235 (defmacro nnmaildir--unlink
(file-arg)
236 `(let ((file ,file-arg
))
237 (if (file-attributes file
) (delete-file file
))))
238 (defun nnmaildir--mkdir (dir)
239 (or (file-exists-p (file-name-as-directory dir
))
240 (make-directory-internal (directory-file-name dir
))))
241 (defun nnmaildir--mkfile (file)
242 (write-region "" nil file nil
'no-message
))
243 (defun nnmaildir--delete-dir-files (dir ls
)
244 (when (file-attributes dir
)
245 (mapc 'delete-file
(funcall ls dir
'full
"\\`[^.]" 'nosort
))
246 (delete-directory dir
)))
248 (defun nnmaildir--group-maxnum (server group
)
250 (if (zerop (nnmaildir--grp-count group
)) (throw 'return
0))
251 (let ((dir (nnmaildir--srvgrp-dir (nnmaildir--srv-dir server
)
252 (nnmaildir--grp-name group
)))
254 attr ino-opened nlink number-linked
)
255 (setq dir
(nnmaildir--nndir dir
)
256 dir
(nnmaildir--num-dir dir
))
258 (setq attr
(file-attributes
259 (concat dir
(number-to-string number-opened
))))
260 (or attr
(throw 'return
(1- number-opened
)))
261 (setq ino-opened
(nth 10 attr
)
263 number-linked
(+ number-opened nlink
))
264 (if (or (< nlink
1) (< number-linked nlink
))
265 (signal 'error
'("Arithmetic overflow")))
266 (setq attr
(file-attributes
267 (concat dir
(number-to-string number-linked
))))
268 (or attr
(throw 'return
(1- number-linked
)))
269 (unless (equal ino-opened
(nth 10 attr
))
270 (setq number-opened number-linked
))))))
272 ;; Make the given server, if non-nil, be the current server. Then make the
273 ;; given group, if non-nil, be the current group of the current server. Then
274 ;; return the group object for the current group.
275 (defun nnmaildir--prepare (server group
)
279 (unless (setq server nnmaildir--cur-server
)
281 (unless (setq server
(intern-soft server nnmaildir--servers
))
283 (setq server
(symbol-value server
)
284 nnmaildir--cur-server server
))
285 (unless (setq groups
(nnmaildir--srv-groups server
))
287 (unless (nnmaildir--srv-method server
)
288 (setq x
(concat "nnmaildir:" (nnmaildir--srv-address server
))
289 x
(gnus-server-to-method x
))
290 (unless x
(throw 'return nil
))
291 (setf (nnmaildir--srv-method server
) x
))
293 (unless (setq group
(nnmaildir--srv-curgrp server
))
295 (unless (setq group
(intern-soft group groups
))
297 (setq group
(symbol-value group
)))
300 (defun nnmaildir--tab-to-space (string)
302 (while (string-match "\t" string pos
)
303 (aset string
(match-beginning 0) ?
)
304 (setq pos
(match-end 0))))
307 (defmacro nnmaildir--condcase
(errsym body
&rest handler
)
308 `(condition-case ,errsym
309 (let ((system-messages-locale "C")) ,body
)
312 (defun nnmaildir--emlink-p (err)
313 (and (eq (car err
) 'file-error
)
314 (string= (downcase (caddr err
)) "too many links")))
316 (defun nnmaildir--enoent-p (err)
317 (and (eq (car err
) 'file-error
)
318 (string= (downcase (caddr err
)) "no such file or directory")))
320 (defun nnmaildir--eexist-p (err)
321 (eq (car err
) 'file-already-exists
))
323 (defun nnmaildir--new-number (nndir)
324 "Allocate a new article number by atomically creating a file under NNDIR."
325 (let ((numdir (nnmaildir--num-dir nndir
))
328 number-link previous-number-link path-open path-link ino-open
)
329 (nnmaildir--mkdir numdir
)
332 (setq path-open
(concat numdir
(number-to-string number-open
)))
333 (if (not make-new-file
)
334 (setq previous-number-link number-link
)
335 (nnmaildir--mkfile path-open
)
336 ;; If Emacs had O_CREAT|O_EXCL, we could return number-open here.
337 (setq make-new-file nil
338 previous-number-link
0))
339 (let* ((attr (file-attributes path-open
))
340 (nlink (nth 1 attr
)))
341 (setq ino-open
(nth 10 attr
)
342 number-link
(+ number-open nlink
))
343 (if (or (< nlink
1) (< number-link nlink
))
344 (signal 'error
'("Arithmetic overflow"))))
345 (if (= number-link previous-number-link
)
346 ;; We've already tried this number, in the previous loop iteration,
348 (signal 'error
`("Corrupt internal nnmaildir data" ,path-open
)))
349 (setq path-link
(concat numdir
(number-to-string number-link
)))
350 (nnmaildir--condcase err
352 (add-name-to-file path-open path-link
)
353 (throw 'return number-link
))
355 ((nnmaildir--emlink-p err
)
356 (setq make-new-file t
357 number-open number-link
))
358 ((nnmaildir--eexist-p err
)
359 (let ((attr (file-attributes path-link
)))
360 (unless (equal (nth 10 attr
) ino-open
)
361 (setq number-open number-link
363 (t (signal (car err
) (cdr err
)))))))))
365 (defun nnmaildir--update-nov (server group article
)
366 (let ((nnheader-file-coding-system 'binary
)
367 (srv-dir (nnmaildir--srv-dir server
))
368 (storage-version 1) ;; [version article-number msgid [...nov...]]
369 dir gname pgname msgdir prefix suffix file attr mtime novdir novfile
370 nov msgid nov-beg nov-mid nov-end field val old-extra num numdir
373 (setq gname
(nnmaildir--grp-name group
)
374 pgname
(nnmaildir--pgname server gname
)
375 dir
(nnmaildir--srvgrp-dir srv-dir gname
)
376 msgdir
(if (nnmaildir--param pgname
'read-only
)
377 (nnmaildir--new dir
) (nnmaildir--cur dir
))
378 prefix
(nnmaildir--art-prefix article
)
379 suffix
(nnmaildir--art-suffix article
)
380 file
(concat msgdir prefix suffix
)
381 attr
(file-attributes file
))
383 (nnmaildir--expired-article group article
)
385 (setq mtime
(nth 5 attr
)
387 nov
(nnmaildir--art-nov article
)
388 dir
(nnmaildir--nndir dir
)
389 novdir
(nnmaildir--nov-dir dir
)
390 novfile
(concat novdir prefix
))
391 (unless (equal nnmaildir--extra nnmail-extra-headers
)
392 (setq nnmaildir--extra
(copy-sequence nnmail-extra-headers
)))
393 (nnmaildir--with-nov-buffer
394 ;; First we'll check for already-parsed NOV data.
395 (cond ((not (file-exists-p novfile
))
396 ;; The NOV file doesn't exist; we have to parse the message.
399 ;; The file exists, but the data isn't in memory; read the file.
401 (nnheader-insert-file-contents novfile
)
402 (setq nov
(read (current-buffer)))
403 (if (not (and (vectorp nov
)
405 (equal storage-version
(aref nov
0))))
406 ;; This NOV data seems to be in the wrong format.
408 (unless (nnmaildir--art-num article
)
409 (setf (nnmaildir--art-num article
) (aref nov
1)))
410 (unless (nnmaildir--art-msgid article
)
411 (setf (nnmaildir--art-msgid article
) (aref nov
2)))
412 (setq nov
(aref nov
3)))))
413 ;; Now check whether the already-parsed data (if we have any) is
414 ;; usable: if the message has been edited or if nnmail-extra-headers
415 ;; has been augmented since this data was parsed from the message,
416 ;; then we have to reparse. Otherwise it's up-to-date.
417 (when (and nov
(equal mtime
(nnmaildir--nov-get-mtime nov
)))
418 ;; The timestamp matches. Now check nnmail-extra-headers.
419 (setq old-extra
(nnmaildir--nov-get-extra nov
))
420 (when (equal nnmaildir--extra old-extra
) ;; common case
421 ;; Save memory; use a single copy of the list value.
422 (nnmaildir--nov-set-extra nov nnmaildir--extra
)
424 ;; They're not equal, but maybe the new is a subset of the old.
425 (if (null nnmaildir--extra
)
426 ;; The empty set is a subset of every set.
428 (if (not (memq nil
(mapcar (lambda (e) (memq e old-extra
))
430 (throw 'return nov
)))
431 ;; Parse the NOV data out of the message.
433 (nnheader-insert-file-contents file
)
435 (goto-char (point-min))
437 (if (search-forward "\n\n" nil
'noerror
)
439 (setq nov-mid
(count-lines (point) (point-max)))
440 (narrow-to-region (point-min) (1- (point))))
442 (goto-char (point-min))
444 (setq nov
(nnheader-parse-naked-head)
445 field
(or (mail-header-lines nov
) 0)))
446 (unless (or (zerop field
) (nnmaildir--param pgname
'distrust-Lines
:))
447 (setq nov-mid field
))
448 (setq nov-mid
(number-to-string nov-mid
)
449 nov-mid
(concat (number-to-string attr
) "\t" nov-mid
))
451 (setq field
(or (mail-header-references nov
) ""))
452 (nnmaildir--tab-to-space field
)
453 (setq nov-mid
(concat field
"\t" nov-mid
)
455 (lambda (f) (nnmaildir--tab-to-space (or f
"")))
456 (list (mail-header-subject nov
)
457 (mail-header-from nov
)
458 (mail-header-date nov
)) "\t")
461 (setq field
(symbol-name (car extra
))
463 (nnmaildir--tab-to-space field
)
464 (nnmaildir--tab-to-space val
)
465 (concat field
": " val
))
466 (mail-header-extra nov
) "\t")))
467 (setq msgid
(mail-header-id nov
))
468 (if (or (null msgid
) (nnheader-fake-message-id-p msgid
))
469 (setq msgid
(concat "<" prefix
"@nnmaildir>")))
470 (nnmaildir--tab-to-space msgid
)
471 ;; The data is parsed; create an nnmaildir NOV structure.
472 (setq nov
(nnmaildir--nov-new nov-beg nov-mid nov-end mtime
474 num
(nnmaildir--art-num article
))
476 (setq num
(nnmaildir--new-number dir
))
477 (setf (nnmaildir--art-num article
) num
))
478 ;; Store this new NOV data in a file
480 (prin1 (vector storage-version num msgid nov
) (current-buffer))
481 (setq file
(concat novfile
":"))
482 (nnmaildir--unlink file
)
483 (gmm-write-region (point-min) (point-max) file nil
'no-message nil
485 (rename-file file novfile
'replace
)
486 (setf (nnmaildir--art-msgid article
) msgid
)
489 (defun nnmaildir--cache-nov (group article nov
)
490 (let ((cache (nnmaildir--grp-cache group
))
491 (index (nnmaildir--grp-index group
))
493 (unless (nnmaildir--art-nov article
)
494 (setq goner
(aref cache index
))
495 (if goner
(setf (nnmaildir--art-nov goner
) nil
))
496 (aset cache index article
)
497 (setf (nnmaildir--grp-index group
) (%
(1+ index
) (length cache
))))
498 (setf (nnmaildir--art-nov article
) nov
)))
500 (defun nnmaildir--grp-add-art (server group article
)
501 (let ((nov (nnmaildir--update-nov server group article
))
502 count num min nlist nlist-cdr insert-nlist
)
504 (setq count
(1+ (nnmaildir--grp-count group
))
505 num
(nnmaildir--art-num article
)
506 min
(if (= count
1) num
507 (min num
(nnmaildir--grp-min group
)))
508 nlist
(nnmaildir--grp-nlist group
))
509 (if (or (null nlist
) (> num
(caar nlist
)))
510 (setq nlist
(cons (cons num article
) nlist
))
512 nlist-cdr
(cdr nlist
))
513 (while (and nlist-cdr
(< num
(caar nlist-cdr
)))
514 (setq nlist nlist-cdr
515 nlist-cdr
(cdr nlist
))))
516 (let ((inhibit-quit t
))
517 (setf (nnmaildir--grp-count group
) count
)
518 (setf (nnmaildir--grp-min group
) min
)
520 (setcdr nlist
(cons (cons num article
) nlist-cdr
))
521 (setf (nnmaildir--grp-nlist group
) nlist
))
522 (set (intern (nnmaildir--art-prefix article
)
523 (nnmaildir--grp-flist group
))
525 (set (intern (nnmaildir--art-msgid article
)
526 (nnmaildir--grp-mlist group
))
528 (set (intern (nnmaildir--grp-name group
)
529 (nnmaildir--srv-groups server
))
531 (nnmaildir--cache-nov group article nov
)
534 (defun nnmaildir--group-ls (server pgname
)
535 (or (nnmaildir--param pgname
'directory-files
)
536 (nnmaildir--srv-ls server
)))
538 (defun nnmaildir-article-number-to-file-name
539 (number group-name server-address-string
)
540 (let ((group (nnmaildir--prepare server-address-string group-name
))
544 ;; The given group or server does not exist.
546 (setq article
(nnmaildir--nlist-art group number
))
548 ;; The given article number does not exist in this group.
550 (setq pgname
(nnmaildir--pgname nnmaildir--cur-server group-name
)
551 dir
(nnmaildir--srv-dir nnmaildir--cur-server
)
552 dir
(nnmaildir--srvgrp-dir dir group-name
)
553 dir
(if (nnmaildir--param pgname
'read-only
)
554 (nnmaildir--new dir
) (nnmaildir--cur dir
)))
555 (concat dir
(nnmaildir--art-prefix article
)
556 (nnmaildir--art-suffix article
)))))
558 (defun nnmaildir-article-number-to-base-name
559 (number group-name server-address-string
)
560 (let ((x (nnmaildir--prepare server-address-string group-name
)))
562 (setq x
(nnmaildir--nlist-art x number
))
563 (and x
(cons (nnmaildir--art-prefix x
)
564 (nnmaildir--art-suffix x
))))))
566 (defun nnmaildir-base-name-to-article-number
567 (base-name group-name server-address-string
)
568 (let ((x (nnmaildir--prepare server-address-string group-name
)))
570 (setq x
(nnmaildir--grp-flist x
)
571 x
(nnmaildir--flist-art x base-name
))
572 (and x
(nnmaildir--art-num x
)))))
574 (defun nnmaildir--nlist-iterate (nlist ranges func
)
575 (let (entry high low nlist2
)
577 (setq ranges
`((1 .
,(caar nlist
)))))
579 (setq entry
(car ranges
) ranges
(cdr ranges
))
580 (while (and ranges
(eq entry
(car ranges
)))
581 (setq ranges
(cdr ranges
))) ;; skip duplicates
585 (setq low
(car entry
)
587 (setq nlist2 nlist
) ;; Don't assume any sorting of ranges
590 (if (<= (caar nlist2
) high
) (throw 'iterate-loop nil
))
591 (setq nlist2
(cdr nlist2
))))
594 (setq entry
(car nlist2
) nlist2
(cdr nlist2
))
595 (if (< (car entry
) low
) (throw 'iterate-loop nil
))
596 (funcall func
(cdr entry
)))))))
598 (defun nnmaildir--up2-1 (n)
599 (if (zerop n
) 1 (1- (lsh 1 (1+ (logb n
))))))
601 (defun nnmaildir--system-name ()
602 (gnus-replace-in-string
603 (gnus-replace-in-string
604 (gnus-replace-in-string
606 "\\\\" "\\134" 'literal
)
607 "/" "\\057" 'literal
)
608 ":" "\\072" 'literal
))
610 (defun nnmaildir-request-type (group &optional article
)
613 (defun nnmaildir-status-message (&optional server
)
614 (nnmaildir--prepare server nil
)
615 (nnmaildir--srv-error nnmaildir--cur-server
))
617 (defun nnmaildir-server-opened (&optional server
)
618 (and nnmaildir--cur-server
620 (string-equal server
(nnmaildir--srv-address nnmaildir--cur-server
))
622 (nnmaildir--srv-groups nnmaildir--cur-server
)
625 (defun nnmaildir-open-server (server &optional defs
)
629 (setq server
(intern-soft x nnmaildir--servers
))
631 (and (setq server
(symbol-value server
))
632 (nnmaildir--srv-groups server
)
633 (setq nnmaildir--cur-server server
)
635 (setq server
(make-nnmaildir--srv :address x
))
636 (let ((inhibit-quit t
))
637 (set (intern x nnmaildir--servers
) server
)))
638 (setq dir
(assq 'directory defs
))
640 (setf (nnmaildir--srv-error server
)
641 "You must set \"directory\" in the select method")
645 dir
(expand-file-name dir
)
646 dir
(file-name-as-directory dir
))
647 (unless (file-exists-p dir
)
648 (setf (nnmaildir--srv-error server
) (concat "No such directory: " dir
))
650 (setf (nnmaildir--srv-dir server
) dir
)
651 (setq x
(assq 'directory-files defs
))
653 (setq x
(if nnheader-directory-files-is-safe
'directory-files
654 'nnheader-directory-files-safe
))
656 (unless (functionp x
)
657 (setf (nnmaildir--srv-error server
)
658 (concat "Not a function: " (prin1-to-string x
)))
659 (throw 'return nil
)))
660 (setf (nnmaildir--srv-ls server
) x
)
661 (setq size
(length (funcall x dir nil
"\\`[^.]" 'nosort
))
662 size
(nnmaildir--up2-1 size
))
663 (and (setq x
(assq 'get-new-mail defs
))
666 (setf (nnmaildir--srv-gnm server
) t
)
668 (setq x
(assq 'target-prefix defs
))
673 (setf (nnmaildir--srv-target-prefix server
) x
))
674 (setq x
(assq 'create-directory defs
))
679 x
(file-name-as-directory x
))
680 (setf (nnmaildir--srv-target-prefix server
) x
))
681 (setf (nnmaildir--srv-target-prefix server
) "")))
682 (setf (nnmaildir--srv-groups server
) (make-vector size
0))
683 (setq nnmaildir--cur-server server
)
686 (defun nnmaildir--parse-filename (file)
687 (let ((prefix (car file
))
689 (if (string-match "\\`\\([0-9]+\\)\\(\\..*\\)\\'" prefix
)
691 (setq timestamp
(concat "0000" (match-string 1 prefix
))
692 len
(- (length timestamp
) 4))
693 (vector (string-to-number (substring timestamp
0 len
))
694 (string-to-number (substring timestamp len
))
695 (match-string 2 prefix
)
699 (defun nnmaildir--sort-files (a b
)
702 (throw 'return
(and (consp b
) (string-lessp (car a
) (car b
)))))
703 (if (consp b
) (throw 'return t
))
704 (if (< (aref a
0) (aref b
0)) (throw 'return t
))
705 (if (> (aref a
0) (aref b
0)) (throw 'return nil
))
706 (if (< (aref a
1) (aref b
1)) (throw 'return t
))
707 (if (> (aref a
1) (aref b
1)) (throw 'return nil
))
708 (string-lessp (aref a
2) (aref b
2))))
710 (defun nnmaildir--scan (gname scan-msgs groups method srv-dir srv-ls
)
712 (let ((36h-ago (- (car (current-time)) 2))
713 absdir nndir tdir ndir cdir nattr cattr isnew pgname read-only ls
714 files num dir flist group x
)
715 (setq absdir
(nnmaildir--srvgrp-dir srv-dir gname
)
716 nndir
(nnmaildir--nndir absdir
))
717 (unless (file-exists-p absdir
)
718 (setf (nnmaildir--srv-error nnmaildir--cur-server
)
719 (concat "No such directory: " absdir
))
721 (setq tdir
(nnmaildir--tmp absdir
)
722 ndir
(nnmaildir--new absdir
)
723 cdir
(nnmaildir--cur absdir
)
724 nattr
(file-attributes ndir
)
725 cattr
(file-attributes cdir
))
726 (unless (and (file-exists-p tdir
) nattr cattr
)
727 (setf (nnmaildir--srv-error nnmaildir--cur-server
)
728 (concat "Not a maildir: " absdir
))
730 (setq group
(nnmaildir--prepare nil gname
)
731 pgname
(nnmaildir--pgname nnmaildir--cur-server gname
))
735 group
(make-nnmaildir--grp :name gname
:index
0))
736 (nnmaildir--mkdir nndir
)
737 (nnmaildir--mkdir (nnmaildir--nov-dir nndir
))
738 (nnmaildir--mkdir (nnmaildir--marks-dir nndir
)))
739 (setq read-only
(nnmaildir--param pgname
'read-only
)
740 ls
(or (nnmaildir--param pgname
'directory-files
) srv-ls
))
742 (setq x
(nth 11 (file-attributes tdir
)))
743 (unless (and (equal x
(nth 11 nattr
)) (equal x
(nth 11 cattr
)))
744 (setf (nnmaildir--srv-error nnmaildir--cur-server
)
745 (concat "Maildir spans filesystems: " absdir
))
747 (dolist (file (funcall ls tdir
'full
"\\`[^.]" 'nosort
))
748 (setq x
(file-attributes file
))
749 (if (or (> (cadr x
) 1) (< (car (nth 4 x
)) 36h-ago
))
750 (delete-file file
))))
754 (setq nattr
(nth 5 nattr
))
755 (if (equal nattr
(nnmaildir--grp-new group
))
757 (if read-only
(setq dir
(and (or isnew nattr
) ndir
))
758 (when (or isnew nattr
)
759 (dolist (file (funcall ls ndir nil
"\\`[^.]" 'nosort
))
760 (setq x
(concat ndir file
))
761 (and (time-less-p (nth 5 (file-attributes x
)) (current-time))
762 (rename-file x
(concat cdir file
":2,"))))
763 (setf (nnmaildir--grp-new group
) nattr
))
764 (setq cattr
(nth 5 (file-attributes cdir
)))
765 (if (equal cattr
(nnmaildir--grp-cur group
))
767 (setq dir
(and (or isnew cattr
) cdir
)))
768 (unless dir
(throw 'return t
))
769 (setq files
(funcall ls dir nil
"\\`[^.]" 'nosort
)
770 files
(save-match-data
773 (string-match "\\`\\([^:]*\\)\\(\\(:.*\\)?\\)\\'" f
)
774 (cons (match-string 1 f
) (match-string 2 f
)))
777 (setq num
(nnmaildir--up2-1 (length files
)))
778 (setf (nnmaildir--grp-flist group
) (make-vector num
0))
779 (setf (nnmaildir--grp-mlist group
) (make-vector num
0))
780 (setf (nnmaildir--grp-mmth group
) (make-vector 1 0))
781 (setq num
(nnmaildir--param pgname
'nov-cache-size
))
782 (if (numberp num
) (if (< num
1) (setq num
1))
784 cdir
(nnmaildir--marks-dir nndir
)
785 ndir
(nnmaildir--subdir cdir
"tick")
786 cdir
(nnmaildir--subdir cdir
"read"))
788 (setq file
(car file
))
789 (if (or (not (file-exists-p (concat cdir file
)))
790 (file-exists-p (concat ndir file
)))
791 (setq num
(1+ num
)))))
792 (setf (nnmaildir--grp-cache group
) (make-vector num nil
))
793 (let ((inhibit-quit t
))
794 (set (intern gname groups
) group
))
795 (or scan-msgs
(throw 'return t
)))
796 (setq flist
(nnmaildir--grp-flist group
)
799 (and (null (nnmaildir--flist-art flist
(car file
)))
802 files
(delq nil files
)
803 files
(mapcar 'nnmaildir--parse-filename files
)
804 files
(sort files
'nnmaildir--sort-files
))
806 (setq file
(if (consp file
) file
(aref file
3))
807 x
(make-nnmaildir--art :prefix
(car file
) :suffix
(cdr file
)))
808 (nnmaildir--grp-add-art nnmaildir--cur-server group x
))
809 (if read-only
(setf (nnmaildir--grp-new group
) nattr
)
810 (setf (nnmaildir--grp-cur group
) cattr
)))
813 (defun nnmaildir-request-scan (&optional scan-group server
)
814 (let ((coding-system-for-write nnheader-file-coding-system
)
815 (buffer-file-coding-system nil
)
816 (file-coding-system-alist nil
)
817 (nnmaildir-get-new-mail t
)
818 (nnmaildir-group-alist nil
)
819 (nnmaildir-active-file nil
)
820 x srv-ls srv-dir method groups target-prefix group dirs grp-dir seen
822 (nnmaildir--prepare server nil
)
823 (setq srv-ls
(nnmaildir--srv-ls nnmaildir--cur-server
)
824 srv-dir
(nnmaildir--srv-dir nnmaildir--cur-server
)
825 method
(nnmaildir--srv-method nnmaildir--cur-server
)
826 groups
(nnmaildir--srv-groups nnmaildir--cur-server
)
827 target-prefix
(nnmaildir--srv-target-prefix nnmaildir--cur-server
))
828 (nnmaildir--with-work-buffer
830 (if (stringp scan-group
)
831 (if (nnmaildir--scan scan-group t groups method srv-dir srv-ls
)
832 (if (nnmaildir--srv-gnm nnmaildir--cur-server
)
833 (nnmail-get-new-mail 'nnmaildir nil nil scan-group
))
834 (unintern scan-group groups
))
835 (setq x
(nth 5 (file-attributes srv-dir
))
836 scan-group
(null scan-group
))
837 (if (equal x
(nnmaildir--srv-mtime nnmaildir--cur-server
))
839 (mapatoms (lambda (sym)
840 (nnmaildir--scan (symbol-name sym
) t groups
841 method srv-dir srv-ls
))
843 (setq dirs
(funcall srv-ls srv-dir nil
"\\`[^.]" 'nosort
)
844 dirs
(if (zerop (length target-prefix
))
848 (and (>= (length dir
) (length target-prefix
))
849 (string= (substring dir
0
850 (length target-prefix
))
853 seen
(nnmaildir--up2-1 (length dirs
))
854 seen
(make-vector seen
0))
855 (dolist (grp-dir dirs
)
856 (if (nnmaildir--scan grp-dir scan-group groups method srv-dir
858 (intern grp-dir seen
)))
860 (mapatoms (lambda (group)
861 (setq group
(symbol-name group
))
862 (unless (intern-soft group seen
)
863 (setq x
(cons group x
))))
866 (unintern grp groups
))
867 (setf (nnmaildir--srv-mtime nnmaildir--cur-server
)
868 (nth 5 (file-attributes srv-dir
))))
870 (nnmaildir--srv-gnm nnmaildir--cur-server
)
871 (nnmail-get-new-mail 'nnmaildir nil nil
))))))
874 (defun nnmaildir-request-list (&optional server
)
875 (nnmaildir-request-scan 'find-new-groups server
)
876 (let (pgname ro deactivate-mark
)
877 (nnmaildir--prepare server nil
)
878 (nnmaildir--with-nntp-buffer
880 (mapatoms (lambda (group)
881 (setq pgname
(symbol-name group
)
882 pgname
(nnmaildir--pgname nnmaildir--cur-server pgname
)
883 group
(symbol-value group
)
884 ro
(nnmaildir--param pgname
'read-only
))
885 (insert (gnus-replace-in-string
886 (nnmaildir--grp-name group
) " " "\\ " t
)
888 (princ (nnmaildir--group-maxnum nnmaildir--cur-server group
)
891 (princ (nnmaildir--grp-min group
) nntp-server-buffer
)
892 (insert " " (if ro
"n" "y") "\n"))
893 (nnmaildir--srv-groups nnmaildir--cur-server
))))
896 (defun nnmaildir-request-newgroups (date &optional server
)
897 (nnmaildir-request-list server
))
899 (defun nnmaildir-retrieve-groups (groups &optional server
)
900 (let (group deactivate-mark
)
901 (nnmaildir--prepare server nil
)
902 (nnmaildir--with-nntp-buffer
904 (dolist (gname groups
)
905 (setq group
(nnmaildir--prepare nil gname
))
906 (if (null group
) (insert "411 no such news group\n")
908 (princ (nnmaildir--grp-count group
) nntp-server-buffer
)
910 (princ (nnmaildir--grp-min group
) nntp-server-buffer
)
912 (princ (nnmaildir--group-maxnum nnmaildir--cur-server group
)
915 (gnus-replace-in-string gname
" " "\\ " t
)
919 (defun nnmaildir-request-marks (gname info
&optional server
)
920 (let ((group (nnmaildir--prepare server gname
))
921 pgname flist always-marks never-marks old-marks dotfile num dir
922 markdirs marks mark ranges markdir article read end new-marks ls
923 old-mmth new-mmth mtime mark-sym existing missing deactivate-mark
927 (setf (nnmaildir--srv-error nnmaildir--cur-server
)
928 (concat "No such group: " gname
))
930 (setq gname
(nnmaildir--grp-name group
)
931 pgname
(nnmaildir--pgname nnmaildir--cur-server gname
)
932 flist
(nnmaildir--grp-flist group
))
933 (when (zerop (nnmaildir--grp-count group
))
934 (gnus-info-set-read info nil
)
935 (gnus-info-set-marks info nil
'extend
)
936 (throw 'return info
))
937 (setq old-marks
(cons 'read
(gnus-info-read info
))
938 old-marks
(cons old-marks
(gnus-info-marks info
))
939 always-marks
(nnmaildir--param pgname
'always-marks
)
940 never-marks
(nnmaildir--param pgname
'never-marks
)
941 existing
(nnmaildir--grp-nlist group
)
942 existing
(mapcar 'car existing
)
943 existing
(nreverse existing
)
944 existing
(gnus-compress-sequence existing
'always-list
)
945 missing
(list (cons 1 (nnmaildir--group-maxnum
946 nnmaildir--cur-server group
)))
947 missing
(gnus-range-difference missing existing
)
948 dir
(nnmaildir--srv-dir nnmaildir--cur-server
)
949 dir
(nnmaildir--srvgrp-dir dir gname
)
950 dir
(nnmaildir--nndir dir
)
951 dir
(nnmaildir--marks-dir dir
)
952 ls
(nnmaildir--group-ls nnmaildir--cur-server pgname
)
953 markdirs
(funcall ls dir nil
"\\`[^.]" 'nosort
)
954 new-mmth
(nnmaildir--up2-1 (length markdirs
))
955 new-mmth
(make-vector new-mmth
0)
956 old-mmth
(nnmaildir--grp-mmth group
))
957 (dolist (mark markdirs
)
958 (setq markdir
(nnmaildir--subdir dir mark
)
959 mark-sym
(intern mark
)
962 (if (memq mark-sym never-marks
) (throw 'got-ranges nil
))
963 (when (memq mark-sym always-marks
)
964 (setq ranges existing
)
965 (throw 'got-ranges nil
))
966 (setq mtime
(nth 5 (file-attributes markdir
)))
967 (set (intern mark new-mmth
) mtime
)
968 (when (equal mtime
(symbol-value (intern-soft mark old-mmth
)))
969 (setq ranges
(assq mark-sym old-marks
))
970 (if ranges
(setq ranges
(cdr ranges
)))
971 (throw 'got-ranges nil
))
972 (setq article-list nil
)
973 (dolist (prefix (funcall ls markdir nil
"\\`[^.]" 'nosort
))
974 (setq article
(nnmaildir--flist-art flist prefix
))
977 (cons (nnmaildir--art-num article
) article-list
))))
978 (setq ranges
(gnus-add-to-range ranges
(sort article-list
'<))))
979 (if (eq mark-sym
'read
) (setq read ranges
)
980 (if ranges
(setq marks
(cons (cons mark-sym ranges
) marks
)))))
981 (gnus-info-set-read info
(gnus-range-add read missing
))
982 (gnus-info-set-marks info marks
'extend
)
983 (setf (nnmaildir--grp-mmth group
) new-mmth
)
986 (defun nnmaildir-request-group (gname &optional server fast info
)
987 (let ((group (nnmaildir--prepare server gname
))
991 ;; (insert "411 no such news group\n")
992 (setf (nnmaildir--srv-error nnmaildir--cur-server
)
993 (concat "No such group: " gname
))
995 (setf (nnmaildir--srv-curgrp nnmaildir--cur-server
) group
)
996 (if fast
(throw 'return t
))
997 (nnmaildir--with-nntp-buffer
1000 (princ (nnmaildir--grp-count group
) nntp-server-buffer
)
1002 (princ (nnmaildir--grp-min group
) nntp-server-buffer
)
1004 (princ (nnmaildir--group-maxnum nnmaildir--cur-server group
)
1006 (insert " " (gnus-replace-in-string gname
" " "\\ " t
) "\n")
1009 (defun nnmaildir-request-create-group (gname &optional server args
)
1010 (nnmaildir--prepare server nil
)
1012 (let ((target-prefix (nnmaildir--srv-target-prefix nnmaildir--cur-server
))
1014 (when (zerop (length gname
))
1015 (setf (nnmaildir--srv-error nnmaildir--cur-server
)
1016 "Invalid (empty) group name")
1017 (throw 'return nil
))
1018 (when (eq (aref "." 0) (aref gname
0))
1019 (setf (nnmaildir--srv-error nnmaildir--cur-server
)
1020 "Group names may not start with \".\"")
1021 (throw 'return nil
))
1022 (when (save-match-data (string-match "[\0/\t]" gname
))
1023 (setf (nnmaildir--srv-error nnmaildir--cur-server
)
1024 (concat "Invalid characters (null, tab, or /) in group name: "
1026 (throw 'return nil
))
1027 (setq groups
(nnmaildir--srv-groups nnmaildir--cur-server
))
1028 (when (intern-soft gname groups
)
1029 (setf (nnmaildir--srv-error nnmaildir--cur-server
)
1030 (concat "Group already exists: " gname
))
1031 (throw 'return nil
))
1032 (setq srv-dir
(nnmaildir--srv-dir nnmaildir--cur-server
))
1033 (if (file-name-absolute-p target-prefix
)
1034 (setq dir
(expand-file-name target-prefix
))
1036 dir
(file-truename dir
)
1037 dir
(concat dir target-prefix
)))
1038 (setq dir
(nnmaildir--subdir dir gname
))
1039 (nnmaildir--mkdir dir
)
1040 (nnmaildir--mkdir (nnmaildir--tmp dir
))
1041 (nnmaildir--mkdir (nnmaildir--new dir
))
1042 (nnmaildir--mkdir (nnmaildir--cur dir
))
1043 (unless (string= target-prefix
"")
1044 (make-symbolic-link (concat target-prefix gname
)
1045 (concat srv-dir gname
)))
1046 (nnmaildir-request-scan 'find-new-groups
))))
1048 (defun nnmaildir-request-rename-group (gname new-name
&optional server
)
1049 (let ((group (nnmaildir--prepare server gname
))
1050 (coding-system-for-write nnheader-file-coding-system
)
1051 (buffer-file-coding-system nil
)
1052 (file-coding-system-alist nil
)
1056 (setf (nnmaildir--srv-error nnmaildir--cur-server
)
1057 (concat "No such group: " gname
))
1058 (throw 'return nil
))
1059 (when (zerop (length new-name
))
1060 (setf (nnmaildir--srv-error nnmaildir--cur-server
)
1061 "Invalid (empty) group name")
1062 (throw 'return nil
))
1063 (when (eq (aref "." 0) (aref new-name
0))
1064 (setf (nnmaildir--srv-error nnmaildir--cur-server
)
1065 "Group names may not start with \".\"")
1066 (throw 'return nil
))
1067 (when (save-match-data (string-match "[\0/\t]" new-name
))
1068 (setf (nnmaildir--srv-error nnmaildir--cur-server
)
1069 (concat "Invalid characters (null, tab, or /) in group name: "
1071 (throw 'return nil
))
1072 (if (string-equal gname new-name
) (throw 'return t
))
1073 (when (intern-soft new-name
1074 (nnmaildir--srv-groups nnmaildir--cur-server
))
1075 (setf (nnmaildir--srv-error nnmaildir--cur-server
)
1076 (concat "Group already exists: " new-name
))
1077 (throw 'return nil
))
1078 (setq srv-dir
(nnmaildir--srv-dir nnmaildir--cur-server
))
1080 (rename-file (concat srv-dir gname
)
1081 (concat srv-dir new-name
))
1083 (setf (nnmaildir--srv-error nnmaildir--cur-server
)
1084 (concat "Error renaming link: " (prin1-to-string err
)))
1085 (throw 'return nil
)))
1086 (setq x
(nnmaildir--srv-groups nnmaildir--cur-server
)
1087 groups
(make-vector (length x
) 0))
1088 (mapatoms (lambda (sym)
1089 (unless (eq (symbol-value sym
) group
)
1090 (set (intern (symbol-name sym
) groups
)
1091 (symbol-value sym
))))
1093 (setq group
(copy-sequence group
))
1094 (setf (nnmaildir--grp-name group
) new-name
)
1095 (set (intern new-name groups
) group
)
1096 (setf (nnmaildir--srv-groups nnmaildir--cur-server
) groups
)
1099 (defun nnmaildir-request-delete-group (gname force
&optional server
)
1100 (let ((group (nnmaildir--prepare server gname
))
1101 pgname grp-dir target dir ls deactivate-mark
)
1104 (setf (nnmaildir--srv-error nnmaildir--cur-server
)
1105 (concat "No such group: " gname
))
1106 (throw 'return nil
))
1107 (setq gname
(nnmaildir--grp-name group
)
1108 pgname
(nnmaildir--pgname nnmaildir--cur-server gname
)
1109 grp-dir
(nnmaildir--srv-dir nnmaildir--cur-server
)
1110 target
(car (file-attributes (concat grp-dir gname
)))
1111 grp-dir
(nnmaildir--srvgrp-dir grp-dir gname
))
1112 (unless (or force
(stringp target
))
1113 (setf (nnmaildir--srv-error nnmaildir--cur-server
)
1114 (concat "Not a symlink: " gname
))
1115 (throw 'return nil
))
1116 (if (eq group
(nnmaildir--srv-curgrp nnmaildir--cur-server
))
1117 (setf (nnmaildir--srv-curgrp nnmaildir--cur-server
) nil
))
1118 (unintern gname
(nnmaildir--srv-groups nnmaildir--cur-server
))
1121 (setq grp-dir
(directory-file-name grp-dir
))
1122 (nnmaildir--unlink grp-dir
))
1123 (setq ls
(nnmaildir--group-ls nnmaildir--cur-server pgname
))
1124 (if (nnmaildir--param pgname
'read-only
)
1125 (progn (delete-directory (nnmaildir--tmp grp-dir
))
1126 (nnmaildir--unlink (nnmaildir--new grp-dir
))
1127 (delete-directory (nnmaildir--cur grp-dir
)))
1128 (nnmaildir--delete-dir-files (nnmaildir--tmp grp-dir
) ls
)
1129 (nnmaildir--delete-dir-files (nnmaildir--new grp-dir
) ls
)
1130 (nnmaildir--delete-dir-files (nnmaildir--cur grp-dir
) ls
))
1131 (setq dir
(nnmaildir--nndir grp-dir
))
1132 (dolist (subdir `(,(nnmaildir--nov-dir dir
) ,(nnmaildir--num-dir dir
)
1133 ,@(funcall ls
(nnmaildir--marks-dir dir
)
1134 'full
"\\`[^.]" 'nosort
)))
1135 (nnmaildir--delete-dir-files subdir ls
))
1136 (setq dir
(nnmaildir--nndir grp-dir
))
1137 (nnmaildir--unlink (concat dir
"markfile"))
1138 (nnmaildir--unlink (concat dir
"markfile{new}"))
1139 (delete-directory (nnmaildir--marks-dir dir
))
1140 (delete-directory dir
)
1141 (if (not (stringp target
))
1142 (delete-directory grp-dir
)
1143 (setq grp-dir
(directory-file-name grp-dir
)
1145 (unless (eq (aref "/" 0) (aref dir
0))
1146 (setq dir
(concat (file-truename
1147 (nnmaildir--srv-dir nnmaildir--cur-server
))
1149 (delete-directory dir
)
1150 (nnmaildir--unlink grp-dir
)))
1153 (defun nnmaildir-retrieve-headers (articles &optional gname server fetch-old
)
1154 (let ((group (nnmaildir--prepare server gname
))
1155 srv-dir dir nlist mlist article num start stop nov nlist2 insert-nov
1159 (setq nov
(nnmaildir--update-nov nnmaildir--cur-server group
1162 (nnmaildir--cache-nov group article nov
)
1163 (setq num
(nnmaildir--art-num article
))
1164 (princ num nntp-server-buffer
)
1165 (insert "\t" (nnmaildir--nov-get-beg nov
) "\t"
1166 (nnmaildir--art-msgid article
) "\t"
1167 (nnmaildir--nov-get-mid nov
) "\tXref: nnmaildir "
1168 (gnus-replace-in-string gname
" " "\\ " t
) ":")
1169 (princ num nntp-server-buffer
)
1170 (insert "\t" (nnmaildir--nov-get-end nov
) "\n"))))
1173 (setf (nnmaildir--srv-error nnmaildir--cur-server
)
1174 (if gname
(concat "No such group: " gname
) "No current group"))
1175 (throw 'return nil
))
1176 (nnmaildir--with-nntp-buffer
1178 (setq mlist
(nnmaildir--grp-mlist group
)
1179 nlist
(nnmaildir--grp-nlist group
)
1180 gname
(nnmaildir--grp-name group
)
1181 srv-dir
(nnmaildir--srv-dir nnmaildir--cur-server
)
1182 dir
(nnmaildir--srvgrp-dir srv-dir gname
))
1185 ((and fetch-old
(not (numberp fetch-old
)))
1186 (nnmaildir--nlist-iterate nlist
'all insert-nov
))
1188 ((stringp (car articles
))
1189 (dolist (msgid articles
)
1190 (setq article
(nnmaildir--mlist-art mlist msgid
))
1191 (if article
(funcall insert-nov article
))))
1194 ;; Assume the article range list is sorted ascending
1195 (setq stop
(car articles
)
1196 start
(car (last articles
))
1197 stop
(if (numberp stop
) stop
(car stop
))
1198 start
(if (numberp start
) start
(cdr start
))
1199 stop
(- stop fetch-old
)
1200 stop
(if (< stop
1) 1 stop
)
1201 articles
(list (cons stop start
))))
1202 (nnmaildir--nlist-iterate nlist articles insert-nov
)))
1203 (sort-numeric-fields 1 (point-min) (point-max))
1206 (defun nnmaildir-request-article (num-msgid &optional gname server to-buffer
)
1207 (let ((group (nnmaildir--prepare server gname
))
1208 (case-fold-search t
)
1209 list article dir pgname deactivate-mark
)
1212 (setf (nnmaildir--srv-error nnmaildir--cur-server
)
1213 (if gname
(concat "No such group: " gname
) "No current group"))
1214 (throw 'return nil
))
1215 (if (numberp num-msgid
)
1216 (setq article
(nnmaildir--nlist-art group num-msgid
))
1217 (setq list
(nnmaildir--grp-mlist group
)
1218 article
(nnmaildir--mlist-art list num-msgid
))
1219 (if article
(setq num-msgid
(nnmaildir--art-num article
))
1223 (setq group
(symbol-value group-sym
)
1224 list
(nnmaildir--grp-mlist group
)
1225 article
(nnmaildir--mlist-art list num-msgid
))
1227 (setq num-msgid
(nnmaildir--art-num article
))
1228 (throw 'found nil
)))
1229 (nnmaildir--srv-groups nnmaildir--cur-server
))))
1231 (setf (nnmaildir--srv-error nnmaildir--cur-server
) "No such article")
1232 (throw 'return nil
)))
1233 (setq gname
(nnmaildir--grp-name group
)
1234 pgname
(nnmaildir--pgname nnmaildir--cur-server gname
)
1235 dir
(nnmaildir--srv-dir nnmaildir--cur-server
)
1236 dir
(nnmaildir--srvgrp-dir dir gname
)
1237 dir
(if (nnmaildir--param pgname
'read-only
)
1238 (nnmaildir--new dir
) (nnmaildir--cur dir
))
1239 nnmaildir-article-file-name
1241 (nnmaildir--art-prefix article
)
1242 (nnmaildir--art-suffix article
)))
1243 (unless (file-exists-p nnmaildir-article-file-name
)
1244 (nnmaildir--expired-article group article
)
1245 (setf (nnmaildir--srv-error nnmaildir--cur-server
)
1246 "Article has expired")
1247 (throw 'return nil
))
1248 (with-current-buffer (or to-buffer nntp-server-buffer
)
1250 (nnheader-insert-file-contents nnmaildir-article-file-name
))
1251 (cons gname num-msgid
))))
1253 (defun nnmaildir-request-post (&optional server
)
1254 (let (message-required-mail-headers)
1255 (funcall message-send-mail-function
)))
1257 (defun nnmaildir-request-replace-article (number gname buffer
)
1258 (let ((group (nnmaildir--prepare nil gname
))
1259 (coding-system-for-write nnheader-file-coding-system
)
1260 (buffer-file-coding-system nil
)
1261 (file-coding-system-alist nil
)
1262 dir file article suffix tmpfile deactivate-mark
)
1265 (setf (nnmaildir--srv-error nnmaildir--cur-server
)
1266 (concat "No such group: " gname
))
1267 (throw 'return nil
))
1268 (when (nnmaildir--param (nnmaildir--pgname nnmaildir--cur-server gname
)
1270 (setf (nnmaildir--srv-error nnmaildir--cur-server
)
1271 (concat "Read-only group: " group
))
1272 (throw 'return nil
))
1273 (setq dir
(nnmaildir--srv-dir nnmaildir--cur-server
)
1274 dir
(nnmaildir--srvgrp-dir dir gname
)
1275 article
(nnmaildir--nlist-art group number
))
1277 (setf (nnmaildir--srv-error nnmaildir--cur-server
)
1278 (concat "No such article: " (number-to-string number
)))
1279 (throw 'return nil
))
1280 (setq suffix
(nnmaildir--art-suffix article
)
1281 file
(nnmaildir--art-prefix article
)
1282 tmpfile
(concat (nnmaildir--tmp dir
) file
))
1283 (when (file-exists-p tmpfile
)
1284 (setf (nnmaildir--srv-error nnmaildir--cur-server
)
1285 (concat "File exists: " tmpfile
))
1286 (throw 'return nil
))
1287 (with-current-buffer buffer
1288 (gmm-write-region (point-min) (point-max) tmpfile nil
'no-message nil
1290 (unix-sync) ;; no fsync :(
1291 (rename-file tmpfile
(concat (nnmaildir--cur dir
) file suffix
) 'replace
)
1294 (defun nnmaildir-request-move-article (article gname server accept-form
1295 &optional last move-is-internal
)
1296 (let ((group (nnmaildir--prepare server gname
))
1297 pgname suffix result nnmaildir--file deactivate-mark
)
1300 (setf (nnmaildir--srv-error nnmaildir--cur-server
)
1301 (concat "No such group: " gname
))
1302 (throw 'return nil
))
1303 (setq gname
(nnmaildir--grp-name group
)
1304 pgname
(nnmaildir--pgname nnmaildir--cur-server gname
)
1305 article
(nnmaildir--nlist-art group article
))
1307 (setf (nnmaildir--srv-error nnmaildir--cur-server
) "No such article")
1308 (throw 'return nil
))
1309 (setq suffix
(nnmaildir--art-suffix article
)
1310 nnmaildir--file
(nnmaildir--srv-dir nnmaildir--cur-server
)
1311 nnmaildir--file
(nnmaildir--srvgrp-dir nnmaildir--file gname
)
1312 nnmaildir--file
(if (nnmaildir--param pgname
'read-only
)
1313 (nnmaildir--new nnmaildir--file
)
1314 (nnmaildir--cur nnmaildir--file
))
1315 nnmaildir--file
(concat nnmaildir--file
1316 (nnmaildir--art-prefix article
)
1318 (unless (file-exists-p nnmaildir--file
)
1319 (nnmaildir--expired-article group article
)
1320 (setf (nnmaildir--srv-error nnmaildir--cur-server
)
1321 "Article has expired")
1322 (throw 'return nil
))
1323 (nnmaildir--with-move-buffer
1325 (nnheader-insert-file-contents nnmaildir--file
)
1326 (setq result
(eval accept-form
)))
1327 (unless (or (null result
) (nnmaildir--param pgname
'read-only
))
1328 (nnmaildir--unlink nnmaildir--file
)
1329 (nnmaildir--expired-article group article
))
1332 (defun nnmaildir-request-accept-article (gname &optional server last
)
1333 (let ((group (nnmaildir--prepare server gname
))
1334 (coding-system-for-write nnheader-file-coding-system
)
1335 (buffer-file-coding-system nil
)
1336 (file-coding-system-alist nil
)
1337 srv-dir dir file time tmpfile curfile
24h article
)
1340 (setf (nnmaildir--srv-error nnmaildir--cur-server
)
1341 (concat "No such group: " gname
))
1342 (throw 'return nil
))
1343 (setq gname
(nnmaildir--grp-name group
))
1344 (when (nnmaildir--param (nnmaildir--pgname nnmaildir--cur-server gname
)
1346 (setf (nnmaildir--srv-error nnmaildir--cur-server
)
1347 (concat "Read-only group: " gname
))
1348 (throw 'return nil
))
1349 (setq srv-dir
(nnmaildir--srv-dir nnmaildir--cur-server
)
1350 dir
(nnmaildir--srvgrp-dir srv-dir gname
)
1352 file
(format-time-string "%s." time
))
1353 (unless (string-equal nnmaildir--delivery-time file
)
1354 (setq nnmaildir--delivery-time file
1355 nnmaildir--delivery-count
0))
1356 (when (and (consp (cdr time
))
1357 (consp (cddr time
)))
1358 (setq file
(concat file
"M" (number-to-string (caddr time
)))))
1359 (setq file
(concat file nnmaildir--delivery-pid
)
1360 file
(concat file
"Q" (number-to-string nnmaildir--delivery-count
))
1361 file
(concat file
"." (nnmaildir--system-name))
1362 tmpfile
(concat (nnmaildir--tmp dir
) file
)
1363 curfile
(concat (nnmaildir--cur dir
) file
":2,"))
1364 (when (file-exists-p tmpfile
)
1365 (setf (nnmaildir--srv-error nnmaildir--cur-server
)
1366 (concat "File exists: " tmpfile
))
1367 (throw 'return nil
))
1368 (when (file-exists-p curfile
)
1369 (setf (nnmaildir--srv-error nnmaildir--cur-server
)
1370 (concat "File exists: " curfile
))
1371 (throw 'return nil
))
1372 (setq nnmaildir--delivery-count
(1+ nnmaildir--delivery-count
)
1373 24h
(run-with-timer 86400 nil
1375 (nnmaildir--unlink tmpfile
)
1376 (setf (nnmaildir--srv-error
1377 nnmaildir--cur-server
)
1378 "24-hour timer expired")
1379 (throw 'return nil
))))
1380 (condition-case nil
(add-name-to-file nnmaildir--file tmpfile
)
1382 (gmm-write-region (point-min) (point-max) tmpfile nil
'no-message nil
1384 (unix-sync))) ;; no fsync :(
1385 (nnheader-cancel-timer 24h
)
1387 (add-name-to-file tmpfile curfile
)
1389 (setf (nnmaildir--srv-error nnmaildir--cur-server
)
1390 (concat "Error linking: " (prin1-to-string err
)))
1391 (nnmaildir--unlink tmpfile
)
1392 (throw 'return nil
)))
1393 (nnmaildir--unlink tmpfile
)
1394 (setq article
(make-nnmaildir--art :prefix file
:suffix
":2,"))
1395 (if (nnmaildir--grp-add-art nnmaildir--cur-server group article
)
1396 (cons gname
(nnmaildir--art-num article
))))))
1398 (defun nnmaildir-save-mail (group-art)
1401 (throw 'return nil
))
1402 (let (ga gname x groups nnmaildir--file deactivate-mark
)
1404 (goto-char (point-min))
1406 (while (looking-at "From ")
1407 (replace-match "X-From-Line: ")
1409 (setq groups
(nnmaildir--srv-groups nnmaildir--cur-server
)
1410 ga
(car group-art
) group-art
(cdr group-art
)
1412 (or (intern-soft gname groups
)
1413 (nnmaildir-request-create-group gname
)
1414 (throw 'return nil
)) ;; not that nnmail bothers to check :(
1415 (unless (nnmaildir-request-accept-article gname
)
1416 (throw 'return nil
))
1417 (setq nnmaildir--file
(nnmaildir--srv-dir nnmaildir--cur-server
)
1418 nnmaildir--file
(nnmaildir--srvgrp-dir nnmaildir--file gname
)
1419 x
(nnmaildir--prepare nil gname
)
1420 x
(nnmaildir--grp-nlist x
)
1422 nnmaildir--file
(concat nnmaildir--file
1423 (nnmaildir--art-prefix x
)
1424 (nnmaildir--art-suffix x
)))
1428 (setq gname
(car ga
))
1429 (and (or (intern-soft gname groups
)
1430 (nnmaildir-request-create-group gname
))
1431 (nnmaildir-request-accept-article gname
)
1435 (defun nnmaildir-active-number (gname)
1438 (declare-function gnus-group-mark-article-read
"gnus-group" (group article
))
1440 (defun nnmaildir-request-expire-articles (ranges &optional gname server force
)
1441 (let ((no-force (not force
))
1442 (group (nnmaildir--prepare server gname
))
1443 pgname time boundary bound-iter high low target dir nlist nlist2
1444 stop article didnt nnmaildir--file nnmaildir-article-file-name
1448 (setf (nnmaildir--srv-error nnmaildir--cur-server
)
1449 (if gname
(concat "No such group: " gname
) "No current group"))
1450 (throw 'return
(gnus-uncompress-range ranges
)))
1451 (setq gname
(nnmaildir--grp-name group
)
1452 pgname
(nnmaildir--pgname nnmaildir--cur-server gname
))
1453 (if (nnmaildir--param pgname
'read-only
)
1454 (throw 'return
(gnus-uncompress-range ranges
)))
1455 (setq time
(nnmaildir--param pgname
'expire-age
))
1457 (setq time
(or (and nnmail-expiry-wait-function
1458 (funcall nnmail-expiry-wait-function gname
))
1459 nnmail-expiry-wait
))
1460 (if (eq time
'immediate
)
1463 (setq time
(* time
86400)))))
1465 (unless (integerp time
) ;; handle 'never
1466 (throw 'return
(gnus-uncompress-range ranges
)))
1467 (setq boundary
(current-time)
1468 high
(- (car boundary
) (/ time
65536))
1469 low
(- (cadr boundary
) (% time
65536)))
1471 (setq low
(+ low
65536)
1473 (setcar (cdr boundary
) low
)
1474 (setcar boundary high
))
1475 (setq dir
(nnmaildir--srv-dir nnmaildir--cur-server
)
1476 dir
(nnmaildir--srvgrp-dir dir gname
)
1477 dir
(nnmaildir--cur dir
)
1478 nlist
(nnmaildir--grp-nlist group
)
1479 ranges
(reverse ranges
))
1480 (nnmaildir--with-move-buffer
1481 (nnmaildir--nlist-iterate
1484 (setq nnmaildir--file
(nnmaildir--art-prefix article
)
1485 nnmaildir--file
(concat dir nnmaildir--file
1486 (nnmaildir--art-suffix article
))
1487 time
(file-attributes nnmaildir--file
))
1490 (nnmaildir--expired-article group article
))
1493 (setq time
(nth 5 time
)
1494 bound-iter boundary
)
1495 (while (and bound-iter time
1496 (= (car bound-iter
) (car time
)))
1497 (setq bound-iter
(cdr bound-iter
)
1499 (and bound-iter time
1500 (car-less-than-car bound-iter time
))))
1501 (setq didnt
(cons (nnmaildir--art-num article
) didnt
)))
1503 (setq nnmaildir-article-file-name nnmaildir--file
1504 target
(if force nil
1507 (nnmaildir--param pgname
'expire-group
)))))
1508 (when (and (stringp target
)
1509 (not (string-equal target pgname
))) ;; Move it.
1511 (nnheader-insert-file-contents nnmaildir--file
)
1512 (let ((group-art (gnus-request-accept-article
1513 target nil nil
'no-encode
)))
1514 (when (consp group-art
)
1515 ;; Maybe also copy: dormant forward reply save tick
1516 ;; (gnus-add-mark? gnus-request-set-mark?)
1517 (gnus-group-mark-article-read target
(cdr group-art
)))))
1518 (if (equal target pgname
)
1520 (setq didnt
(cons (nnmaildir--art-num article
) didnt
))
1521 (nnmaildir--unlink nnmaildir--file
)
1522 (nnmaildir--expired-article group article
))))))
1526 (defun nnmaildir-request-set-mark (gname actions
&optional server
)
1527 (let ((group (nnmaildir--prepare server gname
))
1528 (coding-system-for-write nnheader-file-coding-system
)
1529 (buffer-file-coding-system nil
)
1530 (file-coding-system-alist nil
)
1531 del-mark del-action add-action set-action marksdir nlist
1532 ranges begin end article all-marks todo-marks mdir mfile
1533 pgname ls permarkfile deactivate-mark
)
1536 (setq mfile
(nnmaildir--subdir marksdir
(symbol-name mark
))
1537 mfile
(concat mfile
(nnmaildir--art-prefix article
)))
1538 (nnmaildir--unlink mfile
))
1539 del-action
(lambda (article) (mapcar del-mark todo-marks
))
1544 (setq mdir
(nnmaildir--subdir marksdir
(symbol-name mark
))
1545 permarkfile
(concat mdir
":")
1546 mfile
(concat mdir
(nnmaildir--art-prefix article
)))
1547 (nnmaildir--condcase err
(add-name-to-file permarkfile mfile
)
1549 ((nnmaildir--eexist-p err
))
1550 ((nnmaildir--enoent-p err
)
1551 (nnmaildir--mkdir mdir
)
1552 (nnmaildir--mkfile permarkfile
)
1553 (add-name-to-file permarkfile mfile
))
1554 ((nnmaildir--emlink-p err
)
1555 (let ((permarkfilenew (concat permarkfile
"{new}")))
1556 (nnmaildir--mkfile permarkfilenew
)
1557 (rename-file permarkfilenew permarkfile
'replace
)
1558 (add-name-to-file permarkfile mfile
)))
1559 (t (signal (car err
) (cdr err
))))))
1561 set-action
(lambda (article)
1562 (funcall add-action
)
1563 (mapcar (lambda (mark)
1564 (unless (memq mark todo-marks
)
1565 (funcall del-mark mark
)))
1569 (setf (nnmaildir--srv-error nnmaildir--cur-server
)
1570 (concat "No such group: " gname
))
1571 (dolist (action actions
)
1572 (setq ranges
(gnus-range-add ranges
(car action
))))
1573 (throw 'return ranges
))
1574 (setq nlist
(nnmaildir--grp-nlist group
)
1575 marksdir
(nnmaildir--srv-dir nnmaildir--cur-server
)
1576 marksdir
(nnmaildir--srvgrp-dir marksdir gname
)
1577 marksdir
(nnmaildir--nndir marksdir
)
1578 marksdir
(nnmaildir--marks-dir marksdir
)
1579 gname
(nnmaildir--grp-name group
)
1580 pgname
(nnmaildir--pgname nnmaildir--cur-server gname
)
1581 ls
(nnmaildir--group-ls nnmaildir--cur-server pgname
)
1582 all-marks
(funcall ls marksdir nil
"\\`[^.]" 'nosort
)
1583 all-marks
(mapcar 'intern all-marks
))
1584 (dolist (action actions
)
1585 (setq ranges
(car action
)
1586 todo-marks
(caddr action
))
1587 (dolist (mark todo-marks
)
1588 (add-to-list 'all-marks mark
))
1589 (if (numberp (cdr ranges
)) (setq ranges
(list ranges
)))
1590 (nnmaildir--nlist-iterate nlist ranges
1591 (cond ((eq 'del
(cadr action
)) del-action
)
1592 ((eq 'add
(cadr action
)) add-action
)
1596 (defun nnmaildir-close-group (gname &optional server
)
1597 (let ((group (nnmaildir--prepare server gname
))
1598 pgname ls dir msgdir files flist dirs
)
1601 (setf (nnmaildir--srv-error nnmaildir--cur-server
)
1602 (concat "No such group: " gname
))
1604 (setq pgname
(nnmaildir--pgname nnmaildir--cur-server gname
)
1605 ls
(nnmaildir--group-ls nnmaildir--cur-server pgname
)
1606 dir
(nnmaildir--srv-dir nnmaildir--cur-server
)
1607 dir
(nnmaildir--srvgrp-dir dir gname
)
1608 msgdir
(if (nnmaildir--param pgname
'read-only
)
1609 (nnmaildir--new dir
) (nnmaildir--cur dir
))
1610 dir
(nnmaildir--nndir dir
)
1611 dirs
(cons (nnmaildir--nov-dir dir
)
1612 (funcall ls
(nnmaildir--marks-dir dir
) 'full
"\\`[^.]"
1616 (cons dir
(funcall ls dir nil
"\\`[^.]" 'nosort
)))
1618 files
(funcall ls msgdir nil
"\\`[^.]" 'nosort
)
1619 flist
(nnmaildir--up2-1 (length files
))
1620 flist
(make-vector flist
0))
1622 (dolist (file files
)
1623 (string-match "\\`\\([^:]*\\)\\(:.*\\)?\\'" file
)
1624 (intern (match-string 1 file
) flist
)))
1626 (setq files
(cdr dir
)
1627 dir
(file-name-as-directory (car dir
)))
1628 (dolist (file files
)
1629 (unless (or (intern-soft file flist
) (string= file
":"))
1630 (setq file
(concat dir file
))
1631 (delete-file file
))))
1634 (defun nnmaildir-close-server (&optional server
)
1635 (let (flist ls dirs dir files file x
)
1636 (nnmaildir--prepare server nil
)
1637 (when nnmaildir--cur-server
1638 (setq server nnmaildir--cur-server
1639 nnmaildir--cur-server nil
)
1640 (unintern (nnmaildir--srv-address server
) nnmaildir--servers
)))
1643 (defun nnmaildir-request-close ()
1644 (let (servers buffer
)
1645 (mapatoms (lambda (server)
1646 (setq servers
(cons (symbol-name server
) servers
)))
1648 (mapc 'nnmaildir-close-server servers
)
1649 (setq buffer
(get-buffer " *nnmaildir work*"))
1650 (if buffer
(kill-buffer buffer
))
1651 (setq buffer
(get-buffer " *nnmaildir nov*"))
1652 (if buffer
(kill-buffer buffer
))
1653 (setq buffer
(get-buffer " *nnmaildir move*"))
1654 (if buffer
(kill-buffer buffer
)))
1657 (provide 'nnmaildir
)
1660 ;; indent-tabs-mode: t
1664 ;;; nnmaildir.el ends here