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 ;;; ELMO Maildir folder
45 (luna-define-class elmo-maildir-folder
47 (directory unread-locations
50 (luna-define-internal-accessors 'elmo-maildir-folder))
52 (luna-define-method elmo-folder-initialize ((folder
55 (if (file-name-absolute-p name)
56 (elmo-maildir-folder-set-directory-internal
58 (expand-file-name name))
59 (elmo-maildir-folder-set-directory-internal
63 elmo-maildir-folder-path)))
66 (luna-define-method elmo-folder-expand-msgdb-path ((folder
69 (elmo-replace-string-as-filename
70 (elmo-maildir-folder-directory-internal folder))
73 elmo-msgdb-directory)))
75 (defun elmo-maildir-message-file-name (folder location)
76 "Get a file name of the message from FOLDER which corresponded to
78 (let ((file (file-name-completion
82 (elmo-maildir-folder-directory-internal folder)))))
85 (if (eq file t) location file)
88 (elmo-maildir-folder-directory-internal folder))))))
90 (defsubst elmo-maildir-list-location (dir &optional child-dir)
91 (let* ((cur-dir (expand-file-name (or child-dir "cur") dir))
92 (cur (directory-files cur-dir
94 unread-locations flagged-locations answered-locations
95 seen flagged answered sym locations)
99 (if (string-match "^\\([^:]+\\):\\([^:]+\\)$" x)
101 (setq seen nil answered nil flagged nil)
104 ((string-match "F" (elmo-match-string 2 x))
106 ((string-match "R" (elmo-match-string 2 x))
108 ((string-match "S" (elmo-match-string 2 x))
110 (setq sym (elmo-match-string 1 x))
112 (flagged (setq flagged-locations
113 (cons sym flagged-locations)))
114 (answered (setq answered-locations
115 (cons sym answered-locations)))
118 (setq unread-locations (cons sym unread-locations))))
122 (list locations unread-locations flagged-locations answered-locations)))
124 (luna-define-method elmo-map-folder-list-message-locations
125 ((folder elmo-maildir-folder))
126 (elmo-maildir-update-current folder)
127 (let ((locs (elmo-maildir-list-location
128 (elmo-maildir-folder-directory-internal folder))))
129 ;; 0: locations, 1: unread-locs, 2: flagged-locs 3: answered-locs
130 (elmo-maildir-folder-set-unread-locations-internal folder (nth 1 locs))
131 (elmo-maildir-folder-set-flagged-locations-internal folder (nth 2 locs))
132 (elmo-maildir-folder-set-answered-locations-internal folder (nth 3 locs))
135 (luna-define-method elmo-map-folder-list-unreads
136 ((folder elmo-maildir-folder))
137 (elmo-maildir-folder-unread-locations-internal folder))
139 (luna-define-method elmo-map-folder-list-importants
140 ((folder elmo-maildir-folder))
141 (elmo-maildir-folder-flagged-locations-internal folder))
143 (luna-define-method elmo-map-folder-list-answereds
144 ((folder elmo-maildir-folder))
145 (elmo-maildir-folder-answered-locations-internal folder))
147 (luna-define-method elmo-folder-msgdb-create
148 ((folder elmo-maildir-folder) numbers flag-table)
149 (let* ((unread-list (elmo-maildir-folder-unread-locations-internal folder))
150 (flagged-list (elmo-maildir-folder-flagged-locations-internal folder))
151 (answered-list (elmo-maildir-folder-answered-locations-internal
153 (len (length numbers))
155 overview number-alist mark-alist entity message-id flag
156 file location pair mark cache-status file-flag)
157 (message "Creating msgdb...")
158 (dolist (number numbers)
159 (setq location (elmo-map-message-location folder number))
161 (elmo-msgdb-create-overview-entity-from-file
164 (elmo-maildir-message-file-name folder location))))
167 (elmo-msgdb-append-element overview entity)
169 (elmo-msgdb-number-add number-alist
170 (elmo-message-entity-number entity)
172 (elmo-message-entity-field
173 entity 'message-id)))
174 ;; Precede flag-table to file-info.
175 flag (elmo-flag-table-get flag-table message-id)
179 (elmo-file-cache-status (elmo-file-cache-get message-id)))
181 ;; Already flagged on filename (precede it to flag-table).
183 ((member location flagged-list)
184 (setq file-flag 'important
185 mark elmo-msgdb-important-mark))
186 ((member location answered-list)
187 (setq file-flag 'answered
188 mark (elmo-msgdb-mark 'answered cache-status)))
189 ((member location unread-list)
190 (setq file-flag 'unread
191 mark (elmo-msgdb-mark 'unread cache-status)))
192 (t (setq file-flag 'read)))
194 ;; Set mark according to flag-table if file status is unread or read.
195 (when (or (eq file-flag 'read)
196 (eq file-flag 'unread))
198 (unless (eq 'read flag)
199 (setq mark (elmo-msgdb-mark flag cache-status 'new)))
200 ;; Update filename's info portion according to the flag-table.
202 ((and (or (eq flag 'important)
203 (setq mark (elmo-msgdb-global-mark-get
204 (elmo-message-entity-field
205 entity 'message-id))))
206 (not (eq file-flag 'important)))
207 (elmo-maildir-set-mark file ?F)
208 ;; Delete from unread location list.
209 (elmo-maildir-folder-set-unread-locations-internal
212 (elmo-maildir-folder-unread-locations-internal
214 ;; Append to flagged location list.
215 (elmo-maildir-folder-set-flagged-locations-internal
218 (elmo-maildir-folder-flagged-locations-internal
220 ((and (eq flag 'answered)
221 (not (eq file-flag 'answered)))
222 (elmo-maildir-set-mark file ?R)
223 ;; Delete from unread locations.
224 (elmo-maildir-folder-set-unread-locations-internal
227 (elmo-maildir-folder-unread-locations-internal folder)))
228 ;; Append to answered location list.
229 (elmo-maildir-folder-set-answered-locations-internal
232 (elmo-maildir-folder-answered-locations-internal folder))))
233 ((and (eq flag 'read)
234 (not (eq file-flag 'read)))
235 (elmo-maildir-set-mark file ?S)
236 ;; Delete from unread locations.
237 (elmo-maildir-folder-set-unread-locations-internal
240 (elmo-maildir-folder-unread-locations-internal
244 (elmo-msgdb-mark-append
246 (elmo-msgdb-overview-entity-get-number
249 (when (> len elmo-display-progress-threshold)
251 (elmo-display-progress
252 'elmo-maildir-msgdb-create "Creating msgdb..."
253 (/ (* i 100) len)))))
254 (message "Creating msgdb...done")
255 (elmo-msgdb-sort-by-date
256 (list overview number-alist mark-alist))))
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 ":2,[A-Z]*$" (car news))
295 (expand-file-name "cur" maildir)))
296 (setq news (cdr news)))))
298 (defun elmo-maildir-set-mark (filename mark)
299 "Mark the FILENAME file in the maildir. MARK is a character."
300 (if (string-match "^\\([^:]+:[12],\\)\\(.*\\)$" filename)
301 (let ((flaglist (string-to-char-list (elmo-match-string
303 (unless (memq mark flaglist)
304 (setq flaglist (sort (cons mark flaglist) '<))
305 (rename-file filename
306 (concat (elmo-match-string 1 filename)
307 (char-list-to-string flaglist)))))
308 ;; Rescue no info file in maildir.
309 (rename-file filename
310 (concat filename ":2," (char-to-string mark))))
313 (defun elmo-maildir-delete-mark (filename mark)
314 "Mark the FILENAME file in the maildir. MARK is a character."
315 (if (string-match "^\\([^:]+:2,\\)\\(.*\\)$" filename)
316 (let ((flaglist (string-to-char-list (elmo-match-string
318 (when (memq mark flaglist)
319 (setq flaglist (delq mark flaglist))
320 (rename-file filename
321 (concat (elmo-match-string 1 filename)
323 (char-list-to-string flaglist))))))))
325 (defsubst elmo-maildir-set-mark-msgs (folder locs mark)
327 (elmo-maildir-set-mark
328 (elmo-maildir-message-file-name folder loc)
332 (defsubst elmo-maildir-delete-mark-msgs (folder locs mark)
334 (elmo-maildir-delete-mark
335 (elmo-maildir-message-file-name folder loc)
339 (luna-define-method elmo-map-folder-mark-as-important ((folder elmo-maildir-folder)
341 (elmo-maildir-set-mark-msgs folder locs ?F))
343 (luna-define-method elmo-map-folder-unmark-important ((folder elmo-maildir-folder)
345 (elmo-maildir-delete-mark-msgs folder locs ?F))
347 (luna-define-method elmo-map-folder-mark-as-read ((folder elmo-maildir-folder)
349 (elmo-maildir-set-mark-msgs folder locs ?S))
351 (luna-define-method elmo-map-folder-unmark-read ((folder elmo-maildir-folder)
353 (elmo-maildir-delete-mark-msgs folder locs ?S))
355 (luna-define-method elmo-map-folder-mark-as-answered ((folder
358 (elmo-maildir-set-mark-msgs folder locs ?R))
360 (luna-define-method elmo-map-folder-unmark-answered ((folder
363 (elmo-maildir-delete-mark-msgs folder locs ?R))
365 (luna-define-method elmo-folder-list-subfolders
366 ((folder elmo-maildir-folder) &optional one-level)
367 (let ((prefix (concat (elmo-folder-name-internal folder)
368 (unless (string= (elmo-folder-prefix-internal folder)
369 (elmo-folder-name-internal folder))
371 (elmo-list-subdirectories-ignore-regexp
372 "^\\(\\.\\.?\\|cur\\|tmp\\|new\\)$")
373 elmo-have-link-count)
375 (list (elmo-folder-name-internal folder))
376 (elmo-mapcar-list-of-list
377 (function (lambda (x) (concat prefix x)))
378 (elmo-list-subdirectories
379 (elmo-maildir-folder-directory-internal folder)
383 (defvar elmo-maildir-sequence-number-internal 0)
386 ((>= emacs-major-version 19)
387 (defun elmo-maildir-make-unique-string ()
388 "This function generates a string that can be used as a unique
389 file name for maildir directories."
390 (let ((cur-time (current-time)))
391 (format "%.0f.%d_%d.%s"
393 (float 65536)) (cadr cur-time))
395 (incf elmo-maildir-sequence-number-internal)
397 ((eq emacs-major-version 18)
398 ;; A fake function for v18
399 (defun elmo-maildir-make-unique-string ()
400 "This function generates a string that can be used as a unique
401 file name for maildir directories."
402 (unless (fboundp 'float-to-string)
403 (load-library "float"))
404 (let ((time (current-time)))
408 (f+ (f* (f (car time))
413 (% (abs (random t)) 10000); dummy pid
416 (defun elmo-maildir-temporal-filename (basedir)
417 (let ((filename (expand-file-name
418 (concat "tmp/" (elmo-maildir-make-unique-string))
420 (unless (file-exists-p (file-name-directory filename))
421 (make-directory (file-name-directory filename)))
422 (while (file-exists-p filename)
423 ;;; I don't want to wait.
427 (concat "tmp/" (elmo-maildir-make-unique-string))
431 (luna-define-method elmo-folder-append-buffer ((folder elmo-maildir-folder)
432 &optional status number)
433 (let ((basedir (elmo-maildir-folder-directory-internal folder))
434 (src-buf (current-buffer))
438 (setq filename (elmo-maildir-temporal-filename basedir))
439 (setq dst-buf (current-buffer))
440 (with-current-buffer src-buf
441 (copy-to-buffer dst-buf (point-min) (point-max)))
442 (as-binary-output-file
443 (write-region (point-min) (point-max) filename nil 'no-msg))
444 ;; add link from new.
445 (elmo-add-name-to-file
448 (concat "new/" (file-name-nondirectory filename))
451 ;; If an error occured, return nil.
454 (luna-define-method elmo-folder-message-file-p ((folder elmo-maildir-folder))
457 (luna-define-method elmo-message-file-name ((folder elmo-maildir-folder)
459 (elmo-maildir-message-file-name
461 (elmo-map-message-location folder number)))
463 (luna-define-method elmo-folder-message-make-temp-file-p
464 ((folder elmo-maildir-folder))
467 (luna-define-method elmo-folder-message-make-temp-files ((folder
472 (let ((temp-dir (elmo-folder-make-temporary-directory folder))
473 (cur-number (if start-number 0)))
474 (dolist (number numbers)
476 (elmo-message-file-name folder number)
478 (int-to-string (if start-number (incf cur-number) number))
482 (luna-define-method elmo-folder-append-messages :around
483 ((folder elmo-maildir-folder)
484 src-folder numbers &optional same-number)
485 (if (elmo-folder-message-file-p src-folder)
486 (let ((src-msgdb-exists (not (zerop (elmo-folder-length src-folder))))
487 (dir (elmo-maildir-folder-directory-internal folder))
488 (table (elmo-flag-table-load (elmo-folder-msgdb-path folder)))
490 filename mark flag id)
491 (dolist (number numbers)
492 (setq mark (and src-msgdb-exists
493 (elmo-message-mark src-folder (car numbers)))
496 ((member mark (elmo-msgdb-answered-marks))
498 ((not (member mark (elmo-msgdb-unread-marks)))
500 filename (elmo-maildir-temporal-filename dir))
502 (elmo-message-file-name src-folder number)
504 (elmo-add-name-to-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 (car numbers)
513 (elmo-flag-table-set table id flag))
514 (elmo-progress-notify 'elmo-folder-move-messages))
515 (when (elmo-folder-persistent-p folder)
516 (elmo-flag-table-save (elmo-folder-msgdb-path folder) table))
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)))))
530 (luna-define-method elmo-map-message-fetch ((folder elmo-maildir-folder)
532 &optional section unseen)
533 (let ((file (elmo-maildir-message-file-name folder location)))
534 (when (file-exists-p file)
535 (insert-file-contents-as-binary file))))
537 (luna-define-method elmo-folder-exists-p ((folder elmo-maildir-folder))
538 (let ((basedir (elmo-maildir-folder-directory-internal folder)))
539 (and (file-directory-p (expand-file-name "new" basedir))
540 (file-directory-p (expand-file-name "cur" basedir))
541 (file-directory-p (expand-file-name "tmp" basedir)))))
543 (luna-define-method elmo-folder-diff ((folder elmo-maildir-folder))
544 (let* ((dir (elmo-maildir-folder-directory-internal folder))
545 (new-len (length (car (elmo-maildir-list-location dir "new"))))
546 (cur-len (length (car (elmo-maildir-list-location dir "cur")))))
547 (cons new-len (+ new-len cur-len))))
549 (luna-define-method elmo-folder-creatable-p ((folder elmo-maildir-folder))
552 (luna-define-method elmo-folder-writable-p ((folder elmo-maildir-folder))
555 (luna-define-method elmo-folder-create ((folder elmo-maildir-folder))
556 (let ((basedir (elmo-maildir-folder-directory-internal folder)))
559 (dolist (dir '("." "new" "cur" "tmp"))
560 (setq dir (expand-file-name dir basedir))
561 (or (file-directory-p dir)
563 (elmo-make-directory dir)
564 (set-file-modes dir 448))))
568 (luna-define-method elmo-folder-delete ((folder elmo-maildir-folder))
569 (let ((msgs (and (elmo-folder-exists-p folder)
570 (elmo-folder-list-messages folder))))
571 (when (yes-or-no-p (format "%sDelete msgdb and substance of \"%s\"? "
572 (if (> (length msgs) 0)
573 (format "%d msg(s) exists. " (length msgs))
575 (elmo-folder-name-internal folder)))
576 (let ((basedir (elmo-maildir-folder-directory-internal folder)))
578 (let ((tmp-files (directory-files
579 (expand-file-name "tmp" basedir)
581 ;; Delete files in tmp.
582 (dolist (file tmp-files)
584 (dolist (dir '("new" "cur" "tmp" "."))
585 (setq dir (expand-file-name dir basedir))
586 (if (not (file-directory-p dir))
588 (elmo-delete-directory dir t))))
590 (elmo-msgdb-delete-path folder)
593 (luna-define-method elmo-folder-rename-internal ((folder elmo-maildir-folder)
595 (let* ((old (elmo-maildir-folder-directory-internal folder))
596 (new (elmo-maildir-folder-directory-internal new-folder))
597 (new-dir (directory-file-name (file-name-directory new))))
598 (unless (file-directory-p old)
599 (error "No such directory: %s" old))
600 (when (file-exists-p new)
601 (error "Already exists directory: %s" new))
602 (unless (file-directory-p new-dir)
603 (elmo-make-directory new-dir))
604 (rename-file old new)
608 (product-provide (provide 'elmo-maildir) (require 'elmo-version))
610 ;;; elmo-maildir.el ends here