* elmo.el (elmo-folder-list-flagged): New generic function.
[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 flagged-locations))
48   (luna-define-internal-accessors 'elmo-maildir-folder))
49
50 (luna-define-method elmo-folder-initialize ((folder
51                                              elmo-maildir-folder)
52                                             name)
53   (if (file-name-absolute-p name)
54       (elmo-maildir-folder-set-directory-internal
55        folder
56        (expand-file-name name))
57     (elmo-maildir-folder-set-directory-internal
58      folder
59      (expand-file-name
60       name
61       elmo-maildir-folder-path)))
62   folder)
63
64 (luna-define-method elmo-folder-expand-msgdb-path ((folder
65                                                     elmo-maildir-folder))
66   (expand-file-name
67    (elmo-replace-string-as-filename
68     (elmo-maildir-folder-directory-internal folder))
69    (expand-file-name
70     "maildir"
71     elmo-msgdb-directory)))
72
73 (defun elmo-maildir-message-file-name (folder location)
74   "Get a file name of the message from FOLDER which corresponded to
75 LOCATION."
76   (let ((file (file-name-completion
77                location
78                (expand-file-name
79                 "cur"
80                 (elmo-maildir-folder-directory-internal folder)))))
81     (if file
82         (expand-file-name
83          (if (eq file t) location file)
84          (expand-file-name
85           "cur"
86           (elmo-maildir-folder-directory-internal folder))))))
87
88 (defsubst elmo-maildir-list-location (dir &optional child-dir)
89   (let* ((cur-dir (expand-file-name (or child-dir "cur") dir))
90          (cur (directory-files cur-dir
91                                nil "^[^.].*$" t))
92          unread-locations flagged-locations seen flagged sym
93          locations)
94     (setq locations
95           (mapcar
96            (lambda (x)
97              (if (string-match "^\\([^:]+\\):\\([^:]+\\)$" x)
98                  (progn
99                    (setq seen nil)
100                    (save-match-data
101                      (cond
102                       ((string-match "S" (elmo-match-string 2 x))
103                        (setq seen t))
104                       ((string-match "F" (elmo-match-string 2 x))
105                        (setq flagged t))))
106                    (setq sym (elmo-match-string 1 x))
107                    (unless seen (setq unread-locations
108                                       (cons sym unread-locations)))
109                    (if flagged (setq flagged-locations
110                                      (cons sym flagged-locations)))
111                    sym)
112                x))
113            cur))
114     (list locations unread-locations flagged-locations)))
115
116 (luna-define-method elmo-map-folder-list-message-locations
117   ((folder elmo-maildir-folder))
118   (elmo-maildir-update-current folder)
119   (let ((locs (elmo-maildir-list-location
120                (elmo-maildir-folder-directory-internal folder))))
121     ;; 0: locations, 1: unread-locations, 2: flagged-locations
122     (elmo-maildir-folder-set-unread-locations-internal folder (nth 1 locs))
123     (elmo-maildir-folder-set-flagged-locations-internal folder (nth 2 locs))
124     (nth 0 locs)))
125
126 (luna-define-method elmo-map-folder-list-unreads
127   ((folder elmo-maildir-folder))
128   (elmo-maildir-folder-unread-locations-internal folder))
129
130 (luna-define-method elmo-map-folder-list-importants
131   ((folder elmo-maildir-folder))
132   (elmo-maildir-folder-flagged-locations-internal folder))
133
134 (luna-define-method elmo-folder-msgdb-create 
135   ((folder elmo-maildir-folder) numbers seen-list)
136   (let* ((unread-list (elmo-maildir-folder-unread-locations-internal folder))
137          (flagged-list (elmo-maildir-folder-flagged-locations-internal folder))
138          (len (length numbers))
139          (i 0)
140          overview number-alist mark-alist entity
141          location pair mark)
142     (message "Creating msgdb...")
143     (dolist
144         (number numbers)
145       (setq location (elmo-map-message-location folder number))
146       (setq entity
147             (elmo-msgdb-create-overview-entity-from-file
148              number
149              (elmo-maildir-message-file-name folder location)))
150       (when entity
151         (setq overview
152               (elmo-msgdb-append-element overview entity))
153         (setq number-alist
154               (elmo-msgdb-number-add number-alist
155                                      (elmo-msgdb-overview-entity-get-number
156                                       entity)
157                                      (elmo-msgdb-overview-entity-get-id
158                                       entity)))
159         (cond 
160          ((member location unread-list)
161           (setq mark elmo-msgdb-new-mark)) ; unread!
162          ((member location flagged-list)
163           (setq mark elmo-msgdb-important-mark)))
164         (if (setq mark (or (elmo-msgdb-global-mark-get
165                             (elmo-msgdb-overview-entity-get-id
166                              entity))
167                            mark))
168             (setq mark-alist
169                   (elmo-msgdb-mark-append
170                    mark-alist
171                    (elmo-msgdb-overview-entity-get-number
172                     entity)
173                    mark)))
174         (when (> len elmo-display-progress-threshold)
175           (setq i (1+ i))
176           (elmo-display-progress
177            'elmo-maildir-msgdb-create "Creating msgdb..."
178            (/ (* i 100) len)))))
179     (message "Creating msgdb...done")
180     (elmo-msgdb-sort-by-date
181      (list overview number-alist mark-alist))))
182
183 (defun elmo-maildir-cleanup-temporal (dir)
184   ;; Delete files in the tmp dir which are not accessed
185   ;; for more than 36 hours.
186   (let ((cur-time (current-time))
187         (count 0)
188         last-accessed)
189     (mapcar (function
190              (lambda (file)
191                (setq last-accessed (nth 4 (file-attributes file)))
192                (when (or (> (- (car cur-time)(car last-accessed)) 1)
193                          (and (eq (- (car cur-time)(car last-accessed)) 1)
194                               (> (- (cadr cur-time)(cadr last-accessed))
195                                  64064))) ; 36 hours.
196                  (message "Maildir: %d tmp file(s) are cleared."
197                           (setq count (1+ count)))
198                  (delete-file file))))
199             (directory-files (expand-file-name "tmp" dir)
200                              t ; full
201                              "^[^.].*$" t))))
202
203 (defun elmo-maildir-update-current (folder)
204   "Move all new msgs to cur in the maildir."
205   (let* ((maildir (elmo-maildir-folder-directory-internal folder))
206          (news (directory-files (expand-file-name "new"
207                                                   maildir)
208                                 nil
209                                 "^[^.].*$" t)))
210     ;; cleanup tmp directory.
211     (elmo-maildir-cleanup-temporal maildir)
212     ;; move new msgs to cur directory.
213     (while news
214       (rename-file
215        (expand-file-name (car news) (expand-file-name "new" maildir))
216        (expand-file-name (concat (car news) ":2,")
217                          (expand-file-name "cur" maildir)))
218       (setq news (cdr news)))))
219
220 (defun elmo-maildir-set-mark (filename mark)
221   "Mark the FILENAME file in the maildir.  MARK is a character."
222   (if (string-match "^\\([^:]+:[12],\\)\\(.*\\)$" filename)
223       (let ((flaglist (string-to-char-list (elmo-match-string
224                                             2 filename))))
225         (unless (memq mark flaglist)
226           (setq flaglist (sort (cons mark flaglist) '<))
227           (rename-file filename
228                        (concat (elmo-match-string 1 filename)
229                                (char-list-to-string flaglist)))))
230     ;; Rescue no info file in maildir.
231     (rename-file filename
232                  (concat filename ":2," (char-to-string mark))))
233   t)
234
235 (defun elmo-maildir-delete-mark (filename mark)
236   "Mark the FILENAME file in the maildir.  MARK is a character."
237   (if (string-match "^\\([^:]+:2,\\)\\(.*\\)$" filename)
238       (let ((flaglist (string-to-char-list (elmo-match-string
239                                             2 filename))))
240         (when (memq mark flaglist)
241           (setq flaglist (delq mark flaglist))
242           (rename-file filename
243                        (concat (elmo-match-string 1 filename)
244                                (if flaglist
245                                    (char-list-to-string flaglist))))))))
246
247 (defsubst elmo-maildir-set-mark-msgs (folder locs mark)
248   (dolist (loc locs)
249     (elmo-maildir-set-mark
250      (elmo-maildir-message-file-name folder loc)
251      mark))
252   t)
253
254 (defsubst elmo-maildir-delete-mark-msgs (folder locs mark)
255   (dolist (loc locs)
256     (elmo-maildir-delete-mark
257      (elmo-maildir-message-file-name folder loc)
258      mark))
259   t)
260
261 (luna-define-method elmo-map-folder-mark-as-important ((folder elmo-maildir-folder)
262                                                        locs)
263   (elmo-maildir-set-mark-msgs folder locs ?F))
264   
265 (luna-define-method elmo-map-folder-unmark-important ((folder elmo-maildir-folder)
266                                                       locs)
267   (elmo-maildir-delete-mark-msgs folder locs ?F))
268
269 (luna-define-method elmo-map-folder-mark-as-read ((folder elmo-maildir-folder)
270                                                   locs)
271   (elmo-maildir-set-mark-msgs folder locs ?S))
272
273 (luna-define-method elmo-map-folder-unmark-read ((folder elmo-maildir-folder)
274                                                  locs)
275   (elmo-maildir-delete-mark-msgs folder locs ?S))
276
277 (luna-define-method elmo-map-folder-mark-as-answered ((folder
278                                                        elmo-maildir-folder)
279                                                       locs)
280   (elmo-maildir-set-mark-msgs folder locs ?R))
281
282 (luna-define-method elmo-map-folder-unmark-answered ((folder
283                                                       elmo-maildir-folder)
284                                                      locs)
285   (elmo-maildir-delete-mark-msgs folder locs ?R))
286
287 (luna-define-method elmo-folder-list-subfolders
288   ((folder elmo-maildir-folder) &optional one-level)
289   (let ((prefix (concat (elmo-folder-name-internal folder)
290                         (unless (string= (elmo-folder-prefix-internal folder)
291                                          (elmo-folder-name-internal folder))
292                           elmo-path-sep)))
293         (elmo-list-subdirectories-ignore-regexp
294          "^\\(\\.\\.?\\|cur\\|tmp\\|new\\)$")
295         elmo-have-link-count)
296     (append
297      (list (elmo-folder-name-internal folder))
298      (elmo-mapcar-list-of-list
299       (function (lambda (x) (concat prefix x)))
300       (elmo-list-subdirectories
301        (elmo-maildir-folder-directory-internal folder)
302        ""
303        one-level)))))
304
305 (defvar elmo-maildir-sequence-number-internal 0)
306
307 (static-cond
308  ((>= emacs-major-version 19)
309   (defun elmo-maildir-make-unique-string ()
310     "This function generates a string that can be used as a unique
311 file name for maildir directories."
312      (let ((cur-time (current-time)))
313        (format "%.0f.%d_%d.%s"
314               (+ (* (car cur-time)
315                     (float 65536)) (cadr cur-time))
316               (emacs-pid)
317               (incf elmo-maildir-sequence-number-internal)
318               (system-name)))))
319  ((eq emacs-major-version 18)
320   ;; A fake function for v18
321   (defun elmo-maildir-make-unique-string ()
322     "This function generates a string that can be used as a unique
323 file name for maildir directories."
324     (unless (fboundp 'float-to-string)
325       (load-library "float"))
326     (let ((time (current-time)))
327       (format "%s%d.%d.%s"
328               (substring
329                (float-to-string
330                 (f+ (f* (f (car time))
331                         (f 65536))
332                     (f (cadr time))))
333                0 5)
334               (cadr time)
335               (% (abs (random t)) 10000); dummy pid
336               (system-name))))))
337
338 (defun elmo-maildir-temporal-filename (basedir)
339   (let ((filename (expand-file-name
340                    (concat "tmp/" (elmo-maildir-make-unique-string))
341                    basedir)))
342     (unless (file-exists-p (file-name-directory filename))
343       (make-directory (file-name-directory filename)))
344     (while (file-exists-p filename)
345 ;;; I don't want to wait.
346 ;;;   (sleep-for 2)
347       (setq filename
348             (expand-file-name
349              (concat "tmp/" (elmo-maildir-make-unique-string))
350              basedir)))
351     filename))
352
353 (luna-define-method elmo-folder-append-buffer ((folder elmo-maildir-folder)
354                                                unread &optional number)
355   (let ((basedir (elmo-maildir-folder-directory-internal folder))
356         (src-buf (current-buffer))
357         dst-buf filename)
358     (condition-case nil
359         (with-temp-buffer
360           (setq filename (elmo-maildir-temporal-filename basedir))
361           (setq dst-buf (current-buffer))
362           (with-current-buffer src-buf
363             (copy-to-buffer dst-buf (point-min) (point-max)))
364           (as-binary-output-file
365            (write-region (point-min) (point-max) filename nil 'no-msg))
366           ;; add link from new.
367           (elmo-add-name-to-file
368            filename
369            (expand-file-name
370             (concat "new/" (file-name-nondirectory filename))
371             basedir))
372           t)
373       ;; If an error occured, return nil.
374       (error))))
375
376 (luna-define-method elmo-folder-message-file-p ((folder elmo-maildir-folder))
377   t)
378
379 (luna-define-method elmo-message-file-name ((folder elmo-maildir-folder)
380                                             number)
381   (elmo-maildir-message-file-name
382    folder
383    (elmo-map-message-location folder number)))
384
385 (luna-define-method elmo-folder-message-make-temp-file-p
386   ((folder elmo-maildir-folder))
387   t)
388
389 (luna-define-method elmo-folder-message-make-temp-files ((folder
390                                                           elmo-maildir-folder)
391                                                          numbers
392                                                          &optional
393                                                          start-number)
394   (let ((temp-dir (elmo-folder-make-temporary-directory folder))
395         (cur-number (if start-number 0)))
396     (dolist (number numbers)
397       (elmo-copy-file
398        (elmo-message-file-name folder number)
399        (expand-file-name
400         (int-to-string (if start-number (incf cur-number) number))
401         temp-dir)))
402     temp-dir))
403
404 (luna-define-method elmo-folder-append-messages :around
405   ((folder elmo-maildir-folder)
406    src-folder numbers &optional same-number)
407   (if (elmo-folder-message-file-p src-folder)
408       (let ((dir (elmo-maildir-folder-directory-internal folder))
409             (succeeds numbers)
410             filename)
411         (dolist (number numbers)
412           (setq filename (elmo-maildir-temporal-filename dir))
413           (elmo-copy-file
414            (elmo-message-file-name src-folder number)
415            filename)
416           (elmo-add-name-to-file
417            filename
418            (expand-file-name
419             (concat "new/" (file-name-nondirectory filename))
420             dir))
421           (elmo-progress-notify 'elmo-folder-move-messages))
422         succeeds)
423     (luna-call-next-method)))
424
425 (luna-define-method elmo-map-folder-delete-messages
426   ((folder elmo-maildir-folder) locations)
427   (let (file)
428     (dolist (location locations)
429       (setq file (elmo-maildir-message-file-name folder location))
430       (if (and file
431                (file-writable-p file)
432                (not (file-directory-p file)))
433           (delete-file file)))))
434
435 (luna-define-method elmo-map-message-fetch ((folder elmo-maildir-folder)
436                                             location strategy
437                                             &optional section unseen)
438   (let ((file (elmo-maildir-message-file-name folder location)))
439     (when (file-exists-p file)
440       (insert-file-contents-as-binary file))))
441
442 (luna-define-method elmo-folder-exists-p ((folder elmo-maildir-folder))
443   (let ((basedir (elmo-maildir-folder-directory-internal folder)))
444     (and (file-directory-p (expand-file-name "new" basedir))
445          (file-directory-p (expand-file-name "cur" basedir))
446          (file-directory-p (expand-file-name "tmp" basedir)))))
447
448 (luna-define-method elmo-folder-diff ((folder elmo-maildir-folder)
449                                       &optional numbers)
450   (let* ((dir (elmo-maildir-folder-directory-internal folder))
451          (new-len (length (car (elmo-maildir-list-location dir "new"))))
452          (cur-len (length (car (elmo-maildir-list-location dir "cur")))))
453     (cons new-len (+ new-len cur-len))))
454
455 (luna-define-method elmo-folder-creatable-p ((folder elmo-maildir-folder))
456   t)
457
458 (luna-define-method elmo-folder-writable-p ((folder elmo-maildir-folder))
459   t)
460
461 (luna-define-method elmo-folder-create ((folder elmo-maildir-folder))
462   (let ((basedir (elmo-maildir-folder-directory-internal folder)))
463     (condition-case nil
464         (progn
465           (dolist (dir '("." "new" "cur" "tmp"))
466             (setq dir (expand-file-name dir basedir))
467             (or (file-directory-p dir)
468                 (progn
469                   (elmo-make-directory dir)
470                   (set-file-modes dir 448))))
471           t)
472       (error))))
473
474 (luna-define-method elmo-folder-delete :before ((folder elmo-maildir-folder))
475   (let ((basedir (elmo-maildir-folder-directory-internal folder)))
476     (condition-case nil
477         (let ((tmp-files (directory-files
478                           (expand-file-name "tmp" basedir)
479                           t "[^.].*")))
480           ;; Delete files in tmp.
481           (dolist (file tmp-files)
482             (delete-file file))
483           (dolist (dir '("new" "cur" "tmp" "."))
484             (setq dir (expand-file-name dir basedir))
485             (if (not (file-directory-p dir))
486                 (error nil)
487               (elmo-delete-directory dir t)))
488           t)
489       (error nil))))
490
491 (require 'product)
492 (product-provide (provide 'elmo-maildir) (require 'elmo-version))
493
494 ;;; elmo-maildir.el ends here