1 ;;; elmo-maildir.el --- Maildir interface for ELMO.
3 ;; Copyright (C) 1998,1999,2000 Yuuichi Teranishi <teranisi@gohome.org>
5 ;; Author: Yuuichi Teranishi <teranisi@gohome.org>
6 ;; Keywords: mail, net news
8 ;; This file is part of ELMO (Elisp Library for Message Orchestration).
10 ;; This program is free software; you can redistribute it and/or modify
11 ;; it under the terms of the GNU General Public License as published by
12 ;; the Free Software Foundation; either version 2, or (at your option)
15 ;; This program is distributed in the hope that it will be useful,
16 ;; but WITHOUT ANY WARRANTY; without even the implied warranty of
17 ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18 ;; GNU General Public License for more details.
20 ;; You should have received a copy of the GNU General Public License
21 ;; along with GNU Emacs; see the file COPYING. If not, write to the
22 ;; Free Software Foundation, Inc., 59 Temple Place - Suite 330,
23 ;; Boston, MA 02111-1307, USA.
32 (eval-when-compile (require 'cl))
38 (defcustom elmo-maildir-folder-path "~/Maildir"
39 "*Maildir folder path."
43 (defconst elmo-maildir-flag-specs '((important ?F)
48 (defcustom elmo-maildir-separator
50 '(windows-nt OS/2 emx ms-dos win32 w32 mswindows cygwin))
52 "Character separating the id section from the flags section.
53 According to the maildir specification, this should be a colon (?:),
54 but some file systems don't support colons in filenames."
58 (defmacro elmo-maildir-adjust-separator (string)
59 `(if (= elmo-maildir-separator ?:)
61 (elmo-replace-in-string
62 ,string ":" (char-to-string elmo-maildir-separator))))
64 ;;; ELMO Maildir folder
66 (luna-define-class elmo-maildir-folder
68 (directory unread-locations
71 (luna-define-internal-accessors 'elmo-maildir-folder))
73 (luna-define-method elmo-folder-initialize ((folder
76 (if (file-name-absolute-p name)
77 (elmo-maildir-folder-set-directory-internal
79 (expand-file-name name))
80 (elmo-maildir-folder-set-directory-internal
84 elmo-maildir-folder-path)))
87 (luna-define-method elmo-folder-expand-msgdb-path ((folder
90 (elmo-replace-string-as-filename
91 (elmo-maildir-folder-directory-internal folder))
94 elmo-msgdb-directory)))
96 (defun elmo-maildir-message-file-name (folder location)
97 "Get a file name of the message from FOLDER which corresponded to
99 (let ((file (file-name-completion
103 (elmo-maildir-folder-directory-internal folder)))))
106 (if (eq file t) location file)
109 (elmo-maildir-folder-directory-internal folder))))))
111 (defsubst elmo-maildir-list-location (dir &optional child-dir)
112 (let* ((cur-dir (expand-file-name (or child-dir "cur") dir))
113 (cur (directory-files cur-dir
115 unread-locations flagged-locations answered-locations
116 sym locations flag-list)
121 (elmo-maildir-adjust-separator "^\\([^:]+\\):\\([^:]+\\)$")
124 (setq sym (elmo-match-string 1 x)
125 flag-list (string-to-char-list
126 (elmo-match-string 2 x)))
127 (when (memq ?F flag-list)
128 (setq flagged-locations
129 (cons sym flagged-locations)))
130 (when (memq ?R flag-list)
131 (setq answered-locations
132 (cons sym answered-locations)))
133 (unless (memq ?S flag-list)
134 (setq unread-locations
135 (cons sym unread-locations)))
139 (list locations unread-locations flagged-locations answered-locations)))
141 (luna-define-method elmo-map-folder-list-message-locations
142 ((folder elmo-maildir-folder))
143 (elmo-maildir-update-current folder)
144 (let ((locs (elmo-maildir-list-location
145 (elmo-maildir-folder-directory-internal folder))))
146 ;; 0: locations, 1: unread-locs, 2: flagged-locs 3: answered-locs
147 (elmo-maildir-folder-set-unread-locations-internal folder (nth 1 locs))
148 (elmo-maildir-folder-set-flagged-locations-internal folder (nth 2 locs))
149 (elmo-maildir-folder-set-answered-locations-internal folder (nth 3 locs))
152 (luna-define-method elmo-map-folder-list-flagged ((folder elmo-maildir-folder)
156 (elmo-maildir-folder-unread-locations-internal folder))
158 (elmo-maildir-folder-flagged-locations-internal folder))
160 (elmo-maildir-folder-answered-locations-internal folder))
164 (luna-define-method elmo-folder-msgdb-create ((folder elmo-maildir-folder)
166 (let* ((unread-list (elmo-maildir-folder-unread-locations-internal folder))
167 (flagged-list (elmo-maildir-folder-flagged-locations-internal folder))
168 (answered-list (elmo-maildir-folder-answered-locations-internal
170 (len (length numbers))
171 (new-msgdb (elmo-make-msgdb))
173 entity message-id flags location)
174 (message "Creating msgdb...")
175 (dolist (number numbers)
176 (setq location (elmo-map-message-location folder number))
178 (elmo-msgdb-create-message-entity-from-file
179 (elmo-msgdb-message-entity-handler new-msgdb)
181 (elmo-maildir-message-file-name folder location)))
183 (setq message-id (elmo-message-entity-field entity 'message-id)
184 ;; Precede flag-table to file-info.
186 (elmo-flag-table-get flag-table message-id)))
188 ;; Already flagged on filename (precede it to flag-table).
189 (when (member location flagged-list)
190 (or (memq 'important flags)
191 (setq flags (cons 'important flags))))
192 (when (member location answered-list)
193 (or (memq 'answered flags)
194 (setq flags (cons 'answered flags))))
195 (unless (member location unread-list)
196 (and (memq 'unread flags)
197 (setq flags (delq 'unread flags))))
199 ;; Update filename's info portion according to the flag-table.
200 (when (and (memq 'important flags)
201 (not (member location flagged-list)))
202 (elmo-maildir-set-mark
203 (elmo-maildir-message-file-name folder location)
205 ;; Append to flagged location list.
206 (elmo-maildir-folder-set-flagged-locations-internal
209 (elmo-maildir-folder-flagged-locations-internal
211 (setq flags (delq 'unread flags)))
212 (when (and (memq 'answered flags)
213 (not (member location answered-list)))
214 (elmo-maildir-set-mark
215 (elmo-maildir-message-file-name folder location)
217 ;; Append to answered location list.
218 (elmo-maildir-folder-set-answered-locations-internal
221 (elmo-maildir-folder-answered-locations-internal folder)))
222 (setq flags (delq 'unread flags)))
223 (when (and (not (memq 'unread flags))
224 (member location unread-list))
225 (elmo-maildir-set-mark
226 (elmo-maildir-message-file-name folder location)
228 ;; Delete from unread locations.
229 (elmo-maildir-folder-set-unread-locations-internal
232 (elmo-maildir-folder-unread-locations-internal
234 (unless (memq 'unread flags)
235 (setq flags (delq 'new flags)))
236 (elmo-global-flags-set flags folder number message-id)
237 (elmo-msgdb-append-entity new-msgdb entity flags)
238 (when (> len elmo-display-progress-threshold)
240 (elmo-display-progress
241 'elmo-maildir-msgdb-create "Creating msgdb..."
242 (/ (* i 100) len)))))
243 (message "Creating msgdb...done")
244 (elmo-msgdb-sort-by-date new-msgdb)))
246 (defun elmo-maildir-cleanup-temporal (dir)
247 ;; Delete files in the tmp dir which are not accessed
248 ;; for more than 36 hours.
249 (let ((cur-time (current-time))
254 (setq last-accessed (nth 4 (file-attributes file)))
255 (when (or (> (- (car cur-time)(car last-accessed)) 1)
256 (and (eq (- (car cur-time)(car last-accessed)) 1)
257 (> (- (cadr cur-time)(cadr last-accessed))
259 (message "Maildir: %d tmp file(s) are cleared."
260 (setq count (1+ count)))
261 (delete-file file))))
262 (directory-files (expand-file-name "tmp" dir)
266 (defun elmo-maildir-update-current (folder)
267 "Move all new msgs to cur in the maildir."
268 (let* ((maildir (elmo-maildir-folder-directory-internal folder))
269 (news (directory-files (expand-file-name "new"
273 ;; cleanup tmp directory.
274 (elmo-maildir-cleanup-temporal maildir)
275 ;; move new msgs to cur directory.
278 (expand-file-name (car news) (expand-file-name "new" maildir))
279 (expand-file-name (concat
281 (unless (string-match
282 (elmo-maildir-adjust-separator ":2,[A-Z]*$")
284 (elmo-maildir-adjust-separator ":2,")))
285 (expand-file-name "cur" maildir)))
286 (setq news (cdr news)))))
288 (defun elmo-maildir-set-mark (filename mark)
289 "Mark the FILENAME file in the maildir. MARK is a character."
291 (elmo-maildir-adjust-separator "^\\([^:]+:[12],\\)\\(.*\\)$")
293 (let ((flaglist (string-to-char-list (elmo-match-string
295 (unless (memq mark flaglist)
296 (setq flaglist (sort (cons mark flaglist) '<))
297 (rename-file filename
298 (concat (elmo-match-string 1 filename)
299 (char-list-to-string flaglist)))))
300 ;; Rescue no info file in maildir.
301 (rename-file filename
303 (elmo-maildir-adjust-separator ":2,")
304 (char-to-string mark))))
307 (defun elmo-maildir-delete-mark (filename mark)
308 "Mark the FILENAME file in the maildir. MARK is a character."
309 (if (string-match (elmo-maildir-adjust-separator "^\\([^:]+:2,\\)\\(.*\\)$")
311 (let ((flaglist (string-to-char-list (elmo-match-string
313 (when (memq mark flaglist)
314 (setq flaglist (delq mark flaglist))
315 (rename-file filename
316 (concat (elmo-match-string 1 filename)
318 (char-list-to-string flaglist))))))))
320 (defsubst elmo-maildir-set-mark-msgs (folder locs mark)
322 (elmo-maildir-set-mark
323 (elmo-maildir-message-file-name folder loc)
327 (defsubst elmo-maildir-delete-mark-msgs (folder locs mark)
329 (elmo-maildir-delete-mark
330 (elmo-maildir-message-file-name folder loc)
334 (defsubst elmo-maildir-set-mark-messages (folder locations mark remove)
337 (elmo-maildir-delete-mark-msgs folder locations mark)
338 (elmo-maildir-set-mark-msgs folder locations mark))))
340 (luna-define-method elmo-map-folder-set-flag ((folder elmo-maildir-folder)
342 (let ((spec (cdr (assq flag elmo-maildir-flag-specs))))
344 (elmo-maildir-set-mark-messages folder locations
345 (car spec) (nth 1 spec)))))
347 (luna-define-method elmo-map-folder-unset-flag ((folder elmo-maildir-folder)
349 (let ((spec (cdr (assq flag elmo-maildir-flag-specs))))
351 (elmo-maildir-set-mark-messages folder locations
352 (car spec) (not (nth 1 spec))))))
354 (luna-define-method elmo-folder-list-subfolders
355 ((folder elmo-maildir-folder) &optional one-level)
356 (let ((prefix (concat (elmo-folder-name-internal folder)
357 (unless (string= (elmo-folder-prefix-internal folder)
358 (elmo-folder-name-internal folder))
360 (elmo-list-subdirectories-ignore-regexp
361 "^\\(\\.\\.?\\|cur\\|tmp\\|new\\)$")
362 elmo-have-link-count)
364 (list (elmo-folder-name-internal folder))
365 (elmo-mapcar-list-of-list
366 (function (lambda (x) (concat prefix x)))
367 (elmo-list-subdirectories
368 (elmo-maildir-folder-directory-internal folder)
372 (defvar elmo-maildir-sequence-number-internal 0)
375 ((>= emacs-major-version 19)
376 (defun elmo-maildir-make-unique-string ()
377 "This function generates a string that can be used as a unique
378 file name for maildir directories."
379 (let ((cur-time (current-time)))
380 (format "%.0f.%d_%d.%s"
382 (float 65536)) (cadr cur-time))
384 (incf elmo-maildir-sequence-number-internal)
386 ((eq emacs-major-version 18)
387 ;; A fake function for v18
388 (defun elmo-maildir-make-unique-string ()
389 "This function generates a string that can be used as a unique
390 file name for maildir directories."
391 (unless (fboundp 'float-to-string)
392 (load-library "float"))
393 (let ((time (current-time)))
397 (f+ (f* (f (car time))
402 (% (abs (random t)) 10000); dummy pid
405 (defun elmo-maildir-temporal-filename (basedir)
406 (let ((filename (expand-file-name
407 (concat "tmp/" (elmo-maildir-make-unique-string))
409 (unless (file-exists-p (file-name-directory filename))
410 (make-directory (file-name-directory filename)))
411 (while (file-exists-p filename)
412 ;;; I don't want to wait.
416 (concat "tmp/" (elmo-maildir-make-unique-string))
420 (defun elmo-maildir-move-file (src dst)
421 (or (condition-case nil
423 ;; 1. Try add-link-to-file, then delete the original.
424 ;; This is safe on NFS.
425 (add-name-to-file src dst)
427 ;; It's ok if the delete-file fails;
428 ;; elmo-maildir-cleanup-temporal will catch it later.
432 ;; 2. Even on systems with hardlinks, some filesystems (like AFS)
433 ;; might not support them, so fall back on rename-file. This is
434 ;; our best shot at atomic when add-name-to-file fails.
435 (rename-file src dst)))
437 (luna-define-method elmo-folder-append-buffer ((folder elmo-maildir-folder)
438 &optional flags number)
439 (let ((basedir (elmo-maildir-folder-directory-internal folder))
440 (src-buf (current-buffer))
444 (setq filename (elmo-maildir-temporal-filename basedir))
445 (setq dst-buf (current-buffer))
446 (with-current-buffer src-buf
447 (copy-to-buffer dst-buf (point-min) (point-max)))
448 (as-binary-output-file
449 (write-region (point-min) (point-max) filename nil 'no-msg))
450 (elmo-maildir-move-file
453 (concat "new/" (file-name-nondirectory filename))
455 (elmo-folder-preserve-flags
456 folder (elmo-msgdb-get-message-id-from-buffer) flags)
458 ;; If an error occured, return nil.
461 (luna-define-method elmo-folder-message-file-p ((folder elmo-maildir-folder))
464 (luna-define-method elmo-message-file-name ((folder elmo-maildir-folder)
466 (elmo-maildir-message-file-name
468 (elmo-map-message-location folder number)))
470 (luna-define-method elmo-folder-message-make-temp-file-p
471 ((folder elmo-maildir-folder))
474 (luna-define-method elmo-folder-message-make-temp-files ((folder
479 (let ((temp-dir (elmo-folder-make-temporary-directory folder))
480 (cur-number (if start-number 0)))
481 (dolist (number numbers)
483 (elmo-message-file-name folder number)
485 (int-to-string (if start-number (incf cur-number) number))
489 (luna-define-method elmo-folder-append-messages :around
490 ((folder elmo-maildir-folder)
491 src-folder numbers &optional same-number)
492 (if (elmo-folder-message-file-p src-folder)
493 (let ((src-msgdb-exists (not (zerop (elmo-folder-length src-folder))))
494 (dir (elmo-maildir-folder-directory-internal folder))
495 (table (elmo-folder-flag-table folder))
498 (dolist (number numbers)
499 (setq flags (elmo-message-flags src-folder (car numbers))
500 filename (elmo-maildir-temporal-filename dir))
502 (elmo-message-file-name src-folder number)
504 (elmo-maildir-move-file
507 (concat "new/" (file-name-nondirectory filename))
509 ;; src folder's msgdb is loaded.
510 (when (setq id (and src-msgdb-exists
511 (elmo-message-field src-folder number
513 (elmo-flag-table-set table id flags))
514 (elmo-progress-notify 'elmo-folder-move-messages))
515 (when (elmo-folder-persistent-p folder)
516 (elmo-folder-close-flag-table folder))
518 (luna-call-next-method)))
520 (luna-define-method elmo-map-folder-delete-messages
521 ((folder elmo-maildir-folder) locations)
523 (dolist (location locations)
524 (setq file (elmo-maildir-message-file-name folder location))
526 (file-writable-p file)
527 (not (file-directory-p file)))
528 (delete-file file))))
531 (luna-define-method elmo-map-message-fetch ((folder elmo-maildir-folder)
533 &optional section unseen)
534 (let ((file (elmo-maildir-message-file-name folder location)))
535 (when (file-exists-p file)
536 (insert-file-contents-as-binary file)
538 (elmo-map-folder-set-flag folder (list location) 'read))
541 (luna-define-method elmo-folder-exists-p ((folder elmo-maildir-folder))
542 (let ((basedir (elmo-maildir-folder-directory-internal folder)))
543 (and (file-directory-p (expand-file-name "new" basedir))
544 (file-directory-p (expand-file-name "cur" basedir))
545 (file-directory-p (expand-file-name "tmp" basedir)))))
547 (luna-define-method elmo-folder-diff ((folder elmo-maildir-folder))
548 (let* ((dir (elmo-maildir-folder-directory-internal folder))
549 (new-len (length (car (elmo-maildir-list-location dir "new"))))
550 (cur-len (length (car (elmo-maildir-list-location dir "cur")))))
551 (cons new-len (+ new-len cur-len))))
553 (luna-define-method elmo-folder-creatable-p ((folder elmo-maildir-folder))
556 (luna-define-method elmo-folder-writable-p ((folder elmo-maildir-folder))
559 (luna-define-method elmo-folder-create ((folder elmo-maildir-folder))
560 (let ((basedir (elmo-maildir-folder-directory-internal folder)))
563 (dolist (dir '("." "new" "cur" "tmp"))
564 (setq dir (expand-file-name dir basedir))
565 (or (file-directory-p dir)
567 (elmo-make-directory dir)
568 (set-file-modes dir 448))))
572 (luna-define-method elmo-folder-delete ((folder elmo-maildir-folder))
573 (let ((msgs (and (elmo-folder-exists-p folder)
574 (elmo-folder-list-messages folder))))
575 (when (yes-or-no-p (format "%sDelete msgdb and substance of \"%s\"? "
576 (if (> (length msgs) 0)
577 (format "%d msg(s) exists. " (length msgs))
579 (elmo-folder-name-internal folder)))
580 (let ((basedir (elmo-maildir-folder-directory-internal folder)))
582 (let ((tmp-files (directory-files
583 (expand-file-name "tmp" basedir)
585 ;; Delete files in tmp.
586 (dolist (file tmp-files)
588 (dolist (dir '("new" "cur" "tmp" "."))
589 (setq dir (expand-file-name dir basedir))
590 (if (not (file-directory-p dir))
592 (elmo-delete-directory dir t))))
594 (elmo-msgdb-delete-path folder)
597 (luna-define-method elmo-folder-rename-internal ((folder elmo-maildir-folder)
599 (let* ((old (elmo-maildir-folder-directory-internal folder))
600 (new (elmo-maildir-folder-directory-internal new-folder))
601 (new-dir (directory-file-name (file-name-directory new))))
602 (unless (file-directory-p old)
603 (error "No such directory: %s" old))
604 (when (file-exists-p new)
605 (error "Already exists directory: %s" new))
606 (unless (file-directory-p new-dir)
607 (elmo-make-directory new-dir))
608 (rename-file old new)
612 (product-provide (provide 'elmo-maildir) (require 'elmo-version))
614 ;;; elmo-maildir.el ends here