;; copying, restoring, etc.
;;
;; Todo:
-;; * Merge the information from <URL:http://multivac.cwru.edu./nnmaildir/>
-;; into the Gnus manual.
-;; * Allow create-directory = ".", and configurable prefix of maildir names,
-;; stripped off to produce group names.
;; * Add a hook for when moving messages from new/ to cur/, to support
;; nnmail's duplicate detection.
-;; * Allow each mark directory in a group to have its own inode for mark
-;; files, to accommodate AFS.
;; * Improve generated Xrefs, so crossposts are detectable.
+;; * Improve code readability.
;;; Code:
;; Variables to generate filenames of messages being delivered:
(defvar nnmaildir--delivery-time "")
-(defconst nnmaildir--delivery-pid (number-to-string (emacs-pid)))
-(defvar nnmaildir--delivery-ct nil)
+(defconst nnmaildir--delivery-pid (concat "P" (number-to-string (emacs-pid))))
+(defvar nnmaildir--delivery-count nil)
;; An obarry containing symbols whose names are server names and whose values
;; are servers:
; ("Mark Mod Time Hash")
(defstruct nnmaildir--srv
- (address nil :type string) ;; server address string
- (method nil :type list) ;; (nnmaildir "address" ...)
- (prefix nil :type string) ;; "nnmaildir+address:"
- (dir nil :type string) ;; "/expanded/path/to/server/dir/"
- (ls nil :type function) ;; directory-files function
- (groups nil :type vector) ;; obarray mapping group names->groups
- (curgrp nil :type nnmaildir--grp) ;; current group, or nil
- (error nil :type string) ;; last error message, or nil
- (mtime nil :type list) ;; modtime of dir
- (gnm nil) ;; flag: split from mail-sources?
- (create-dir nil :type string)) ;; group creation directory
+ (address nil :type string) ;; server address string
+ (method nil :type list) ;; (nnmaildir "address" ...)
+ (prefix nil :type string) ;; "nnmaildir+address:"
+ (dir nil :type string) ;; "/expanded/path/to/server/dir/"
+ (ls nil :type function) ;; directory-files function
+ (groups nil :type vector) ;; obarray mapping group name->group
+ (curgrp nil :type nnmaildir--grp) ;; current group, or nil
+ (error nil :type string) ;; last error message, or nil
+ (mtime nil :type list) ;; modtime of dir
+ (gnm nil) ;; flag: split from mail-sources?
+ (target-prefix nil :type string)) ;; symlink target prefix
(defun nnmaildir--expired-article (group article)
(setf (nnmaildir--art-nov article) nil)
(or (file-exists-p (file-name-as-directory dir))
(make-directory-internal (directory-file-name dir))))
(defun nnmaildir--delete-dir-files (dir ls)
- (mapcar 'delete-file (funcall ls dir 'full "\\`[^.]" 'nosort))
- (delete-directory dir))
+ (when (file-attributes dir)
+ (mapcar 'delete-file (funcall ls dir 'full "\\`[^.]" 'nosort))
+ (delete-directory dir)))
(defun nnmaildir--group-maxnum (server group)
- (let ((x (nnmaildir--srvgrp-dir (nnmaildir--srv-dir server)
- (nnmaildir--grp-name group))))
- (setq x (nnmaildir--nndir x)
- x (nnmaildir--num-dir x)
- x (nnmaildir--num-file x)
- x (file-attributes x))
- (if x (1- (nth 1 x)) 0)))
+ (if (zerop (nnmaildir--grp-count group)) 0
+ (let ((x (nnmaildir--srvgrp-dir (nnmaildir--srv-dir server)
+ (nnmaildir--grp-name group))))
+ (setq x (nnmaildir--nndir x)
+ x (nnmaildir--num-dir x)
+ x (nnmaildir--num-file x)
+ x (file-attributes x))
+ (if x (1- (nth 1 x)) 0))))
;; Make the given server, if non-nil, be the current server. Then make the
;; given group, if non-nil, be the current group of the current server. Then
(setq nov-mid 0))
(goto-char (point-min))
(delete-char 1)
- (nnheader-fold-continuation-lines)
- (setq nov (nnheader-parse-head 'naked)
+ (setq nov (nnheader-parse-naked-head)
field (or (mail-header-lines nov) 0)))
(unless (or (zerop field) (nnmaildir--param pgname 'distrust-Lines:))
(setq nov-mid field))
nov-end (mapconcat
(lambda (extra)
(setq field (symbol-name (car extra))
- val (cdr field))
+ val (cdr extra))
(nnmaildir--tab-to-space field)
(nnmaildir--tab-to-space val)
(concat field ": " val))
count num min nlist nlist-cdr insert-nlist)
(when nov
(setq count (1+ (nnmaildir--grp-count group))
- min (nnmaildir--grp-min group)
- num (nnmaildir--art-num article))
- (setq min (min min num))
- (setq nlist (nnmaildir--grp-nlist group))
+ num (nnmaildir--art-num article)
+ min (if (= count 1) num
+ (min num (nnmaildir--grp-min group)))
+ nlist (nnmaildir--grp-nlist group))
(if (or (null nlist) (> num (caar nlist)))
(setq nlist (cons (cons num article) nlist))
(setq insert-nlist t
nlist-cdr (cdr nlist))
- (while (< num (caar nlist-cdr))
+ (while (and nlist-cdr (< num (caar nlist-cdr)))
(setq nlist nlist-cdr
nlist-cdr (cdr nlist))))
(let ((inhibit-quit t))
(defun nnmaildir--up2-1 (n)
(if (zerop n) 1 (1- (lsh 1 (1+ (logb n))))))
+(defun nnmaildir--system-name ()
+ (gnus-replace-in-string
+ (gnus-replace-in-string
+ (gnus-replace-in-string
+ (system-name)
+ "\\\\" "\\134" 'literal)
+ "/" "\\057" 'literal)
+ ":" "\\072" 'literal))
+
(defun nnmaildir-request-type (group &optional article)
'mail)
(car x)
(setf (nnmaildir--srv-gnm server) t)
(require 'nnmail))
- (setq x (assq 'create-directory defs))
- (when x
- (setq x (cadr x)
- x (eval x))
- (setf (nnmaildir--srv-create-dir server) x))
+ (setq x (assq 'target-prefix defs))
+ (if x
+ (progn
+ (setq x (cadr x)
+ x (eval x))
+ (setf (nnmaildir--srv-target-prefix server) x))
+ (setq x (assq 'create-directory defs))
+ (if x
+ (progn
+ (setq x (cadr x)
+ x (eval x)
+ x (file-name-as-directory x))
+ (setf (nnmaildir--srv-target-prefix server) x))
+ (setf (nnmaildir--srv-target-prefix server) "")))
(setf (nnmaildir--srv-groups server) (make-vector size 0))
(setq nnmaildir--cur-server server)
t)))
(defun nnmaildir--parse-filename (file)
(let ((prefix (car file))
timestamp len)
- (if (string-match
- "\\`\\([0-9]+\\)\\.\\([0-9]+\\)\\(_\\([0-9]+\\)\\)?\\(\\..*\\)\\'"
- prefix)
+ (if (string-match "\\`\\([0-9]+\\)\\(\\..*\\)\\'" prefix)
(progn
(setq timestamp (concat "0000" (match-string 1 prefix))
len (- (length timestamp) 4))
(vector (string-to-number (substring timestamp 0 len))
(string-to-number (substring timestamp len))
- (string-to-number (match-string 2 prefix))
- (string-to-number (or (match-string 4 prefix) "-1"))
- (match-string 5 prefix)
+ (match-string 2 prefix)
file))
file)))
(if (> (aref a 0) (aref b 0)) (throw 'return nil))
(if (< (aref a 1) (aref b 1)) (throw 'return t))
(if (> (aref a 1) (aref b 1)) (throw 'return nil))
- (if (< (aref a 2) (aref b 2)) (throw 'return t))
- (if (> (aref a 2) (aref b 2)) (throw 'return nil))
- (if (< (aref a 3) (aref b 3)) (throw 'return t))
- (if (> (aref a 3) (aref b 3)) (throw 'return nil))
- (string-lessp (aref a 4) (aref b 4))))
+ (string-lessp (aref a 2) (aref b 2))))
(defun nnmaildir--scan (gname scan-msgs groups method srv-dir srv-ls)
(catch 'return
(when (or isnew nattr)
(mapcar
(lambda (file)
- (rename-file (concat ndir file) (concat cdir file ":2,")))
+ (let ((path (concat ndir file)))
+ (and (time-less-p (nth 5 (file-attributes path)) (current-time))
+ (rename-file path (concat cdir file ":2,")))))
(funcall ls ndir nil "\\`[^.]" 'nosort))
(setf (nnmaildir--grp-new group) nattr))
(setq cattr (nth 5 (file-attributes cdir)))
files (sort files 'nnmaildir--sort-files))
(mapcar
(lambda (file)
- (setq file (if (consp file) file (aref file 5))
+ (setq file (if (consp file) file (aref file 3))
x (make-nnmaildir--art :prefix (car file) :suffix (cdr file)))
(nnmaildir--grp-add-art nnmaildir--cur-server group x))
files)
(nnmaildir-get-new-mail t)
(nnmaildir-group-alist nil)
(nnmaildir-active-file nil)
- x srv-ls srv-dir method groups group dirs grp-dir seen deactivate-mark)
+ x srv-ls srv-dir method groups target-prefix group dirs grp-dir seen
+ deactivate-mark)
(nnmaildir--prepare server nil)
(setq srv-ls (nnmaildir--srv-ls nnmaildir--cur-server)
srv-dir (nnmaildir--srv-dir nnmaildir--cur-server)
method (nnmaildir--srv-method nnmaildir--cur-server)
- groups (nnmaildir--srv-groups nnmaildir--cur-server))
+ groups (nnmaildir--srv-groups nnmaildir--cur-server)
+ target-prefix (nnmaildir--srv-target-prefix nnmaildir--cur-server))
(nnmaildir--with-work-buffer
(save-match-data
(if (stringp scan-group)
method srv-dir srv-ls))
groups))
(setq dirs (funcall srv-ls srv-dir nil "\\`[^.]" 'nosort)
+ dirs (if (zerop (length target-prefix))
+ dirs
+ (gnus-remove-if
+ (lambda (dir)
+ (and (>= (length dir) (length target-prefix))
+ (string= (substring dir 0
+ (length target-prefix))
+ target-prefix)))
+ dirs))
seen (nnmaildir--up2-1 (length dirs))
seen (make-vector seen 0))
(mapcar
(defun nnmaildir-request-update-info (gname info &optional server)
(let ((group (nnmaildir--prepare server gname))
- pgname flist all always-marks never-marks old-marks dotfile num dir
+ pgname flist always-marks never-marks old-marks dotfile num dir
markdirs marks mark ranges markdir article read end new-marks ls
- old-mmth new-mmth mtime mark-sym deactivate-mark)
+ old-mmth new-mmth mtime mark-sym existing missing deactivate-mark)
(catch 'return
(unless group
(setf (nnmaildir--srv-error nnmaildir--cur-server)
old-marks (cons old-marks (gnus-info-marks info))
always-marks (nnmaildir--param pgname 'always-marks)
never-marks (nnmaildir--param pgname 'never-marks)
+ existing (nnmaildir--grp-nlist group)
+ existing (mapcar 'car existing)
+ existing (nreverse existing)
+ existing (gnus-compress-sequence existing 'always-list)
+ missing (list (cons 1 (nnmaildir--group-maxnum
+ nnmaildir--cur-server group)))
+ missing (gnus-range-difference missing existing)
dir (nnmaildir--srv-dir nnmaildir--cur-server)
dir (nnmaildir--srvgrp-dir dir gname)
dir (nnmaildir--nndir dir)
(catch 'got-ranges
(if (memq mark-sym never-marks) (throw 'got-ranges nil))
(when (memq mark-sym always-marks)
- (unless all
- (setq all (nnmaildir--grp-nlist group)
- all (mapcar 'car all)
- all (nreverse all)
- all (gnus-compress-sequence all 'always-list)
- all (cons 'dummy-mark-symbol all)))
- (setq ranges (cdr all))
+ (setq ranges existing)
(throw 'got-ranges nil))
(setq mtime (nth 5 (file-attributes markdir)))
(set (intern mark new-mmth) mtime)
(if (eq mark-sym 'read) (setq read ranges)
(if ranges (setq marks (cons (cons mark-sym ranges) marks)))))
markdirs)
- (gnus-info-set-read info read)
+ (gnus-info-set-read info (gnus-range-add read missing))
(gnus-info-set-marks info marks 'extend)
(setf (nnmaildir--grp-mmth group) new-mmth)
info)))
(defun nnmaildir-request-group (gname &optional server fast)
(let ((group (nnmaildir--prepare server gname))
deactivate-mark)
- (nnmaildir--with-nntp-buffer
- (erase-buffer)
- (catch 'return
- (unless group
- (insert "411 no such news group\n")
- (setf (nnmaildir--srv-error nnmaildir--cur-server)
- (concat "No such group: " gname))
- (throw 'return nil))
- (setf (nnmaildir--srv-curgrp nnmaildir--cur-server) group)
- (if fast (throw 'return t))
+ (catch 'return
+ (unless group
+ ;; (insert "411 no such news group\n")
+ (setf (nnmaildir--srv-error nnmaildir--cur-server)
+ (concat "No such group: " gname))
+ (throw 'return nil))
+ (setf (nnmaildir--srv-curgrp nnmaildir--cur-server) group)
+ (if fast (throw 'return t))
+ (nnmaildir--with-nntp-buffer
+ (erase-buffer)
(insert "211 ")
(princ (nnmaildir--grp-count group) nntp-server-buffer)
(insert " ")
(defun nnmaildir-request-create-group (gname &optional server args)
(nnmaildir--prepare server nil)
(catch 'return
- (let ((create-dir (nnmaildir--srv-create-dir nnmaildir--cur-server))
+ (let ((target-prefix (nnmaildir--srv-target-prefix nnmaildir--cur-server))
srv-dir dir groups)
(when (zerop (length gname))
(setf (nnmaildir--srv-error nnmaildir--cur-server)
(concat "Group already exists: " gname))
(throw 'return nil))
(setq srv-dir (nnmaildir--srv-dir nnmaildir--cur-server))
- (if (file-name-absolute-p create-dir)
- (setq dir (expand-file-name create-dir))
+ (if (file-name-absolute-p target-prefix)
+ (setq dir (expand-file-name target-prefix))
(setq dir srv-dir
dir (file-truename dir)
- dir (concat dir create-dir)))
- (setq dir (nnmaildir--subdir (file-name-as-directory dir) gname))
+ dir (concat dir target-prefix)))
+ (setq dir (nnmaildir--subdir dir gname))
(nnmaildir--mkdir dir)
(nnmaildir--mkdir (nnmaildir--tmp dir))
(nnmaildir--mkdir (nnmaildir--new dir))
(nnmaildir--mkdir (nnmaildir--cur dir))
- (setq create-dir (file-name-as-directory create-dir))
- (make-symbolic-link (concat create-dir gname) (concat srv-dir gname))
+ (unless (string= target-prefix "")
+ (make-symbolic-link (concat target-prefix gname)
+ (concat srv-dir gname)))
(nnmaildir-request-scan 'find-new-groups))))
(defun nnmaildir-request-rename-group (gname new-name &optional server)
(defun nnmaildir-request-delete-group (gname force &optional server)
(let ((group (nnmaildir--prepare server gname))
- pgname grp-dir dir ls deactivate-mark)
+ pgname grp-dir target dir ls deactivate-mark)
(catch 'return
(unless group
(setf (nnmaildir--srv-error nnmaildir--cur-server)
(concat "No such group: " gname))
(throw 'return nil))
+ (setq gname (nnmaildir--grp-name group)
+ pgname (nnmaildir--pgname nnmaildir--cur-server gname)
+ grp-dir (nnmaildir--srv-dir nnmaildir--cur-server)
+ target (car (file-attributes (concat grp-dir gname)))
+ grp-dir (nnmaildir--srvgrp-dir grp-dir gname))
+ (unless (or force (stringp target))
+ (setf (nnmaildir--srv-error nnmaildir--cur-server)
+ (concat "Not a symlink: " gname))
+ (throw 'return nil))
(if (eq group (nnmaildir--srv-curgrp nnmaildir--cur-server))
(setf (nnmaildir--srv-curgrp nnmaildir--cur-server) nil))
- (setq gname (nnmaildir--grp-name group)
- pgname (nnmaildir--pgname nnmaildir--cur-server gname))
(unintern gname (nnmaildir--srv-groups nnmaildir--cur-server))
- (setq grp-dir (nnmaildir--srv-dir nnmaildir--cur-server)
- grp-dir (nnmaildir--srvgrp-dir grp-dir gname))
- (if (not force) (setq grp-dir (directory-file-name grp-dir))
+ (if (not force)
+ (progn
+ (setq grp-dir (directory-file-name grp-dir))
+ (nnmaildir--unlink grp-dir))
(setq ls (nnmaildir--group-ls nnmaildir--cur-server pgname))
(if (nnmaildir--param pgname 'read-only)
(progn (delete-directory (nnmaildir--tmp grp-dir))
(nnmaildir--unlink (concat dir "markfile{new}"))
(delete-directory (nnmaildir--marks-dir dir))
(delete-directory dir)
- (setq grp-dir (directory-file-name grp-dir)
- dir (car (file-attributes grp-dir)))
- (unless (eq (aref "/" 0) (aref dir 0))
- (setq dir (concat (file-truename
- (nnmaildir--srv-dir nnmaildir--cur-server))
- dir)))
- (delete-directory dir))
- (nnmaildir--unlink grp-dir)
+ (if (not (stringp target))
+ (delete-directory grp-dir)
+ (setq grp-dir (directory-file-name grp-dir)
+ dir target)
+ (unless (eq (aref "/" 0) (aref dir 0))
+ (setq dir (concat (file-truename
+ (nnmaildir--srv-dir nnmaildir--cur-server))
+ dir)))
+ (delete-directory dir)
+ (nnmaildir--unlink grp-dir)))
t)))
(defun nnmaildir-retrieve-headers (articles &optional gname server fetch-old)
(coding-system-for-write nnheader-file-coding-system)
(buffer-file-coding-system nil)
(file-coding-system-alist nil)
- srv-dir dir file tmpfile curfile 24h article)
+ srv-dir dir file time tmpfile curfile 24h article)
(catch 'return
(unless group
(setf (nnmaildir--srv-error nnmaildir--cur-server)
(throw 'return nil))
(setq srv-dir (nnmaildir--srv-dir nnmaildir--cur-server)
dir (nnmaildir--srvgrp-dir srv-dir gname)
- file (format-time-string "%s" nil))
+ time (current-time)
+ file (format-time-string "%s." time))
(unless (string-equal nnmaildir--delivery-time file)
(setq nnmaildir--delivery-time file
- nnmaildir--delivery-ct 0))
- (setq file (concat file "." nnmaildir--delivery-pid))
- (unless (zerop nnmaildir--delivery-ct)
- (setq file (concat file "_"
- (number-to-string nnmaildir--delivery-ct))))
- (setq file (concat file "." (system-name))
+ nnmaildir--delivery-count 0))
+ (when (and (consp (cdr time))
+ (consp (cddr time)))
+ (setq file (concat file "M" (number-to-string (caddr time)))))
+ (setq file (concat file nnmaildir--delivery-pid)
+ file (concat file "Q" (number-to-string nnmaildir--delivery-count))
+ file (concat file "." (nnmaildir--system-name))
tmpfile (concat (nnmaildir--tmp dir) file)
curfile (concat (nnmaildir--cur dir) file ":2,"))
(when (file-exists-p tmpfile)
(setf (nnmaildir--srv-error nnmaildir--cur-server)
(concat "File exists: " curfile))
(throw 'return nil))
- (setq nnmaildir--delivery-ct (1+ nnmaildir--delivery-ct)
+ (setq nnmaildir--delivery-count (1+ nnmaildir--delivery-count)
24h (run-with-timer 86400 nil
(lambda ()
(nnmaildir--unlink tmpfile)
(file-coding-system-alist nil)
del-mark del-action add-action set-action marksdir markfile nlist
ranges begin end article all-marks todo-marks did-marks mdir mfile
- pgname ls markfilenew deactivate-mark)
+ pgname ls permarkfile deactivate-mark)
(setq del-mark
(lambda (mark)
(setq mfile (nnmaildir--subdir marksdir (symbol-name mark))
(mapcar
(lambda (mark)
(setq mdir (nnmaildir--subdir marksdir (symbol-name mark))
+ permarkfile (concat mdir ":")
mfile (concat mdir (nnmaildir--art-prefix article)))
(unless (memq mark did-marks)
+ (setq did-marks (cons mark did-marks))
(nnmaildir--mkdir mdir)
- (setq did-marks (cons mark did-marks)))
+ (unless (file-attributes permarkfile)
+ (condition-case nil
+ (add-name-to-file markfile permarkfile)
+ (file-error
+ ;; AFS can't make hard links in separate directories
+ (write-region "" nil permarkfile nil 'no-message)))))
(unless (file-exists-p mfile)
- (condition-case nil
- (add-name-to-file markfile mfile)
- (file-error
- (unless (file-exists-p mfile)
- ;; too many links, maybe
- (write-region "" nil markfilenew nil 'no-message)
- (add-name-to-file markfilenew mfile
- 'ok-if-already-exists)
- (rename-file markfilenew markfile 'replace))))))
+ (add-name-to-file permarkfile mfile)))
todo-marks))
set-action (lambda (article)
(funcall add-action)
marksdir (nnmaildir--srvgrp-dir marksdir gname)
marksdir (nnmaildir--nndir marksdir)
markfile (concat marksdir "markfile")
- markfilenew (concat markfile "{new}")
marksdir (nnmaildir--marks-dir marksdir)
gname (nnmaildir--grp-name group)
pgname (nnmaildir--pgname nnmaildir--cur-server gname)
dir (file-name-as-directory (car dir)))
(mapcar
(lambda (file)
- (unless (intern-soft file flist)
+ (unless (or (intern-soft file flist) (string= file ":"))
(setq file (concat dir file))
(delete-file file)))
files))