583b39a38f418fb7f1b06f4e50151ff670d0ff00
[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
217                           (car news)
218                           (unless (string-match ":2,[A-Z]*$" (car news))
219                             ":2,"))
220                          (expand-file-name "cur" maildir)))
221       (setq news (cdr news)))))
222
223 (defun elmo-maildir-set-mark (filename mark)
224   "Mark the FILENAME file in the maildir.  MARK is a character."
225   (if (string-match "^\\([^:]+:[12],\\)\\(.*\\)$" filename)
226       (let ((flaglist (string-to-char-list (elmo-match-string
227                                             2 filename))))
228         (unless (memq mark flaglist)
229           (setq flaglist (sort (cons mark flaglist) '<))
230           (rename-file filename
231                        (concat (elmo-match-string 1 filename)
232                                (char-list-to-string flaglist)))))
233     ;; Rescue no info file in maildir.
234     (rename-file filename
235                  (concat filename ":2," (char-to-string mark))))
236   t)
237
238 (defun elmo-maildir-delete-mark (filename mark)
239   "Mark the FILENAME file in the maildir.  MARK is a character."
240   (if (string-match "^\\([^:]+:2,\\)\\(.*\\)$" filename)
241       (let ((flaglist (string-to-char-list (elmo-match-string
242                                             2 filename))))
243         (when (memq mark flaglist)
244           (setq flaglist (delq mark flaglist))
245           (rename-file filename
246                        (concat (elmo-match-string 1 filename)
247                                (if flaglist
248                                    (char-list-to-string flaglist))))))))
249
250 (defsubst elmo-maildir-set-mark-msgs (folder locs mark)
251   (dolist (loc locs)
252     (elmo-maildir-set-mark
253      (elmo-maildir-message-file-name folder loc)
254      mark))
255   t)
256
257 (defsubst elmo-maildir-delete-mark-msgs (folder locs mark)
258   (dolist (loc locs)
259     (elmo-maildir-delete-mark
260      (elmo-maildir-message-file-name folder loc)
261      mark))
262   t)
263
264 (luna-define-method elmo-map-folder-mark-as-important ((folder elmo-maildir-folder)
265                                                        locs)
266   (elmo-maildir-set-mark-msgs folder locs ?F))
267   
268 (luna-define-method elmo-map-folder-unmark-important ((folder elmo-maildir-folder)
269                                                       locs)
270   (elmo-maildir-delete-mark-msgs folder locs ?F))
271
272 (luna-define-method elmo-map-folder-mark-as-read ((folder elmo-maildir-folder)
273                                                   locs)
274   (elmo-maildir-set-mark-msgs folder locs ?S))
275
276 (luna-define-method elmo-map-folder-unmark-read ((folder elmo-maildir-folder)
277                                                  locs)
278   (elmo-maildir-delete-mark-msgs folder locs ?S))
279
280 (luna-define-method elmo-map-folder-mark-as-answered ((folder
281                                                        elmo-maildir-folder)
282                                                       locs)
283   (elmo-maildir-set-mark-msgs folder locs ?R))
284
285 (luna-define-method elmo-map-folder-unmark-answered ((folder
286                                                       elmo-maildir-folder)
287                                                      locs)
288   (elmo-maildir-delete-mark-msgs folder locs ?R))
289
290 (luna-define-method elmo-folder-list-subfolders
291   ((folder elmo-maildir-folder) &optional one-level)
292   (let ((prefix (concat (elmo-folder-name-internal folder)
293                         (unless (string= (elmo-folder-prefix-internal folder)
294                                          (elmo-folder-name-internal folder))
295                           elmo-path-sep)))
296         (elmo-list-subdirectories-ignore-regexp
297          "^\\(\\.\\.?\\|cur\\|tmp\\|new\\)$")
298         elmo-have-link-count)
299     (append
300      (list (elmo-folder-name-internal folder))
301      (elmo-mapcar-list-of-list
302       (function (lambda (x) (concat prefix x)))
303       (elmo-list-subdirectories
304        (elmo-maildir-folder-directory-internal folder)
305        ""
306        one-level)))))
307
308 (defvar elmo-maildir-sequence-number-internal 0)
309
310 (static-cond
311  ((>= emacs-major-version 19)
312   (defun elmo-maildir-make-unique-string ()
313     "This function generates a string that can be used as a unique
314 file name for maildir directories."
315      (let ((cur-time (current-time)))
316        (format "%.0f.%d_%d.%s"
317               (+ (* (car cur-time)
318                     (float 65536)) (cadr cur-time))
319               (emacs-pid)
320               (incf elmo-maildir-sequence-number-internal)
321               (system-name)))))
322  ((eq emacs-major-version 18)
323   ;; A fake function for v18
324   (defun elmo-maildir-make-unique-string ()
325     "This function generates a string that can be used as a unique
326 file name for maildir directories."
327     (unless (fboundp 'float-to-string)
328       (load-library "float"))
329     (let ((time (current-time)))
330       (format "%s%d.%d.%s"
331               (substring
332                (float-to-string
333                 (f+ (f* (f (car time))
334                         (f 65536))
335                     (f (cadr time))))
336                0 5)
337               (cadr time)
338               (% (abs (random t)) 10000); dummy pid
339               (system-name))))))
340
341 (defun elmo-maildir-temporal-filename (basedir)
342   (let ((filename (expand-file-name
343                    (concat "tmp/" (elmo-maildir-make-unique-string))
344                    basedir)))
345     (unless (file-exists-p (file-name-directory filename))
346       (make-directory (file-name-directory filename)))
347     (while (file-exists-p filename)
348 ;;; I don't want to wait.
349 ;;;   (sleep-for 2)
350       (setq filename
351             (expand-file-name
352              (concat "tmp/" (elmo-maildir-make-unique-string))
353              basedir)))
354     filename))
355
356 (luna-define-method elmo-folder-append-buffer ((folder elmo-maildir-folder)
357                                                unread &optional number)
358   (let ((basedir (elmo-maildir-folder-directory-internal folder))
359         (src-buf (current-buffer))
360         dst-buf filename)
361     (condition-case nil
362         (with-temp-buffer
363           (setq filename (elmo-maildir-temporal-filename basedir))
364           (setq dst-buf (current-buffer))
365           (with-current-buffer src-buf
366             (copy-to-buffer dst-buf (point-min) (point-max)))
367           (as-binary-output-file
368            (write-region (point-min) (point-max) filename nil 'no-msg))
369           ;; add link from new.
370           (elmo-add-name-to-file
371            filename
372            (expand-file-name
373             (concat "new/" (file-name-nondirectory filename))
374             basedir))
375           t)
376       ;; If an error occured, return nil.
377       (error))))
378
379 (luna-define-method elmo-folder-message-file-p ((folder elmo-maildir-folder))
380   t)
381
382 (luna-define-method elmo-message-file-name ((folder elmo-maildir-folder)
383                                             number)
384   (elmo-maildir-message-file-name
385    folder
386    (elmo-map-message-location folder number)))
387
388 (luna-define-method elmo-folder-message-make-temp-file-p
389   ((folder elmo-maildir-folder))
390   t)
391
392 (luna-define-method elmo-folder-message-make-temp-files ((folder
393                                                           elmo-maildir-folder)
394                                                          numbers
395                                                          &optional
396                                                          start-number)
397   (let ((temp-dir (elmo-folder-make-temporary-directory folder))
398         (cur-number (if start-number 0)))
399     (dolist (number numbers)
400       (elmo-copy-file
401        (elmo-message-file-name folder number)
402        (expand-file-name
403         (int-to-string (if start-number (incf cur-number) number))
404         temp-dir)))
405     temp-dir))
406
407 (luna-define-method elmo-folder-append-messages :around
408   ((folder elmo-maildir-folder)
409    src-folder numbers &optional same-number)
410   (if (elmo-folder-message-file-p src-folder)
411       (let ((dir (elmo-maildir-folder-directory-internal folder))
412             (succeeds numbers)
413             filename)
414         (dolist (number numbers)
415           (setq filename (elmo-maildir-temporal-filename dir))
416           (elmo-copy-file
417            (elmo-message-file-name src-folder number)
418            filename)
419           (elmo-add-name-to-file
420            filename
421            (expand-file-name
422             (concat "new/" (file-name-nondirectory filename))
423             dir))
424           (elmo-progress-notify 'elmo-folder-move-messages))
425         succeeds)
426     (luna-call-next-method)))
427
428 (luna-define-method elmo-map-folder-delete-messages
429   ((folder elmo-maildir-folder) locations)
430   (let (file)
431     (dolist (location locations)
432       (setq file (elmo-maildir-message-file-name folder location))
433       (if (and file
434                (file-writable-p file)
435                (not (file-directory-p file)))
436           (delete-file file)))))
437
438 (luna-define-method elmo-map-message-fetch ((folder elmo-maildir-folder)
439                                             location strategy
440                                             &optional section unseen)
441   (let ((file (elmo-maildir-message-file-name folder location)))
442     (when (file-exists-p file)
443       (insert-file-contents-as-binary file))))
444
445 (luna-define-method elmo-folder-exists-p ((folder elmo-maildir-folder))
446   (let ((basedir (elmo-maildir-folder-directory-internal folder)))
447     (and (file-directory-p (expand-file-name "new" basedir))
448          (file-directory-p (expand-file-name "cur" basedir))
449          (file-directory-p (expand-file-name "tmp" basedir)))))
450
451 (luna-define-method elmo-folder-diff ((folder elmo-maildir-folder)
452                                       &optional numbers)
453   (let* ((dir (elmo-maildir-folder-directory-internal folder))
454          (new-len (length (car (elmo-maildir-list-location dir "new"))))
455          (cur-len (length (car (elmo-maildir-list-location dir "cur")))))
456     (cons new-len (+ new-len cur-len))))
457
458 (luna-define-method elmo-folder-creatable-p ((folder elmo-maildir-folder))
459   t)
460
461 (luna-define-method elmo-folder-writable-p ((folder elmo-maildir-folder))
462   t)
463
464 (luna-define-method elmo-folder-create ((folder elmo-maildir-folder))
465   (let ((basedir (elmo-maildir-folder-directory-internal folder)))
466     (condition-case nil
467         (progn
468           (dolist (dir '("." "new" "cur" "tmp"))
469             (setq dir (expand-file-name dir basedir))
470             (or (file-directory-p dir)
471                 (progn
472                   (elmo-make-directory dir)
473                   (set-file-modes dir 448))))
474           t)
475       (error))))
476
477 (luna-define-method elmo-folder-delete :before ((folder elmo-maildir-folder))
478   (let ((basedir (elmo-maildir-folder-directory-internal folder)))
479     (condition-case nil
480         (let ((tmp-files (directory-files
481                           (expand-file-name "tmp" basedir)
482                           t "[^.].*")))
483           ;; Delete files in tmp.
484           (dolist (file tmp-files)
485             (delete-file file))
486           (dolist (dir '("new" "cur" "tmp" "."))
487             (setq dir (expand-file-name dir basedir))
488             (if (not (file-directory-p dir))
489                 (error nil)
490               (elmo-delete-directory dir t)))
491           t)
492       (error nil))))
493
494 (require 'product)
495 (product-provide (provide 'elmo-maildir) (require 'elmo-version))
496
497 ;;; elmo-maildir.el ends here