37ed43a89abfe6e0253c5f8ad5a2500d4da0ccd0
[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-folder-list-subfolders
281   ((folder elmo-maildir-folder) &optional one-level)
282   (let ((prefix (concat (elmo-folder-name-internal folder)
283                         (unless (string= (elmo-folder-prefix-internal folder)
284                                          (elmo-folder-name-internal folder))
285                           elmo-path-sep)))
286         (elmo-list-subdirectories-ignore-regexp
287          "^\\(\\.\\.?\\|cur\\|tmp\\|new\\)$")
288         elmo-have-link-count)
289     (append
290      (list (elmo-folder-name-internal folder))
291      (elmo-mapcar-list-of-list
292       (function (lambda (x) (concat prefix x)))
293       (elmo-list-subdirectories
294        (elmo-maildir-folder-directory-internal folder)
295        ""
296        one-level)))))
297
298 (defvar elmo-maildir-sequence-number-internal 0)
299
300 (static-cond
301  ((>= emacs-major-version 19)
302   (defun elmo-maildir-make-unique-string ()
303     "This function generates a string that can be used as a unique
304 file name for maildir directories."
305      (let ((cur-time (current-time)))
306        (format "%.0f.%d_%d.%s"
307               (+ (* (car cur-time)
308                     (float 65536)) (cadr cur-time))
309               (emacs-pid)
310               (incf elmo-maildir-sequence-number-internal)
311               (system-name)))))
312  ((eq emacs-major-version 18)
313   ;; A fake function for v18
314   (defun elmo-maildir-make-unique-string ()
315     "This function generates a string that can be used as a unique
316 file name for maildir directories."
317     (unless (fboundp 'float-to-string)
318       (load-library "float"))
319     (let ((time (current-time)))
320       (format "%s%d.%d.%s"
321               (substring
322                (float-to-string
323                 (f+ (f* (f (car time))
324                         (f 65536))
325                     (f (cadr time))))
326                0 5)
327               (cadr time)
328               (% (abs (random t)) 10000); dummy pid
329               (system-name))))))
330
331 (defun elmo-maildir-temporal-filename (basedir)
332   (let ((filename (expand-file-name
333                    (concat "tmp/" (elmo-maildir-make-unique-string))
334                    basedir)))
335     (unless (file-exists-p (file-name-directory filename))
336       (make-directory (file-name-directory filename)))
337     (while (file-exists-p filename)
338 ;;; I don't want to wait.
339 ;;;   (sleep-for 2)
340       (setq filename
341             (expand-file-name
342              (concat "tmp/" (elmo-maildir-make-unique-string))
343              basedir)))
344     filename))
345
346 (luna-define-method elmo-folder-append-buffer ((folder elmo-maildir-folder)
347                                                unread &optional number)
348   (let ((basedir (elmo-maildir-folder-directory-internal folder))
349         (src-buf (current-buffer))
350         dst-buf filename)
351     (condition-case nil
352         (with-temp-buffer
353           (setq filename (elmo-maildir-temporal-filename basedir))
354           (setq dst-buf (current-buffer))
355           (with-current-buffer src-buf
356             (copy-to-buffer dst-buf (point-min) (point-max)))
357           (as-binary-output-file
358            (write-region (point-min) (point-max) filename nil 'no-msg))
359           ;; add link from new.
360           (elmo-add-name-to-file
361            filename
362            (expand-file-name
363             (concat "new/" (file-name-nondirectory filename))
364             basedir))
365           t)
366       ;; If an error occured, return nil.
367       (error))))
368
369 (luna-define-method elmo-folder-message-file-p ((folder elmo-maildir-folder))
370   t)
371
372 (luna-define-method elmo-message-file-name ((folder elmo-maildir-folder)
373                                             number)
374   (elmo-maildir-message-file-name
375    folder
376    (elmo-map-message-location folder number)))
377
378 (luna-define-method elmo-folder-message-make-temp-file-p
379   ((folder elmo-maildir-folder))
380   t)
381
382 (luna-define-method elmo-folder-message-make-temp-files ((folder
383                                                           elmo-maildir-folder)
384                                                          numbers
385                                                          &optional
386                                                          start-number)
387   (let ((temp-dir (elmo-folder-make-temporary-directory folder))
388         (cur-number (if start-number 0)))
389     (dolist (number numbers)
390       (elmo-copy-file
391        (elmo-message-file-name folder number)
392        (expand-file-name
393         (int-to-string (if start-number (incf cur-number) number))
394         temp-dir)))
395     temp-dir))
396
397 (luna-define-method elmo-folder-append-messages :around
398   ((folder elmo-maildir-folder)
399    src-folder numbers &optional same-number)
400   (if (elmo-folder-message-file-p src-folder)
401       (let ((dir (elmo-maildir-folder-directory-internal folder))
402             (succeeds numbers)
403             filename)
404         (dolist (number numbers)
405           (setq filename (elmo-maildir-temporal-filename dir))
406           (elmo-copy-file
407            (elmo-message-file-name src-folder number)
408            filename)
409           (elmo-add-name-to-file
410            filename
411            (expand-file-name
412             (concat "new/" (file-name-nondirectory filename))
413             dir))
414           (elmo-progress-notify 'elmo-folder-move-messages))
415         succeeds)
416     (luna-call-next-method)))
417
418 (luna-define-method elmo-map-folder-delete-messages
419   ((folder elmo-maildir-folder) locations)
420   (let (file)
421     (dolist (location locations)
422       (setq file (elmo-maildir-message-file-name folder location))
423       (if (and file
424                (file-writable-p file)
425                (not (file-directory-p file)))
426           (delete-file file)))))
427
428 (luna-define-method elmo-map-message-fetch ((folder elmo-maildir-folder)
429                                             location strategy
430                                             &optional section unseen)
431   (let ((file (elmo-maildir-message-file-name folder location)))
432     (when (file-exists-p file)
433       (insert-file-contents-as-binary file))))
434
435 (luna-define-method elmo-folder-exists-p ((folder elmo-maildir-folder))
436   (let ((basedir (elmo-maildir-folder-directory-internal folder)))
437     (and (file-directory-p (expand-file-name "new" basedir))
438          (file-directory-p (expand-file-name "cur" basedir))
439          (file-directory-p (expand-file-name "tmp" basedir)))))
440
441 (luna-define-method elmo-folder-diff ((folder elmo-maildir-folder)
442                                       &optional numbers)
443   (let* ((dir (elmo-maildir-folder-directory-internal folder))
444          (new-len (length (car (elmo-maildir-list-location dir "new"))))
445          (cur-len (length (car (elmo-maildir-list-location dir "cur")))))
446     (cons new-len (+ new-len cur-len))))
447
448 (luna-define-method elmo-folder-creatable-p ((folder elmo-maildir-folder))
449   t)
450
451 (luna-define-method elmo-folder-writable-p ((folder elmo-maildir-folder))
452   t)
453
454 (luna-define-method elmo-folder-create ((folder elmo-maildir-folder))
455   (let ((basedir (elmo-maildir-folder-directory-internal folder)))
456     (condition-case nil
457         (progn
458           (dolist (dir '("." "new" "cur" "tmp"))
459             (setq dir (expand-file-name dir basedir))
460             (or (file-directory-p dir)
461                 (progn
462                   (elmo-make-directory dir)
463                   (set-file-modes dir 448))))
464           t)
465       (error))))
466
467 (luna-define-method elmo-folder-delete :before ((folder elmo-maildir-folder))
468   (let ((basedir (elmo-maildir-folder-directory-internal folder)))
469     (condition-case nil
470         (let ((tmp-files (directory-files
471                           (expand-file-name "tmp" basedir)
472                           t "[^.].*")))
473           ;; Delete files in tmp.
474           (dolist (file tmp-files)
475             (delete-file file))
476           (dolist (dir '("new" "cur" "tmp" "."))
477             (setq dir (expand-file-name dir basedir))
478             (if (not (file-directory-p dir))
479                 (error nil)
480               (elmo-delete-directory dir t)))
481           t)
482       (error nil))))
483
484 (require 'product)
485 (product-provide (provide 'elmo-maildir) (require 'elmo-version))
486
487 ;;; elmo-maildir.el ends here