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