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