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 sym locations flag-list)
99 (if (string-match "^\\([^:]+\\):\\([^:]+\\)$" x)
101 (setq sym (elmo-match-string 1 x)
102 flag-list (string-to-char-list
103 (elmo-match-string 2 x)))
104 (when (memq ?F flag-list)
105 (setq flagged-locations
106 (cons sym flagged-locations)))
107 (when (memq ?R flag-list)
108 (setq answered-locations
109 (cons sym answered-locations)))
110 (unless (memq ?S flag-list)
111 (setq unread-locations
112 (cons sym unread-locations)))
116 (list locations unread-locations flagged-locations answered-locations)))
118 (luna-define-method elmo-map-folder-list-message-locations
119 ((folder elmo-maildir-folder))
120 (elmo-maildir-update-current folder)
121 (let ((locs (elmo-maildir-list-location
122 (elmo-maildir-folder-directory-internal folder))))
123 ;; 0: locations, 1: unread-locs, 2: flagged-locs 3: answered-locs
124 (elmo-maildir-folder-set-unread-locations-internal folder (nth 1 locs))
125 (elmo-maildir-folder-set-flagged-locations-internal folder (nth 2 locs))
126 (elmo-maildir-folder-set-answered-locations-internal folder (nth 3 locs))
129 (luna-define-method elmo-map-folder-list-unreads
130 ((folder elmo-maildir-folder))
131 (elmo-maildir-folder-unread-locations-internal folder))
133 (luna-define-method elmo-map-folder-list-importants
134 ((folder elmo-maildir-folder))
135 (elmo-maildir-folder-flagged-locations-internal folder))
137 (luna-define-method elmo-map-folder-list-answereds
138 ((folder elmo-maildir-folder))
139 (elmo-maildir-folder-answered-locations-internal folder))
141 (luna-define-method elmo-folder-msgdb-create ((folder elmo-maildir-folder)
143 (let* ((unread-list (elmo-maildir-folder-unread-locations-internal folder))
144 (flagged-list (elmo-maildir-folder-flagged-locations-internal folder))
145 (answered-list (elmo-maildir-folder-answered-locations-internal
147 (len (length numbers))
148 (new-msgdb (elmo-make-msgdb))
150 entity message-id flags location)
151 (message "Creating msgdb...")
152 (dolist (number numbers)
153 (setq location (elmo-map-message-location folder number))
155 (elmo-msgdb-create-message-entity-from-file
156 (elmo-msgdb-message-entity-handler new-msgdb)
158 (elmo-maildir-message-file-name folder location)))
160 (setq message-id (elmo-message-entity-field entity 'message-id)
161 ;; Precede flag-table to file-info.
163 (elmo-flag-table-get flag-table message-id)))
165 ;; Already flagged on filename (precede it to flag-table).
166 (when (member location flagged-list)
167 (or (memq 'important flags)
168 (setq flags (cons 'important flags))))
169 (when (member location answered-list)
170 (or (memq 'answered flags)
171 (setq flags (cons 'answered flags))))
172 (unless (member location unread-list)
173 (and (memq 'unread flags)
174 (setq flags (delq 'unread flags))))
176 ;; Update filename's info portion according to the flag-table.
177 (when (and (memq 'important flags)
178 (not (member location flagged-list)))
179 (elmo-maildir-set-mark
180 (elmo-maildir-message-file-name folder location)
182 ;; Append to flagged location list.
183 (elmo-maildir-folder-set-flagged-locations-internal
186 (elmo-maildir-folder-flagged-locations-internal
188 (setq flags (delq 'unread flags)))
189 (when (and (memq 'answered flags)
190 (not (member location answered-list)))
191 (elmo-maildir-set-mark
192 (elmo-maildir-message-file-name folder location)
194 ;; Append to answered location list.
195 (elmo-maildir-folder-set-answered-locations-internal
198 (elmo-maildir-folder-answered-locations-internal folder)))
199 (setq flags (delq 'unread flags)))
200 (when (and (not (memq 'unread flags))
201 (member location unread-list))
202 (elmo-maildir-set-mark
203 (elmo-maildir-message-file-name folder location)
205 ;; Delete from unread locations.
206 (elmo-maildir-folder-set-unread-locations-internal
209 (elmo-maildir-folder-unread-locations-internal
211 (unless (memq 'unread flags)
212 (setq flags (delq 'new flags)))
213 (elmo-global-flags-set flags folder number message-id)
214 (elmo-msgdb-append-entity new-msgdb entity flags)
215 (when (> len elmo-display-progress-threshold)
217 (elmo-display-progress
218 'elmo-maildir-msgdb-create "Creating msgdb..."
219 (/ (* i 100) len)))))
220 (message "Creating msgdb...done")
221 (elmo-msgdb-sort-by-date new-msgdb)))
223 (defun elmo-maildir-cleanup-temporal (dir)
224 ;; Delete files in the tmp dir which are not accessed
225 ;; for more than 36 hours.
226 (let ((cur-time (current-time))
231 (setq last-accessed (nth 4 (file-attributes file)))
232 (when (or (> (- (car cur-time)(car last-accessed)) 1)
233 (and (eq (- (car cur-time)(car last-accessed)) 1)
234 (> (- (cadr cur-time)(cadr last-accessed))
236 (message "Maildir: %d tmp file(s) are cleared."
237 (setq count (1+ count)))
238 (delete-file file))))
239 (directory-files (expand-file-name "tmp" dir)
243 (defun elmo-maildir-update-current (folder)
244 "Move all new msgs to cur in the maildir."
245 (let* ((maildir (elmo-maildir-folder-directory-internal folder))
246 (news (directory-files (expand-file-name "new"
250 ;; cleanup tmp directory.
251 (elmo-maildir-cleanup-temporal maildir)
252 ;; move new msgs to cur directory.
255 (expand-file-name (car news) (expand-file-name "new" maildir))
256 (expand-file-name (concat
258 (unless (string-match ":2,[A-Z]*$" (car news))
260 (expand-file-name "cur" maildir)))
261 (setq news (cdr news)))))
263 (defun elmo-maildir-set-mark (filename mark)
264 "Mark the FILENAME file in the maildir. MARK is a character."
265 (if (string-match "^\\([^:]+:[12],\\)\\(.*\\)$" filename)
266 (let ((flaglist (string-to-char-list (elmo-match-string
268 (unless (memq mark flaglist)
269 (setq flaglist (sort (cons mark flaglist) '<))
270 (rename-file filename
271 (concat (elmo-match-string 1 filename)
272 (char-list-to-string flaglist)))))
273 ;; Rescue no info file in maildir.
274 (rename-file filename
275 (concat filename ":2," (char-to-string mark))))
278 (defun elmo-maildir-delete-mark (filename mark)
279 "Mark the FILENAME file in the maildir. MARK is a character."
280 (if (string-match "^\\([^:]+:2,\\)\\(.*\\)$" filename)
281 (let ((flaglist (string-to-char-list (elmo-match-string
283 (when (memq mark flaglist)
284 (setq flaglist (delq mark flaglist))
285 (rename-file filename
286 (concat (elmo-match-string 1 filename)
288 (char-list-to-string flaglist))))))))
290 (defsubst elmo-maildir-set-mark-msgs (folder locs mark)
292 (elmo-maildir-set-mark
293 (elmo-maildir-message-file-name folder loc)
297 (defsubst elmo-maildir-delete-mark-msgs (folder locs mark)
299 (elmo-maildir-delete-mark
300 (elmo-maildir-message-file-name folder loc)
304 (luna-define-method elmo-map-folder-flag-as-important ((folder elmo-maildir-folder)
306 (elmo-maildir-set-mark-msgs folder locs ?F))
308 (luna-define-method elmo-map-folder-unflag-important ((folder elmo-maildir-folder)
310 (elmo-maildir-delete-mark-msgs folder locs ?F))
312 (luna-define-method elmo-map-folder-flag-as-read ((folder elmo-maildir-folder)
314 (elmo-maildir-set-mark-msgs folder locs ?S))
316 (luna-define-method elmo-map-folder-unflag-read ((folder elmo-maildir-folder)
318 (elmo-maildir-delete-mark-msgs folder locs ?S))
320 (luna-define-method elmo-map-folder-flag-as-answered ((folder
323 (elmo-maildir-set-mark-msgs folder locs ?R))
325 (luna-define-method elmo-map-folder-unflag-answered ((folder
328 (elmo-maildir-delete-mark-msgs folder locs ?R))
330 (luna-define-method elmo-folder-list-subfolders
331 ((folder elmo-maildir-folder) &optional one-level)
332 (let ((prefix (concat (elmo-folder-name-internal folder)
333 (unless (string= (elmo-folder-prefix-internal folder)
334 (elmo-folder-name-internal folder))
336 (elmo-list-subdirectories-ignore-regexp
337 "^\\(\\.\\.?\\|cur\\|tmp\\|new\\)$")
338 elmo-have-link-count)
340 (list (elmo-folder-name-internal folder))
341 (elmo-mapcar-list-of-list
342 (function (lambda (x) (concat prefix x)))
343 (elmo-list-subdirectories
344 (elmo-maildir-folder-directory-internal folder)
348 (defvar elmo-maildir-sequence-number-internal 0)
351 ((>= emacs-major-version 19)
352 (defun elmo-maildir-make-unique-string ()
353 "This function generates a string that can be used as a unique
354 file name for maildir directories."
355 (let ((cur-time (current-time)))
356 (format "%.0f.%d_%d.%s"
358 (float 65536)) (cadr cur-time))
360 (incf elmo-maildir-sequence-number-internal)
362 ((eq emacs-major-version 18)
363 ;; A fake function for v18
364 (defun elmo-maildir-make-unique-string ()
365 "This function generates a string that can be used as a unique
366 file name for maildir directories."
367 (unless (fboundp 'float-to-string)
368 (load-library "float"))
369 (let ((time (current-time)))
373 (f+ (f* (f (car time))
378 (% (abs (random t)) 10000); dummy pid
381 (defun elmo-maildir-temporal-filename (basedir)
382 (let ((filename (expand-file-name
383 (concat "tmp/" (elmo-maildir-make-unique-string))
385 (unless (file-exists-p (file-name-directory filename))
386 (make-directory (file-name-directory filename)))
387 (while (file-exists-p filename)
388 ;;; I don't want to wait.
392 (concat "tmp/" (elmo-maildir-make-unique-string))
396 (luna-define-method elmo-folder-append-buffer ((folder elmo-maildir-folder)
397 &optional status number)
398 (let ((basedir (elmo-maildir-folder-directory-internal folder))
399 (src-buf (current-buffer))
403 (setq filename (elmo-maildir-temporal-filename basedir))
404 (setq dst-buf (current-buffer))
405 (with-current-buffer src-buf
406 (copy-to-buffer dst-buf (point-min) (point-max)))
407 (as-binary-output-file
408 (write-region (point-min) (point-max) filename nil 'no-msg))
409 ;; add link from new.
410 (elmo-add-name-to-file
413 (concat "new/" (file-name-nondirectory filename))
416 ;; If an error occured, return nil.
419 (luna-define-method elmo-folder-message-file-p ((folder elmo-maildir-folder))
422 (luna-define-method elmo-message-file-name ((folder elmo-maildir-folder)
424 (elmo-maildir-message-file-name
426 (elmo-map-message-location folder number)))
428 (luna-define-method elmo-folder-message-make-temp-file-p
429 ((folder elmo-maildir-folder))
432 (luna-define-method elmo-folder-message-make-temp-files ((folder
437 (let ((temp-dir (elmo-folder-make-temporary-directory folder))
438 (cur-number (if start-number 0)))
439 (dolist (number numbers)
441 (elmo-message-file-name folder number)
443 (int-to-string (if start-number (incf cur-number) number))
447 (luna-define-method elmo-folder-append-messages :around
448 ((folder elmo-maildir-folder)
449 src-folder numbers &optional same-number)
450 (if (elmo-folder-message-file-p src-folder)
451 (let ((src-msgdb-exists (not (zerop (elmo-folder-length src-folder))))
452 (dir (elmo-maildir-folder-directory-internal folder))
453 (table (elmo-flag-table-load (elmo-folder-msgdb-path folder)))
456 (dolist (number numbers)
457 (setq flags (elmo-message-flags src-folder (car numbers))
458 filename (elmo-maildir-temporal-filename dir))
460 (elmo-message-file-name src-folder number)
462 (elmo-add-name-to-file
465 (concat "new/" (file-name-nondirectory filename))
467 ;; src folder's msgdb is loaded.
468 (when (setq id (and src-msgdb-exists
469 (elmo-message-field src-folder (car numbers)
471 (elmo-flag-table-set table id flags))
472 (elmo-progress-notify 'elmo-folder-move-messages))
473 (when (elmo-folder-persistent-p folder)
474 (elmo-flag-table-save (elmo-folder-msgdb-path folder) table))
476 (luna-call-next-method)))
478 (luna-define-method elmo-map-folder-delete-messages
479 ((folder elmo-maildir-folder) locations)
481 (dolist (location locations)
482 (setq file (elmo-maildir-message-file-name folder location))
484 (file-writable-p file)
485 (not (file-directory-p file)))
486 (delete-file file)))))
488 (luna-define-method elmo-map-message-fetch ((folder elmo-maildir-folder)
490 &optional section unseen)
491 (let ((file (elmo-maildir-message-file-name folder location)))
492 (when (file-exists-p file)
493 (insert-file-contents-as-binary file))))
495 (luna-define-method elmo-folder-exists-p ((folder elmo-maildir-folder))
496 (let ((basedir (elmo-maildir-folder-directory-internal folder)))
497 (and (file-directory-p (expand-file-name "new" basedir))
498 (file-directory-p (expand-file-name "cur" basedir))
499 (file-directory-p (expand-file-name "tmp" basedir)))))
501 (luna-define-method elmo-folder-diff ((folder elmo-maildir-folder))
502 (let* ((dir (elmo-maildir-folder-directory-internal folder))
503 (new-len (length (car (elmo-maildir-list-location dir "new"))))
504 (cur-len (length (car (elmo-maildir-list-location dir "cur")))))
505 (cons new-len (+ new-len cur-len))))
507 (luna-define-method elmo-folder-creatable-p ((folder elmo-maildir-folder))
510 (luna-define-method elmo-folder-writable-p ((folder elmo-maildir-folder))
513 (luna-define-method elmo-folder-create ((folder elmo-maildir-folder))
514 (let ((basedir (elmo-maildir-folder-directory-internal folder)))
517 (dolist (dir '("." "new" "cur" "tmp"))
518 (setq dir (expand-file-name dir basedir))
519 (or (file-directory-p dir)
521 (elmo-make-directory dir)
522 (set-file-modes dir 448))))
526 (luna-define-method elmo-folder-delete ((folder elmo-maildir-folder))
527 (let ((msgs (and (elmo-folder-exists-p folder)
528 (elmo-folder-list-messages folder))))
529 (when (yes-or-no-p (format "%sDelete msgdb and substance of \"%s\"? "
530 (if (> (length msgs) 0)
531 (format "%d msg(s) exists. " (length msgs))
533 (elmo-folder-name-internal folder)))
534 (let ((basedir (elmo-maildir-folder-directory-internal folder)))
536 (let ((tmp-files (directory-files
537 (expand-file-name "tmp" basedir)
539 ;; Delete files in tmp.
540 (dolist (file tmp-files)
542 (dolist (dir '("new" "cur" "tmp" "."))
543 (setq dir (expand-file-name dir basedir))
544 (if (not (file-directory-p dir))
546 (elmo-delete-directory dir t))))
548 (elmo-msgdb-delete-path folder)
551 (luna-define-method elmo-folder-rename-internal ((folder elmo-maildir-folder)
553 (let* ((old (elmo-maildir-folder-directory-internal folder))
554 (new (elmo-maildir-folder-directory-internal new-folder))
555 (new-dir (directory-file-name (file-name-directory new))))
556 (unless (file-directory-p old)
557 (error "No such directory: %s" old))
558 (when (file-exists-p new)
559 (error "Already exists directory: %s" new))
560 (unless (file-directory-p new-dir)
561 (elmo-make-directory new-dir))
562 (rename-file old new)
566 (product-provide (provide 'elmo-maildir) (require 'elmo-version))
568 ;;; elmo-maildir.el ends here