* wl-draft.el (wl-draft): Set buffer-undo-list as nil.
[elisp/wanderlust.git] / elmo / elmo-maildir.el
1 ;;; elmo-maildir.el --- Maildir interface for ELMO.
2
3 ;; Copyright (C) 1998,1999,2000 Yuuichi Teranishi <teranisi@gohome.org>
4
5 ;; Author: Yuuichi Teranishi <teranisi@gohome.org>
6 ;; Keywords: mail, net news
7
8 ;; This file is part of ELMO (Elisp Library for Message Orchestration).
9
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)
13 ;; any later version.
14 ;;
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.
19 ;;
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.
24 ;;
25
26 ;;; Commentary:
27 ;;
28
29 ;;; Code:
30 ;;
31
32 (eval-when-compile (require 'cl))
33
34 (require 'elmo-util)
35 (require 'elmo)
36 (require 'elmo-map)
37
38 (defcustom elmo-maildir-folder-path "~/Maildir"
39   "*Maildir folder path."
40   :type 'directory
41   :group 'elmo)
42
43 ;;; ELMO Maildir folder
44 (eval-and-compile
45   (luna-define-class elmo-maildir-folder
46                      (elmo-map-folder)
47                      (directory unread-locations
48                                 flagged-locations
49                                 answered-locations))
50   (luna-define-internal-accessors 'elmo-maildir-folder))
51
52 (luna-define-method elmo-folder-initialize ((folder
53                                              elmo-maildir-folder)
54                                             name)
55   (if (file-name-absolute-p name)
56       (elmo-maildir-folder-set-directory-internal
57        folder
58        (expand-file-name name))
59     (elmo-maildir-folder-set-directory-internal
60      folder
61      (expand-file-name
62       name
63       elmo-maildir-folder-path)))
64   folder)
65
66 (luna-define-method elmo-folder-expand-msgdb-path ((folder
67                                                     elmo-maildir-folder))
68   (expand-file-name
69    (elmo-replace-string-as-filename
70     (elmo-maildir-folder-directory-internal folder))
71    (expand-file-name
72     "maildir"
73     elmo-msgdb-directory)))
74
75 (defun elmo-maildir-message-file-name (folder location)
76   "Get a file name of the message from FOLDER which corresponded to
77 LOCATION."
78   (let ((file (file-name-completion
79                location
80                (expand-file-name
81                 "cur"
82                 (elmo-maildir-folder-directory-internal folder)))))
83     (if file
84         (expand-file-name
85          (if (eq file t) location file)
86          (expand-file-name
87           "cur"
88           (elmo-maildir-folder-directory-internal folder))))))
89
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
93                                nil "^[^.].*$" t))
94          unread-locations flagged-locations answered-locations
95          seen flagged answered sym locations)
96     (setq locations
97           (mapcar
98            (lambda (x)
99              (if (string-match "^\\([^:]+\\):\\([^:]+\\)$" x)
100                  (progn
101                    (setq seen nil answered nil flagged nil)
102                    (save-match-data
103                      (cond
104                       ((string-match "F" (elmo-match-string 2 x))
105                        (setq flagged t))
106                       ((string-match "R" (elmo-match-string 2 x))
107                        (setq answered t))
108                       ((string-match "S" (elmo-match-string 2 x))
109                        (setq seen t))))
110                    (setq sym (elmo-match-string 1 x))
111                    (cond
112                     (flagged (setq flagged-locations
113                                    (cons sym flagged-locations)))
114                     (answered (setq answered-locations
115                                     (cons sym answered-locations)))
116                     (seen)
117                     (t
118                      (setq unread-locations (cons sym unread-locations))))
119                    sym)
120                x))
121            cur))
122     (list locations unread-locations flagged-locations answered-locations)))
123
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))
133     (nth 0 locs)))
134
135 (luna-define-method elmo-map-folder-list-unreads
136   ((folder elmo-maildir-folder))
137   (elmo-maildir-folder-unread-locations-internal folder))
138
139 (luna-define-method elmo-map-folder-list-importants
140   ((folder elmo-maildir-folder))
141   (elmo-maildir-folder-flagged-locations-internal folder))
142
143 (luna-define-method elmo-map-folder-list-answereds
144   ((folder elmo-maildir-folder))
145   (elmo-maildir-folder-answered-locations-internal folder))
146
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
152                          folder))
153          (len (length numbers))
154          (i 0)
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))
160       (setq entity
161             (elmo-msgdb-create-overview-entity-from-file
162              number
163              (setq file
164                    (elmo-maildir-message-file-name folder location))))
165       (when entity
166         (setq overview
167               (elmo-msgdb-append-element overview entity)
168               number-alist
169               (elmo-msgdb-number-add number-alist
170                                      (elmo-message-entity-number entity)
171                                      (setq message-id
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)
176               file-flag nil
177               mark nil)
178         (setq cache-status
179               (elmo-file-cache-status (elmo-file-cache-get message-id)))
180         
181         ;; Already flagged on filename (precede it to flag-table).
182         (cond
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)))
193
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))
197           ;; 
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.
201           (cond
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
210              folder
211              (delete location
212                      (elmo-maildir-folder-unread-locations-internal
213                       folder)))
214             ;; Append to flagged location list.
215             (elmo-maildir-folder-set-flagged-locations-internal
216              folder
217              (cons location
218                    (elmo-maildir-folder-flagged-locations-internal
219                     folder))))
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
225              folder
226              (delete location
227                      (elmo-maildir-folder-unread-locations-internal folder)))
228             ;; Append to answered location list.
229             (elmo-maildir-folder-set-answered-locations-internal
230              folder
231              (cons location
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
238              folder
239              (delete location
240                      (elmo-maildir-folder-unread-locations-internal
241                       folder))))))
242         (if mark
243             (setq mark-alist
244                   (elmo-msgdb-mark-append
245                    mark-alist
246                    (elmo-msgdb-overview-entity-get-number
247                     entity)
248                    mark)))
249         (when (> len elmo-display-progress-threshold)
250           (setq i (1+ i))
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))))
257
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))
262         (count 0)
263         last-accessed)
264     (mapcar (function
265              (lambda (file)
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))
270                                  64064))) ; 36 hours.
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)
275                              t ; full
276                              "^[^.].*$" t))))
277
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"
282                                                   maildir)
283                                 nil
284                                 "^[^.].*$" t)))
285     ;; cleanup tmp directory.
286     (elmo-maildir-cleanup-temporal maildir)
287     ;; move new msgs to cur directory.
288     (while news
289       (rename-file
290        (expand-file-name (car news) (expand-file-name "new" maildir))
291        (expand-file-name (concat
292                           (car news)
293                           (unless (string-match ":2,[A-Z]*$" (car news))
294                             ":2,"))
295                          (expand-file-name "cur" maildir)))
296       (setq news (cdr news)))))
297
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
302                                             2 filename))))
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))))
311   t)
312
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
317                                             2 filename))))
318         (when (memq mark flaglist)
319           (setq flaglist (delq mark flaglist))
320           (rename-file filename
321                        (concat (elmo-match-string 1 filename)
322                                (if flaglist
323                                    (char-list-to-string flaglist))))))))
324
325 (defsubst elmo-maildir-set-mark-msgs (folder locs mark)
326   (dolist (loc locs)
327     (elmo-maildir-set-mark
328      (elmo-maildir-message-file-name folder loc)
329      mark))
330   t)
331
332 (defsubst elmo-maildir-delete-mark-msgs (folder locs mark)
333   (dolist (loc locs)
334     (elmo-maildir-delete-mark
335      (elmo-maildir-message-file-name folder loc)
336      mark))
337   t)
338
339 (luna-define-method elmo-map-folder-mark-as-important ((folder elmo-maildir-folder)
340                                                        locs)
341   (elmo-maildir-set-mark-msgs folder locs ?F))
342   
343 (luna-define-method elmo-map-folder-unmark-important ((folder elmo-maildir-folder)
344                                                       locs)
345   (elmo-maildir-delete-mark-msgs folder locs ?F))
346
347 (luna-define-method elmo-map-folder-mark-as-read ((folder elmo-maildir-folder)
348                                                   locs)
349   (elmo-maildir-set-mark-msgs folder locs ?S))
350
351 (luna-define-method elmo-map-folder-unmark-read ((folder elmo-maildir-folder)
352                                                  locs)
353   (elmo-maildir-delete-mark-msgs folder locs ?S))
354
355 (luna-define-method elmo-map-folder-mark-as-answered ((folder
356                                                        elmo-maildir-folder)
357                                                       locs)
358   (elmo-maildir-set-mark-msgs folder locs ?R))
359
360 (luna-define-method elmo-map-folder-unmark-answered ((folder
361                                                       elmo-maildir-folder)
362                                                      locs)
363   (elmo-maildir-delete-mark-msgs folder locs ?R))
364
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))
370                           elmo-path-sep)))
371         (elmo-list-subdirectories-ignore-regexp
372          "^\\(\\.\\.?\\|cur\\|tmp\\|new\\)$")
373         elmo-have-link-count)
374     (append
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)
380        ""
381        one-level)))))
382
383 (defvar elmo-maildir-sequence-number-internal 0)
384
385 (static-cond
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"
392               (+ (* (car cur-time)
393                     (float 65536)) (cadr cur-time))
394               (emacs-pid)
395               (incf elmo-maildir-sequence-number-internal)
396               (system-name)))))
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)))
405       (format "%s%d.%d.%s"
406               (substring
407                (float-to-string
408                 (f+ (f* (f (car time))
409                         (f 65536))
410                     (f (cadr time))))
411                0 5)
412               (cadr time)
413               (% (abs (random t)) 10000); dummy pid
414               (system-name))))))
415
416 (defun elmo-maildir-temporal-filename (basedir)
417   (let ((filename (expand-file-name
418                    (concat "tmp/" (elmo-maildir-make-unique-string))
419                    basedir)))
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.
424 ;;;   (sleep-for 2)
425       (setq filename
426             (expand-file-name
427              (concat "tmp/" (elmo-maildir-make-unique-string))
428              basedir)))
429     filename))
430
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))
435         dst-buf filename)
436     (condition-case nil
437         (with-temp-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
446            filename
447            (expand-file-name
448             (concat "new/" (file-name-nondirectory filename))
449             basedir))
450           t)
451       ;; If an error occured, return nil.
452       (error))))
453
454 (luna-define-method elmo-folder-message-file-p ((folder elmo-maildir-folder))
455   t)
456
457 (luna-define-method elmo-message-file-name ((folder elmo-maildir-folder)
458                                             number)
459   (elmo-maildir-message-file-name
460    folder
461    (elmo-map-message-location folder number)))
462
463 (luna-define-method elmo-folder-message-make-temp-file-p
464   ((folder elmo-maildir-folder))
465   t)
466
467 (luna-define-method elmo-folder-message-make-temp-files ((folder
468                                                           elmo-maildir-folder)
469                                                          numbers
470                                                          &optional
471                                                          start-number)
472   (let ((temp-dir (elmo-folder-make-temporary-directory folder))
473         (cur-number (if start-number 0)))
474     (dolist (number numbers)
475       (elmo-copy-file
476        (elmo-message-file-name folder number)
477        (expand-file-name
478         (int-to-string (if start-number (incf cur-number) number))
479         temp-dir)))
480     temp-dir))
481
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)))
489             (succeeds numbers)
490             filename mark flag id)
491         (dolist (number numbers)
492           (setq mark (and src-msgdb-exists
493                           (elmo-message-mark src-folder (car numbers)))
494                 flag (cond
495                       ((null mark) 'read)
496                       ((member mark (elmo-msgdb-answered-marks))
497                        'answered)
498                       ((not (member mark (elmo-msgdb-unread-marks)))
499                        'read))
500                 filename (elmo-maildir-temporal-filename dir))
501           (elmo-copy-file
502            (elmo-message-file-name src-folder number)
503            filename)
504           (elmo-add-name-to-file
505            filename
506            (expand-file-name
507             (concat "new/" (file-name-nondirectory filename))
508             dir))
509           ;; src folder's msgdb is loaded.
510           (when (setq id (and src-msgdb-exists
511                               (elmo-message-field src-folder (car numbers)
512                                                   'message-id)))
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))
517         succeeds)
518     (luna-call-next-method)))
519
520 (luna-define-method elmo-map-folder-delete-messages
521   ((folder elmo-maildir-folder) locations)
522   (let (file)
523     (dolist (location locations)
524       (setq file (elmo-maildir-message-file-name folder location))
525       (if (and file
526                (file-writable-p file)
527                (not (file-directory-p file)))
528           (delete-file file)))))
529
530 (luna-define-method elmo-map-message-fetch ((folder elmo-maildir-folder)
531                                             location strategy
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))))
536
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)))))
542
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))))
548
549 (luna-define-method elmo-folder-creatable-p ((folder elmo-maildir-folder))
550   t)
551
552 (luna-define-method elmo-folder-writable-p ((folder elmo-maildir-folder))
553   t)
554
555 (luna-define-method elmo-folder-create ((folder elmo-maildir-folder))
556   (let ((basedir (elmo-maildir-folder-directory-internal folder)))
557     (condition-case nil
558         (progn
559           (dolist (dir '("." "new" "cur" "tmp"))
560             (setq dir (expand-file-name dir basedir))
561             (or (file-directory-p dir)
562                 (progn
563                   (elmo-make-directory dir)
564                   (set-file-modes dir 448))))
565           t)
566       (error))))
567
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))
574                                  "")
575                                (elmo-folder-name-internal folder)))
576       (let ((basedir (elmo-maildir-folder-directory-internal folder)))
577         (condition-case nil
578             (let ((tmp-files (directory-files
579                               (expand-file-name "tmp" basedir)
580                               t "[^.].*")))
581               ;; Delete files in tmp.
582               (dolist (file tmp-files)
583                 (delete-file file))
584               (dolist (dir '("new" "cur" "tmp" "."))
585                 (setq dir (expand-file-name dir basedir))
586                 (if (not (file-directory-p dir))
587                     (error nil)
588                   (elmo-delete-directory dir t))))
589           (error nil)))
590       (elmo-msgdb-delete-path folder)
591       t)))
592
593 (luna-define-method elmo-folder-rename-internal ((folder elmo-maildir-folder)
594                                                  new-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)
605     t))
606
607 (require 'product)
608 (product-provide (provide 'elmo-maildir) (require 'elmo-version))
609
610 ;;; elmo-maildir.el ends here