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 x-time y-time)
119 (setq x-time (elmo-get-last-modification-time
120 (expand-file-name x cur-dir))
121 y-time (elmo-get-last-modification-time
122 (expand-file-name y cur-dir)))
127 (< (elmo-maildir-sequence-number x)
128 (elmo-maildir-sequence-number y)))))))
133 (elmo-maildir-adjust-separator "^\\([^:]+\\):\\([^:]+\\)$")
136 (setq sym (elmo-match-string 1 x)
137 flag-list (string-to-char-list
138 (elmo-match-string 2 x)))
139 (when (memq ?F flag-list)
140 (setq flagged-locations
141 (cons sym flagged-locations)))
142 (when (memq ?R flag-list)
143 (setq answered-locations
144 (cons sym answered-locations)))
145 (unless (memq ?S flag-list)
146 (setq unread-locations
147 (cons sym unread-locations)))
151 (list locations unread-locations flagged-locations answered-locations)))
153 (luna-define-method elmo-map-folder-list-message-locations
154 ((folder elmo-maildir-folder))
155 (elmo-maildir-update-current folder)
156 (let ((locs (elmo-maildir-list-location
157 (elmo-maildir-folder-directory-internal folder))))
158 ;; 0: locations, 1: unread-locs, 2: flagged-locs 3: answered-locs
159 (elmo-maildir-folder-set-unread-locations-internal folder (nth 1 locs))
160 (elmo-maildir-folder-set-flagged-locations-internal folder (nth 2 locs))
161 (elmo-maildir-folder-set-answered-locations-internal folder (nth 3 locs))
164 (luna-define-method elmo-map-folder-list-flagged ((folder elmo-maildir-folder)
168 (elmo-maildir-folder-unread-locations-internal folder))
170 (elmo-maildir-folder-flagged-locations-internal folder))
172 (elmo-maildir-folder-answered-locations-internal folder))
176 (luna-define-method elmo-folder-msgdb-create ((folder elmo-maildir-folder)
178 (let* ((unread-list (elmo-maildir-folder-unread-locations-internal folder))
179 (flagged-list (elmo-maildir-folder-flagged-locations-internal folder))
180 (answered-list (elmo-maildir-folder-answered-locations-internal
182 (len (length numbers))
183 (new-msgdb (elmo-make-msgdb))
185 entity message-id flags location)
186 (message "Creating msgdb...")
187 (dolist (number numbers)
188 (setq location (elmo-map-message-location folder number))
190 (elmo-msgdb-create-message-entity-from-file
191 (elmo-msgdb-message-entity-handler new-msgdb)
193 (elmo-maildir-message-file-name folder location)))
195 (setq message-id (elmo-message-entity-field entity 'message-id)
196 ;; Precede flag-table to file-info.
198 (elmo-flag-table-get flag-table message-id)))
200 ;; Already flagged on filename (precede it to flag-table).
201 (when (member location flagged-list)
202 (or (memq 'important flags)
203 (setq flags (cons 'important flags))))
204 (when (member location answered-list)
205 (or (memq 'answered flags)
206 (setq flags (cons 'answered flags))))
207 (unless (member location unread-list)
208 (and (memq 'unread flags)
209 (setq flags (delq 'unread flags))))
211 ;; Update filename's info portion according to the flag-table.
212 (when (and (memq 'important flags)
213 (not (member location flagged-list)))
214 (elmo-maildir-set-mark
215 (elmo-maildir-message-file-name folder location)
217 ;; Append to flagged location list.
218 (elmo-maildir-folder-set-flagged-locations-internal
221 (elmo-maildir-folder-flagged-locations-internal
223 (setq flags (delq 'unread flags)))
224 (when (and (memq 'answered flags)
225 (not (member location answered-list)))
226 (elmo-maildir-set-mark
227 (elmo-maildir-message-file-name folder location)
229 ;; Append to answered location list.
230 (elmo-maildir-folder-set-answered-locations-internal
233 (elmo-maildir-folder-answered-locations-internal folder)))
234 (setq flags (delq 'unread flags)))
235 (when (and (not (memq 'unread flags))
236 (member location unread-list))
237 (elmo-maildir-set-mark
238 (elmo-maildir-message-file-name folder location)
240 ;; Delete from unread locations.
241 (elmo-maildir-folder-set-unread-locations-internal
244 (elmo-maildir-folder-unread-locations-internal
246 (unless (memq 'unread flags)
247 (setq flags (delq 'new flags)))
248 (elmo-global-flags-set flags folder number message-id)
249 (elmo-msgdb-append-entity new-msgdb entity flags)
250 (when (> len elmo-display-progress-threshold)
252 (elmo-display-progress
253 'elmo-maildir-msgdb-create "Creating msgdb..."
254 (/ (* i 100) len)))))
255 (message "Creating msgdb...done")
258 (defun elmo-maildir-cleanup-temporal (dir)
259 ;; Delete files in the tmp dir which are not accessed
260 ;; for more than 36 hours.
261 (let ((cur-time (current-time))
266 (setq last-accessed (nth 4 (file-attributes file)))
267 (when (or (> (- (car cur-time)(car last-accessed)) 1)
268 (and (eq (- (car cur-time)(car last-accessed)) 1)
269 (> (- (cadr cur-time)(cadr last-accessed))
271 (message "Maildir: %d tmp file(s) are cleared."
272 (setq count (1+ count)))
273 (delete-file file))))
274 (directory-files (expand-file-name "tmp" dir)
278 (defun elmo-maildir-update-current (folder)
279 "Move all new msgs to cur in the maildir."
280 (let* ((maildir (elmo-maildir-folder-directory-internal folder))
281 (news (directory-files (expand-file-name "new"
285 ;; cleanup tmp directory.
286 (elmo-maildir-cleanup-temporal maildir)
287 ;; move new msgs to cur directory.
290 (expand-file-name (car news) (expand-file-name "new" maildir))
291 (expand-file-name (concat
293 (unless (string-match
294 (elmo-maildir-adjust-separator ":2,[A-Z]*$")
296 (elmo-maildir-adjust-separator ":2,")))
297 (expand-file-name "cur" maildir)))
298 (setq news (cdr news)))))
300 (defun elmo-maildir-set-mark (filename mark)
301 "Mark the FILENAME file in the maildir. MARK is a character."
303 (elmo-maildir-adjust-separator "^\\([^:]+:[12],\\)\\(.*\\)$")
305 (let ((flaglist (string-to-char-list (elmo-match-string
307 (unless (memq mark flaglist)
308 (setq flaglist (sort (cons mark flaglist) '<))
309 (rename-file filename
310 (concat (elmo-match-string 1 filename)
311 (char-list-to-string flaglist)))))
312 ;; Rescue no info file in maildir.
313 (rename-file filename
315 (elmo-maildir-adjust-separator ":2,")
316 (char-to-string mark))))
319 (defun elmo-maildir-delete-mark (filename mark)
320 "Mark the FILENAME file in the maildir. MARK is a character."
321 (if (string-match (elmo-maildir-adjust-separator "^\\([^:]+:2,\\)\\(.*\\)$")
323 (let ((flaglist (string-to-char-list (elmo-match-string
325 (when (memq mark flaglist)
326 (setq flaglist (delq mark flaglist))
327 (rename-file filename
328 (concat (elmo-match-string 1 filename)
330 (char-list-to-string flaglist))))))))
332 (defsubst elmo-maildir-set-mark-msgs (folder locs mark)
334 (elmo-maildir-set-mark
335 (elmo-maildir-message-file-name folder loc)
339 (defsubst elmo-maildir-delete-mark-msgs (folder locs mark)
341 (elmo-maildir-delete-mark
342 (elmo-maildir-message-file-name folder loc)
346 (defsubst elmo-maildir-set-mark-messages (folder locations mark remove)
349 (elmo-maildir-delete-mark-msgs folder locations mark)
350 (elmo-maildir-set-mark-msgs folder locations mark))))
352 (luna-define-method elmo-map-folder-set-flag ((folder elmo-maildir-folder)
354 (let ((spec (cdr (assq flag elmo-maildir-flag-specs))))
356 (elmo-maildir-set-mark-messages folder locations
357 (car spec) (nth 1 spec)))))
359 (luna-define-method elmo-map-folder-unset-flag ((folder elmo-maildir-folder)
361 (let ((spec (cdr (assq flag elmo-maildir-flag-specs))))
363 (elmo-maildir-set-mark-messages folder locations
364 (car spec) (not (nth 1 spec))))))
366 (luna-define-method elmo-folder-list-subfolders
367 ((folder elmo-maildir-folder) &optional one-level)
368 (let ((prefix (concat (elmo-folder-name-internal folder)
369 (unless (string= (elmo-folder-prefix-internal folder)
370 (elmo-folder-name-internal folder))
372 (elmo-list-subdirectories-ignore-regexp
373 "^\\(\\.\\.?\\|cur\\|tmp\\|new\\)$")
374 elmo-have-link-count)
376 (list (elmo-folder-name-internal folder))
377 (elmo-mapcar-list-of-list
378 (function (lambda (x) (concat prefix x)))
379 (elmo-list-subdirectories
380 (elmo-maildir-folder-directory-internal folder)
384 (defvar elmo-maildir-sequence-number-internal 0)
386 (defun elmo-maildir-sequence-number (file)
387 "Get `elmo-maildir' specific sequence number from FILE.
388 Not that FILE is the name without directory."
389 ;; elmo-maildir specific.
390 (if (string-match "^.*_\\([0-9]+\\)\\..*" file)
391 (string-to-number (match-string 1 file))
394 (defun elmo-maildir-make-unique-string ()
395 "This function generates a string that can be used as a unique
396 file name for maildir directories."
397 (let ((cur-time (current-time)))
398 (format "%.0f.%d_%d.%s"
400 (float 65536)) (cadr cur-time))
402 (incf elmo-maildir-sequence-number-internal)
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 number)
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