* elmo-util.el (elmo-list-diff): Reverse order of result lists.
[elisp/wanderlust.git] / elmo / elmo-util.el
1 ;;; elmo-util.el --- Utilities 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 (require 'elmo-vars)
34 (require 'elmo-date)
35 (require 'mcharset)
36 (require 'pces)
37 (require 'std11)
38 (require 'eword-decode)
39 (require 'utf7)
40 (require 'poem)
41 (require 'emu)
42
43 (eval-and-compile
44   (autoload 'md5 "md5"))
45
46 (defvar elmo-work-buf-name " *elmo work*")
47 (defvar elmo-temp-buf-name " *elmo temp*")
48
49 (or (boundp 'default-enable-multibyte-characters)
50     (defvar default-enable-multibyte-characters (featurep 'mule)
51       "The mock variable except for Emacs 20."))
52
53 (defun elmo-base64-encode-string (string &optional no-line-break))
54 (defun elmo-base64-decode-string (string))
55
56 ;; base64 encoding/decoding
57 (require 'mel)
58 (fset 'elmo-base64-encode-string
59       (mel-find-function 'mime-encode-string "base64"))
60 (fset 'elmo-base64-decode-string
61       (mel-find-function 'mime-decode-string "base64"))
62
63 ;; Any Emacsen may have add-name-to-file(), because loadup.el requires it. :-p
64 ;; Check make-symbolic-link() instead.  -- 981002 by Fuji
65 (if (fboundp 'make-symbolic-link)  ;; xxx
66     (defalias 'elmo-add-name-to-file 'add-name-to-file)
67   (defun elmo-add-name-to-file
68     (filename newname &optional ok-if-already-exists)
69     (copy-file filename newname ok-if-already-exists t)))
70
71 (defmacro elmo-set-work-buf (&rest body)
72   "Execute BODY on work buffer.  Work buffer remains."
73   (` (save-excursion
74        (set-buffer (get-buffer-create elmo-work-buf-name))
75        (set-buffer-multibyte default-enable-multibyte-characters)
76        (erase-buffer)
77        (,@ body))))
78
79 (put 'elmo-set-work-buf 'lisp-indent-function 0)
80 (def-edebug-spec elmo-set-work-buf t)
81
82 (defmacro elmo-bind-directory (dir &rest body)
83   "Set current directory DIR and execute BODY."
84   (` (let ((default-directory (file-name-as-directory (, dir))))
85        (,@ body))))
86
87 (put 'elmo-bind-directory 'lisp-indent-function 1)
88 (def-edebug-spec elmo-bind-directory
89   (form &rest form))
90
91 (defconst elmo-multibyte-buffer-name " *elmo-multibyte-buffer*")
92
93 (defmacro elmo-with-enable-multibyte (&rest body)
94   "Evaluate BODY with `enable-multibyte-character' as non-nil."
95   `(let ((default-enable-multibyte-characters t))
96      (with-current-buffer (get-buffer-create elmo-multibyte-buffer-name)
97        ,@body)))
98
99 (put 'elmo-with-enable-multibyte 'lisp-indent-function 0)
100 (def-edebug-spec elmo-with-enable-multibyte t)
101
102 (defun elmo-object-load (filename &optional mime-charset no-err)
103   "Load OBJECT from the file specified by FILENAME.
104 File content is decoded with MIME-CHARSET."
105     (if (not (file-readable-p filename))
106         nil
107       (elmo-set-work-buf
108        (as-binary-input-file
109         (insert-file-contents filename))
110        (when mime-charset
111          (set-buffer-multibyte default-enable-multibyte-characters)
112          (decode-mime-charset-region (point-min) (point-max) mime-charset))
113        (condition-case nil
114            (read (current-buffer))
115          (error (unless no-err
116                   (message "Warning: Loading object from %s failed."
117                            filename)
118                   (elmo-object-save filename nil))
119                 nil)))))
120
121 (defsubst elmo-save-buffer (filename &optional mime-charset)
122   "Save current buffer to the file specified by FILENAME.
123 Directory of the file is created if it doesn't exist.
124 File content is encoded with MIME-CHARSET."
125   (let ((dir (directory-file-name (file-name-directory filename))))
126     (if (file-directory-p dir)
127         () ; ok.
128       (unless (file-exists-p dir)
129         (elmo-make-directory dir)))
130     (if (file-writable-p filename)
131         (progn
132           (when mime-charset
133 ;;;         (set-buffer-multibyte default-enable-multibyte-characters)
134             (encode-mime-charset-region (point-min) (point-max) mime-charset))
135           (as-binary-output-file
136            (write-region (point-min) (point-max) filename nil 'no-msg)))
137       (message "%s is not writable." filename))))
138
139 (defun elmo-object-save (filename object &optional mime-charset)
140   "Save OBJECT to the file specified by FILENAME.
141 Directory of the file is created if it doesn't exist.
142 File content is encoded with MIME-CHARSET."
143   (elmo-set-work-buf
144    (let (print-length print-level)
145      (prin1 object (current-buffer)))
146 ;;;(princ "\n" (current-buffer))
147    (elmo-save-buffer filename mime-charset)))
148
149 ;;; Search Condition
150
151 (defconst elmo-condition-atom-regexp "[^/ \")|&]*")
152
153 (defsubst elmo-condition-parse-error ()
154   (error "Syntax error in '%s'" (buffer-string)))
155
156 (defun elmo-parse-search-condition (condition)
157   "Parse CONDITION.
158 Return value is a cons cell of (STRUCTURE . REST)"
159   (with-temp-buffer
160     (insert condition)
161     (goto-char (point-min))
162     (cons (elmo-condition-parse) (buffer-substring (point) (point-max)))))
163
164 ;; condition    ::= or-expr
165 (defun elmo-condition-parse ()
166   (or (elmo-condition-parse-or-expr)
167       (elmo-condition-parse-error)))
168
169 ;; or-expr      ::= and-expr /
170 ;;                  and-expr "|" or-expr
171 (defun elmo-condition-parse-or-expr ()
172   (let ((left (elmo-condition-parse-and-expr)))
173     (if (looking-at "| *")
174         (progn
175           (goto-char (match-end 0))
176           (list 'or left (elmo-condition-parse-or-expr)))
177       left)))
178
179 ;; and-expr     ::= primitive /
180 ;;                  primitive "&" and-expr
181 (defun elmo-condition-parse-and-expr ()
182   (let ((left (elmo-condition-parse-primitive)))
183     (if (looking-at "& *")
184         (progn
185           (goto-char (match-end 0))
186           (list 'and left (elmo-condition-parse-and-expr)))
187       left)))
188
189 ;; primitive    ::= "(" expr ")" /
190 ;;                  ["!"] search-key SPACE* ":" SPACE* search-value
191 (defun elmo-condition-parse-primitive ()
192   (cond
193    ((looking-at "( *")
194     (goto-char (match-end 0))
195     (prog1 (elmo-condition-parse)
196       (unless (looking-at ") *")
197         (elmo-condition-parse-error))
198       (goto-char (match-end 0))))
199 ;; search-key   ::= [A-Za-z-]+
200 ;;                 ;; "since" / "before" / "last" / "first" /
201 ;;                 ;; "body" / "flag" / field-name
202    ((looking-at "\\(!\\)? *\\([A-Za-z-]+\\) *: *")
203     (goto-char (match-end 0))
204     (let ((search-key (vector
205                        (if (match-beginning 1) 'unmatch 'match)
206                        (downcase (elmo-match-buffer 2))
207                        (elmo-condition-parse-search-value))))
208       ;; syntax sugar.
209       (if (string= (aref search-key 1) "tocc")
210           (if (eq (aref search-key 0) 'match)
211               (list 'or
212                     (vector 'match "to" (aref search-key 2))
213                     (vector 'match "cc" (aref search-key 2)))
214             (list 'and
215                   (vector 'unmatch "to" (aref search-key 2))
216                   (vector 'unmatch "cc" (aref search-key 2))))
217         search-key)))))
218
219 ;; search-value ::= quoted / time / number / atom
220 ;; quoted       ::= <elisp string expression>
221 ;; time         ::= "yesterday" / "lastweek" / "lastmonth" / "lastyear" /
222 ;;                   number SPACE* "daysago" /
223 ;;                   number "-" month "-" number  ; ex. 10-May-2000
224 ;;                   number "-" number "-" number  ; ex. 2000-05-10
225 ;; number       ::= [0-9]+
226 ;; month        ::= "Jan" / "Feb" / "Mar" / "Apr" / "May" / "Jun" /
227 ;;                  "Jul" / "Aug" / "Sep" / "Oct" / "Nov" / "Dec"
228 ;; atom         ::= ATOM_CHARS*
229 ;; SPACE        ::= <ascii space character, 0x20>
230 ;; ATOM_CHARS   ::= <any character except specials>
231 ;; specials     ::= SPACE / <"> / </> / <)> / <|> / <&>
232 ;;                  ;; These characters should be quoted.
233 (defun elmo-condition-parse-search-value ()
234   (cond
235    ((looking-at "\"")
236     (read (current-buffer)))
237    ((or (looking-at elmo-condition-atom-regexp)
238         (looking-at "yesterday") (looking-at "lastweek")
239         (looking-at "lastmonth") (looking-at "lastyear")
240         (looking-at "[0-9]+ *daysago")
241         (looking-at "[0-9]+-[A-Za-z]+-[0-9]+")
242         (looking-at "[0-9]+-[0-9]+-[0-9]+")
243         (looking-at "[0-9]+"))
244     (prog1 (elmo-match-buffer 0)
245       (goto-char (match-end 0))))
246    (t (error "Syntax error '%s'" (buffer-string)))))
247
248 ;;;
249 (defsubst elmo-buffer-replace (regexp &optional newtext)
250   (goto-char (point-min))
251   (while (re-search-forward regexp nil t)
252     (replace-match (or newtext ""))))
253
254 (defsubst elmo-delete-char (char string &optional unibyte)
255   (save-match-data
256     (elmo-set-work-buf
257      (let ((coding-system-for-read 'no-conversion)
258            (coding-system-for-write 'no-conversion))
259        (if unibyte (set-buffer-multibyte nil))
260        (insert string)
261        (goto-char (point-min))
262        (while (search-forward (char-to-string char) nil t)
263          (replace-match ""))
264        (buffer-string)))))
265
266 (defsubst elmo-delete-cr-buffer ()
267   "Delete CR from buffer."
268   (save-excursion
269     (goto-char (point-min))
270     (while (search-forward "\r\n" nil t)
271       (replace-match "\n")) ))
272
273 (defsubst elmo-delete-cr-get-content-type ()
274   (save-excursion
275     (goto-char (point-min))
276     (while (search-forward "\r\n" nil t)
277       (replace-match "\n"))
278     (goto-char (point-min))
279     (or (std11-field-body "content-type")
280         t)))
281
282 (defun elmo-delete-cr (string)
283   (save-match-data
284     (elmo-set-work-buf
285      (insert string)
286      (goto-char (point-min))
287      (while (search-forward "\r\n" nil t)
288        (replace-match "\n"))
289      (buffer-string))))
290
291 (defun elmo-last (list)
292   (and list (nth (1- (length list)) list)))
293
294 (defun elmo-set-list (vars vals)
295   (while vars
296     (when (car vars)
297       (set (car vars) (car vals)))
298     (setq vars (cdr vars)
299           vals (cdr vals))))
300
301 (defun elmo-uniq-list (lst &optional delete-function)
302   "Distractively uniqfy elements of LST."
303   (setq delete-function (or delete-function #'delete))
304   (let ((tmp lst))
305     (while tmp
306       (setq tmp
307             (setcdr tmp
308                     (and (cdr tmp)
309                          (funcall delete-function
310                                   (car tmp)
311                                   (cdr tmp)))))))
312   lst)
313
314 (defun elmo-uniq-sorted-list (list &optional equal-function)
315   "Distractively uniqfy elements of sorted LIST."
316   (setq equal-function (or equal-function #'equal))
317   (let ((list list))
318     (while list
319       (while (funcall equal-function (car list) (cadr list))
320         (setcdr list (cddr list)))
321       (setq list (cdr list))))
322   list)
323
324 (defun elmo-list-insert (list element after)
325   (let* ((match (memq after list))
326          (rest (and match (cdr (memq after list)))))
327     (if match
328         (progn
329           (setcdr match (list element))
330           (nconc list rest))
331       (nconc list (list element)))))
332
333 (defun elmo-get-file-string (filename &optional remove-final-newline)
334   (elmo-set-work-buf
335    (let (insert-file-contents-pre-hook   ; To avoid autoconv-xmas...
336          insert-file-contents-post-hook)
337      (when (file-exists-p filename)
338        (if filename
339            (as-binary-input-file (insert-file-contents filename)))
340        (when (and remove-final-newline
341                   (> (buffer-size) 0)
342                   (= (char-after (1- (point-max))) ?\n))
343          (goto-char (point-max))
344          (delete-backward-char 1))
345        (buffer-string)))))
346
347 (defun elmo-save-string (string filename)
348   (if string
349       (elmo-set-work-buf
350        (as-binary-output-file
351         (insert string)
352         (write-region (point-min) (point-max)
353                       filename nil 'no-msg))
354        )))
355
356 (defun elmo-max-of-list (nlist)
357   (let ((l nlist)
358         (max-num 0))
359     (while l
360       (if (< max-num (car l))
361           (setq max-num (car l)))
362       (setq l (cdr l)))
363     max-num))
364
365 (defun elmo-concat-path (path filename)
366   (if (not (string= path ""))
367       (elmo-replace-in-string
368        (if (string= elmo-path-sep (substring path (- (length path) 1)))
369            (concat path filename)
370          (concat path elmo-path-sep filename))
371        (concat (regexp-quote elmo-path-sep)(regexp-quote elmo-path-sep))
372        elmo-path-sep)
373     filename))
374
375 (defvar elmo-passwd-alist nil)
376
377 (defun elmo-passwd-alist-load ()
378   (with-temp-buffer
379     (let ((filename (expand-file-name elmo-passwd-alist-file-name
380                                       elmo-msgdb-directory))
381           insert-file-contents-pre-hook ; To avoid autoconv-xmas...
382           insert-file-contents-post-hook
383           ret-val)
384       (if (not (file-readable-p filename))
385           ()
386         (insert-file-contents filename)
387         (condition-case nil
388             (read (current-buffer))
389           (error nil nil))))))
390
391 (defun elmo-passwd-alist-clear ()
392   "Clear password cache."
393   (interactive)
394   (dolist (pair elmo-passwd-alist)
395     (when (stringp (cdr-safe pair))
396       (fillarray (cdr pair) 0)))
397   (setq elmo-passwd-alist nil))
398
399 (defun elmo-passwd-alist-save ()
400   "Save password into file."
401   (interactive)
402   (with-temp-buffer
403     (let ((filename (expand-file-name elmo-passwd-alist-file-name
404                                       elmo-msgdb-directory))
405           print-length print-level)
406       (prin1 elmo-passwd-alist (current-buffer))
407       (princ "\n" (current-buffer))
408 ;;;   (if (and (file-exists-p filename)
409 ;;;            (not (equal 384 (file-modes filename))))
410 ;;;       (error "%s is not safe.chmod 600 %s!" filename filename))
411       (if (file-writable-p filename)
412           (progn
413             (write-region (point-min) (point-max)
414                           filename nil 'no-msg)
415             (set-file-modes filename 384))
416         (message "%s is not writable." filename)))))
417
418 (defun elmo-get-passwd (key)
419   "Get password from password pool."
420   (let (pair pass)
421     (if (not elmo-passwd-alist)
422         (setq elmo-passwd-alist (elmo-passwd-alist-load)))
423     (setq pair (assoc key elmo-passwd-alist))
424     (if pair
425         (elmo-base64-decode-string (cdr pair))
426       (setq pass (elmo-read-passwd (format "Password for %s: "
427                                            key) t))
428       (setq elmo-passwd-alist
429             (append elmo-passwd-alist
430                     (list (cons key
431                                 (elmo-base64-encode-string pass)))))
432       (if elmo-passwd-life-time
433           (run-with-timer elmo-passwd-life-time nil
434                           (` (lambda () (elmo-remove-passwd (, key))))))
435       pass)))
436
437 (defun elmo-remove-passwd (key)
438   "Remove password from password pool (for failure)."
439   (let (pass-cons)
440     (while (setq pass-cons (assoc key elmo-passwd-alist))
441       (unwind-protect
442           (fillarray (cdr pass-cons) 0)
443         (setq elmo-passwd-alist
444               (delete pass-cons elmo-passwd-alist))))))
445
446 (defmacro elmo-read-char-exclusive ()
447   (cond ((featurep 'xemacs)
448          '(let ((table (quote ((backspace . ?\C-h) (delete . ?\C-?)
449                                (left . ?\C-h))))
450                 event key)
451             (while (not
452                     (and
453                      (key-press-event-p (setq event (next-command-event)))
454                      (setq key (or (event-to-character event)
455                                    (cdr (assq (event-key event) table)))))))
456             key))
457         ((fboundp 'read-char-exclusive)
458          '(read-char-exclusive))
459         (t
460          '(read-char))))
461
462 (defun elmo-read-passwd (prompt &optional stars)
463   "Read a single line of text from user without echoing, and return it."
464   (let ((ans "")
465         (c 0)
466         (echo-keystrokes 0)
467         (cursor-in-echo-area t)
468         (log-message-max-size 0)
469         message-log-max done msg truncate)
470     (while (not done)
471       (if (or (not stars) (string= "" ans))
472           (setq msg prompt)
473         (setq msg (concat prompt (make-string (length ans) ?.)))
474         (setq truncate
475               (1+ (- (length msg) (window-width (minibuffer-window)))))
476         (and (> truncate 0)
477              (setq msg (concat "$" (substring msg (1+ truncate))))))
478       (message "%s" msg)
479       (setq c (elmo-read-char-exclusive))
480       (cond ((= c ?\C-g)
481              (setq quit-flag t
482                    done t))
483             ((or (= c ?\r) (= c ?\n) (= c ?\e))
484              (setq done t))
485             ((= c ?\C-u)
486              (setq ans ""))
487             ((and (/= c ?\b) (/= c ?\177))
488              (setq ans (concat ans (char-to-string c))))
489             ((> (length ans) 0)
490              (setq ans (substring ans 0 -1)))))
491     (if quit-flag
492         (prog1
493             (setq quit-flag nil)
494           (message "Quit")
495           (beep t))
496       (message "")
497       ans)))
498
499 (defun elmo-string-to-list (string)
500   (elmo-set-work-buf
501    (insert string)
502    (goto-char (point-min))
503    (insert "(")
504    (goto-char (point-max))
505    (insert ")")
506    (goto-char (point-min))
507    (read (current-buffer))))
508
509 (defun elmo-list-to-string (list)
510   (let ((tlist list)
511         str)
512     (if (listp tlist)
513         (progn
514           (setq str "(")
515           (while (car tlist)
516             (setq str
517                   (concat str
518                           (if (symbolp (car tlist))
519                               (symbol-name (car tlist))
520                             (car tlist))))
521             (if (cdr tlist)
522                 (setq str
523                       (concat str " ")))
524             (setq tlist (cdr tlist)))
525           (setq str
526                 (concat str ")")))
527       (setq str
528             (if (symbolp tlist)
529                 (symbol-name tlist)
530               tlist)))
531     str))
532
533
534 (defun elmo-plug-on-by-servers (alist &optional servers)
535   (let ((server-list (or servers elmo-plug-on-servers)))
536     (catch 'plugged
537       (while server-list
538         (if (elmo-plugged-p (car server-list))
539             (throw 'plugged t))
540         (setq server-list (cdr server-list))))))
541
542 (defun elmo-plug-on-by-exclude-servers (alist &optional servers)
543   (let ((server-list (or servers elmo-plug-on-exclude-servers))
544         server other-servers)
545     (while alist
546       (when (and (not (member (setq server (caaar alist)) server-list))
547                  (not (member server other-servers)))
548         (push server other-servers))
549       (setq alist (cdr alist)))
550     (elmo-plug-on-by-servers alist other-servers)))
551
552 (defun elmo-plugged-p (&optional server port stream-type alist label-exp)
553   (let ((alist (or alist elmo-plugged-alist))
554         plugged-info)
555     (cond ((and (not port) (not server))
556            (cond ((eq elmo-plugged-condition 'one)
557                   (if alist
558                       (catch 'plugged
559                         (while alist
560                           (if (nth 2 (car alist))
561                               (throw 'plugged t))
562                           (setq alist (cdr alist))))
563                     elmo-plugged))
564                  ((eq elmo-plugged-condition 'all)
565                   (if alist
566                       (catch 'plugged
567                         (while alist
568                           (if (not (nth 2 (car alist)))
569                               (throw 'plugged nil))
570                           (setq alist (cdr alist)))
571                         t)
572                     elmo-plugged))
573                  ((functionp elmo-plugged-condition)
574                   (funcall elmo-plugged-condition alist))
575                  (t ;; independent
576                   elmo-plugged)))
577           ((not port) ;; server
578            (catch 'plugged
579              (while alist
580                (when (string= server (caaar alist))
581                  (if (nth 2 (car alist))
582                      (throw 'plugged t)))
583                (setq alist (cdr alist)))))
584           (t
585            (setq plugged-info (assoc (list server port stream-type) alist))
586            (if (not plugged-info)
587                ;; add elmo-plugged-alist automatically
588                (progn
589                  (elmo-set-plugged elmo-plugged server port stream-type
590                                    nil nil nil label-exp)
591                  elmo-plugged)
592              (if (and elmo-auto-change-plugged
593                       (> elmo-auto-change-plugged 0)
594                       (nth 3 plugged-info)  ;; time
595                       (elmo-time-expire (nth 3 plugged-info)
596                                         elmo-auto-change-plugged))
597                  t
598                (nth 2 plugged-info)))))))
599
600 (defun elmo-set-plugged (plugged &optional server port stream-type time
601                                  alist label-exp add)
602   (let ((alist (or alist elmo-plugged-alist))
603         label plugged-info)
604     (cond ((and (not port) (not server))
605            (setq elmo-plugged plugged)
606            ;; set plugged all element of elmo-plugged-alist.
607            (while alist
608              (setcdr (cdar alist) (list plugged time))
609              (setq alist (cdr alist))))
610           ((not port)
611            ;; set plugged all port of server
612            (while alist
613              (when (string= server (caaar alist))
614                (setcdr (cdar alist) (list plugged time)))
615              (setq alist (cdr alist))))
616           (t
617            ;; set plugged one port of server
618            (setq plugged-info (assoc (list server port stream-type) alist))
619            (setq label (if label-exp
620                            (eval label-exp)
621                          (nth 1 plugged-info)))
622            (if plugged-info
623                ;; if add is non-nil, don't reset plug state.
624                (unless add
625                  (setcdr plugged-info (list label plugged time)))
626              (setq alist
627                    (setq elmo-plugged-alist
628                          (nconc
629                           elmo-plugged-alist
630                           (list
631                            (list (list server port stream-type)
632                                  label plugged time))))))))
633     alist))
634
635 (defun elmo-delete-plugged (&optional server port alist)
636   (let* ((alist (or alist elmo-plugged-alist))
637          (alist2 alist))
638     (cond ((and (not port) (not server))
639            (setq alist nil))
640           ((not port)
641            ;; delete plugged all port of server
642            (while alist2
643              (when (string= server (caaar alist2))
644                (setq alist (delete (car alist2) alist)))
645              (setq alist2 (cdr alist2))))
646           (t
647            ;; delete plugged one port of server
648            (setq alist
649                  (delete (assoc (cons server port) alist) alist))))
650     alist))
651
652 (defun elmo-disk-usage (path)
653   "Get disk usage (bytes) in PATH."
654   (let ((file-attr
655          (condition-case () (file-attributes path) (error nil))))
656     (if file-attr
657         (if (nth 0 file-attr) ; directory
658             (let ((files (condition-case ()
659                              (directory-files path t "^[^\\.]")
660                            (error nil)))
661                   (result 0.0))
662               ;; (result (nth 7 file-attr))) ... directory size
663               (while files
664                 (setq result (+ result (or (elmo-disk-usage (car files)) 0)))
665                 (setq files (cdr files)))
666               result)
667           (float (nth 7 file-attr)))
668       0)))
669
670 (defun elmo-get-last-accessed-time (path &optional dir)
671   "Return the last accessed time of PATH."
672   (let ((last-accessed (nth 4 (file-attributes (or (and dir
673                                                         (expand-file-name
674                                                          path dir))
675                                                    path)))))
676     (if last-accessed
677         (setq last-accessed (+ (* (nth 0 last-accessed)
678                                   (float 65536)) (nth 1 last-accessed)))
679       0)))
680
681 (defun elmo-get-last-modification-time (path &optional dir)
682   "Return the last accessed time of PATH."
683   (let ((last-modified (nth 5 (file-attributes (or (and dir
684                                                         (expand-file-name
685                                                          path dir))
686                                                    path)))))
687     (setq last-modified (+ (* (nth 0 last-modified)
688                               (float 65536)) (nth 1 last-modified)))))
689
690 (defun elmo-make-directory (path &optional mode)
691   "Create directory recursively."
692   (let ((parent (directory-file-name (file-name-directory path))))
693     (if (null (file-directory-p parent))
694         (elmo-make-directory parent))
695     (make-directory path)
696     (set-file-modes path (or mode
697                              (+ (* 64 7) (* 8 0) 0))))) ; chmod 0700
698
699 (defun elmo-delete-directory (path &optional no-hierarchy)
700   "Delete directory recursively."
701   (if (stringp path) ; nil is not permitted.
702   (let ((dirent (directory-files path))
703         relpath abspath hierarchy)
704     (while dirent
705       (setq relpath (car dirent)
706             dirent (cdr dirent)
707             abspath (expand-file-name relpath path))
708       (when (not (string-match "^\\.\\.?$" relpath))
709         (if (eq (nth 0 (file-attributes abspath)) t)
710             (if no-hierarchy
711                 (setq hierarchy t)
712               (elmo-delete-directory abspath no-hierarchy))
713           (delete-file abspath))))
714     (unless hierarchy
715       (delete-directory path)))))
716
717 (defun elmo-delete-match-files (path regexp &optional remove-if-empty)
718   "Delete directory files specified by PATH.
719 If optional REMOVE-IF-EMPTY is non-nil, delete directory itself if
720 the directory becomes empty after deletion."
721   (when (stringp path) ; nil is not permitted.
722     (dolist (file (directory-files path t regexp))
723       (delete-file file))
724     (if remove-if-empty
725         (ignore-errors
726           (delete-directory path) ; should be removed if empty.
727           ))))
728
729 (defun elmo-list-filter (l1 l2)
730   "Rerurn a list from L2 in which each element is a member of L1."
731   (elmo-delete-if (lambda (x) (not (memq x l1))) l2))
732
733 (defsubst elmo-list-delete-if-smaller (list number)
734   (let ((ret-val (copy-sequence list)))
735     (while list
736       (if (< (car list) number)
737           (setq ret-val (delq (car list) ret-val)))
738       (setq list (cdr list)))
739     ret-val))
740
741 (defun elmo-list-diff (list1 list2)
742   (let ((clist1 (sort (copy-sequence list1) #'<))
743         (clist2 (sort (copy-sequence list2) #'<))
744         list1-only list2-only)
745     (while (or clist1 clist2)
746       (cond
747        ((null clist1)
748         (while clist2
749           (setq list2-only (cons (car clist2) list2-only))
750           (setq clist2 (cdr clist2))))
751        ((null clist2)
752         (while clist1
753           (setq list1-only (cons (car clist1) list1-only))
754           (setq clist1 (cdr clist1))))
755        ((< (car clist1) (car clist2))
756         (while (and clist1 (< (car clist1) (car clist2)))
757           (setq list1-only (cons (car clist1) list1-only))
758           (setq clist1 (cdr clist1))))
759        ((< (car clist2) (car clist1))
760         (while (and clist2 (< (car clist2) (car clist1)))
761           (setq list2-only (cons (car clist2) list2-only))
762           (setq clist2 (cdr clist2))))
763        ((= (car clist1) (car clist2))
764         (setq clist1 (cdr clist1)
765               clist2 (cdr clist2)))))
766     (list (nreverse list1-only) (nreverse list2-only))))
767
768 (defun elmo-list-diff-nonsortable (list1 list2)
769   (let ((clist1 (copy-sequence list1))
770         (clist2 (copy-sequence list2)))
771     (while list2
772       (setq clist1 (delq (car list2) clist1))
773       (setq list2 (cdr list2)))
774     (while list1
775       (setq clist2 (delq (car list1) clist2))
776       (setq list1 (cdr list1)))
777     (list clist1 clist2)))
778
779 (defun elmo-list-bigger-diff (list1 list2 &optional mes)
780   "Returns a list (- +). + is bigger than max of LIST1, in LIST2."
781   (if (null list2)
782       (cons list1  nil)
783     (let* ((l1 list1)
784            (l2 list2)
785            (max-of-l2 (or (nth (max 0 (1- (length l2))) l2) 0))
786            diff1 num i percent
787            )
788       (setq i 0)
789       (setq num (+ (length l1)))
790       (while l1
791         (if (memq (car l1) l2)
792             (if (eq (car l1) (car l2))
793                 (setq l2 (cdr l2))
794               (delq (car l1) l2))
795           (if (> (car l1) max-of-l2)
796               (setq diff1 (nconc diff1 (list (car l1))))))
797         (if mes
798             (progn
799               (setq i (+ i 1))
800               (setq percent (/ (* i 100) num))
801               (if (eq (% percent 5) 0)
802                   (elmo-display-progress
803                    'elmo-list-bigger-diff "%s%d%%" percent mes))))
804         (setq l1 (cdr l1)))
805       (cons diff1 (list l2)))))
806
807 (defmacro elmo-filter-condition-p (filter)
808   `(or (vectorp ,filter) (consp ,filter)))
809
810 (defmacro elmo-filter-type (filter)
811   (` (aref (, filter) 0)))
812
813 (defmacro elmo-filter-key (filter)
814   (` (aref (, filter) 1)))
815
816 (defmacro elmo-filter-value (filter)
817   (` (aref (, filter) 2)))
818
819 (defsubst elmo-buffer-field-primitive-condition-match (condition
820                                                        number
821                                                        number-list)
822   (let (result)
823     (goto-char (point-min))
824     (cond
825      ((string= (elmo-filter-key condition) "last")
826       (setq result (<= (length (memq number number-list))
827                        (string-to-int (elmo-filter-value condition)))))
828      ((string= (elmo-filter-key condition) "first")
829       (setq result (< (- (length number-list)
830                          (length (memq number number-list)))
831                       (string-to-int (elmo-filter-value condition)))))
832      ((string= (elmo-filter-key condition) "since")
833       (let ((field-date (elmo-date-make-sortable-string
834                          (timezone-fix-time
835                           (std11-field-body "date")
836                           (current-time-zone) nil)))
837             (specified-date (elmo-date-make-sortable-string
838                              (elmo-date-get-datevec
839                               (elmo-filter-value condition)))))
840         (setq result
841               (or (string= field-date specified-date)
842                   (string< specified-date field-date)))))
843      ((string= (elmo-filter-key condition) "before")
844       (setq result
845             (string<
846              (elmo-date-make-sortable-string
847               (timezone-fix-time
848                (std11-field-body "date")
849                (current-time-zone) nil))
850              (elmo-date-make-sortable-string
851               (elmo-date-get-datevec
852                (elmo-filter-value condition))))))
853      ((string= (elmo-filter-key condition) "body")
854       (and (re-search-forward "^$" nil t)          ; goto body
855            (setq result (search-forward (elmo-filter-value condition)
856                                         nil t))))
857      (t
858       (dolist (fval (elmo-multiple-field-body (elmo-filter-key condition)))
859         (if (eq (length fval) 0) (setq fval nil))
860         (if fval (setq fval (eword-decode-string fval)))
861         (setq result (or result
862                          (and fval (string-match
863                                     (elmo-filter-value condition) fval)))))))
864     (if (eq (elmo-filter-type condition) 'unmatch)
865         (setq result (not result)))
866     result))
867
868 (defun elmo-condition-in-msgdb-p-internal (condition fields)
869   (cond
870    ((vectorp condition)
871     (if (not (member (elmo-filter-key condition) fields))
872         (throw 'found t)))
873    ((or (eq (car condition) 'and)
874         (eq (car condition) 'or))
875     (elmo-condition-in-msgdb-p-internal (nth 1 condition) fields)
876     (elmo-condition-in-msgdb-p-internal (nth 2 condition) fields))))
877
878 (defun elmo-condition-in-msgdb-p (condition)
879   (not (catch 'found
880          (elmo-condition-in-msgdb-p-internal condition
881                                              (append
882                                               elmo-msgdb-extra-fields
883                                               '("last" "first" "from"
884                                                 "subject" "to" "cc" "since"
885                                                 "before"))))))
886
887 (defun elmo-buffer-field-condition-match (condition number number-list)
888   (cond
889    ((vectorp condition)
890     (elmo-buffer-field-primitive-condition-match
891      condition number number-list))
892    ((eq (car condition) 'and)
893     (and (elmo-buffer-field-condition-match
894           (nth 1 condition) number number-list)
895          (elmo-buffer-field-condition-match
896           (nth 2 condition) number number-list)))
897    ((eq (car condition) 'or)
898     (or (elmo-buffer-field-condition-match
899          (nth 1 condition) number number-list)
900         (elmo-buffer-field-condition-match
901          (nth 2 condition) number number-list)))))
902
903 (defsubst elmo-file-field-primitive-condition-match (file
904                                                      condition
905                                                      number
906                                                      number-list)
907   (let (result)
908     (goto-char (point-min))
909     (cond
910      ((string= (elmo-filter-key condition) "last")
911       (setq result (<= (length (memq number number-list))
912                        (string-to-int (elmo-filter-value condition))))
913       (if (eq (elmo-filter-type condition) 'unmatch)
914           (setq result (not result))))
915      ((string= (elmo-filter-key condition) "first")
916       (setq result (< (- (length number-list)
917                          (length (memq number number-list)))
918                       (string-to-int (elmo-filter-value condition))))
919       (if (eq (elmo-filter-type condition) 'unmatch)
920           (setq result (not result))))
921      (t
922       (elmo-set-work-buf
923        (as-binary-input-file (insert-file-contents file))
924        (set-buffer-multibyte default-enable-multibyte-characters)
925        ;; Should consider charset?
926        (decode-mime-charset-region (point-min)(point-max) elmo-mime-charset)
927        (setq result
928              (elmo-buffer-field-primitive-condition-match
929               condition number number-list)))))
930     result))
931
932 (defun elmo-file-field-condition-match (file condition number number-list)
933   (cond
934    ((vectorp condition)
935     (elmo-file-field-primitive-condition-match
936      file condition number number-list))
937    ((eq (car condition) 'and)
938     (and (elmo-file-field-condition-match
939           file (nth 1 condition) number number-list)
940          (elmo-file-field-condition-match
941           file (nth 2 condition) number number-list)))
942    ((eq (car condition) 'or)
943     (or (elmo-file-field-condition-match
944          file (nth 1 condition) number number-list)
945         (elmo-file-field-condition-match
946          file (nth 2 condition) number number-list)))))
947
948 (defmacro elmo-get-hash-val (string hashtable)
949   (static-if (fboundp 'unintern)
950       `(symbol-value (intern-soft ,string ,hashtable))
951     `(let ((sym (intern-soft ,string ,hashtable)))
952        (and (boundp sym)
953             (symbol-value sym)))))
954
955 (defmacro elmo-set-hash-val (string value hashtable)
956   `(set (intern ,string ,hashtable) ,value))
957
958 (defmacro elmo-clear-hash-val (string hashtable)
959   (static-if (fboundp 'unintern)
960       (list 'unintern string hashtable)
961     (list 'makunbound (list 'intern string hashtable))))
962
963 (defmacro elmo-unintern (string)
964   "`unintern' symbol named STRING,  When can use `unintern'.
965 Emacs 19.28 or earlier does not have `unintern'."
966   (static-if (fboundp 'unintern)
967       (list 'unintern string)))
968
969 (defun elmo-make-hash (&optional hashsize)
970   "Make a new hash table which have HASHSIZE size."
971   (make-vector
972    (if hashsize
973        (max
974         ;; Prime numbers as lengths tend to result in good
975         ;; hashing; lengths one less than a power of two are
976         ;; also good.
977         (min
978          (let ((i 1))
979            (while (< (- i 1) hashsize)
980              (setq i (* 2 i)))
981            (- i 1))
982          elmo-hash-maximum-size)
983         elmo-hash-minimum-size)
984      elmo-hash-minimum-size)
985    0))
986
987 (defsubst elmo-mime-string (string)
988   "Normalize MIME encoded STRING."
989   (and string
990        (elmo-set-work-buf
991         (set-buffer-multibyte default-enable-multibyte-characters)
992         (setq string
993               (encode-mime-charset-string
994                (eword-decode-and-unfold-unstructured-field-body
995                 string)
996                elmo-mime-charset))
997         (set-buffer-multibyte nil)
998         string)))
999
1000 (defsubst elmo-collect-field (beg end downcase-field-name)
1001   (save-excursion
1002     (save-restriction
1003       (narrow-to-region beg end)
1004       (goto-char (point-min))
1005       (let ((regexp (concat "\\(" std11-field-head-regexp "\\)[ \t]*"))
1006             dest name body)
1007         (while (re-search-forward regexp nil t)
1008           (setq name (buffer-substring-no-properties
1009                       (match-beginning 1)(1- (match-end 1))))
1010           (if downcase-field-name
1011               (setq name (downcase name)))
1012           (setq body (buffer-substring-no-properties
1013                       (match-end 0) (std11-field-end)))
1014           (or (assoc name dest)
1015               (setq dest (cons (cons name body) dest))))
1016         dest))))
1017
1018 (defsubst elmo-collect-field-from-string (string downcase-field-name)
1019   (with-temp-buffer
1020     (insert string)
1021     (goto-char (point-min))
1022     (let ((regexp (concat "\\(" std11-field-head-regexp "\\)[ \t]*"))
1023           dest name body)
1024       (while (re-search-forward regexp nil t)
1025         (setq name (buffer-substring-no-properties
1026                     (match-beginning 1)(1- (match-end 1))))
1027         (if downcase-field-name
1028             (setq name (downcase name)))
1029         (setq body (buffer-substring-no-properties
1030                     (match-end 0) (std11-field-end)))
1031         (or (assoc name dest)
1032             (setq dest (cons (cons name body) dest))))
1033       dest)))
1034
1035 (defun elmo-safe-filename (folder)
1036   (elmo-replace-in-string
1037    (elmo-replace-in-string
1038     (elmo-replace-in-string folder "/" " ")
1039     ":" "__")
1040    "|" "_or_"))
1041
1042 (defvar elmo-filename-replace-chars nil)
1043
1044 (defsubst elmo-replace-string-as-filename (msgid)
1045   "Replace string as filename."
1046   (setq msgid (elmo-replace-in-string msgid " " "  "))
1047   (if (null elmo-filename-replace-chars)
1048       (setq elmo-filename-replace-chars
1049             (regexp-quote (mapconcat
1050                            'car elmo-filename-replace-string-alist ""))))
1051   (while (string-match (concat "[" elmo-filename-replace-chars "]")
1052                        msgid)
1053     (setq msgid (concat
1054                  (substring msgid 0 (match-beginning 0))
1055                  (cdr (assoc
1056                        (substring msgid
1057                                   (match-beginning 0) (match-end 0))
1058                        elmo-filename-replace-string-alist))
1059                  (substring msgid (match-end 0)))))
1060   msgid)
1061
1062 (defsubst elmo-recover-string-from-filename (filename)
1063   "Recover string from FILENAME."
1064   (let (tmp result)
1065     (while (string-match " " filename)
1066       (setq tmp (substring filename
1067                            (match-beginning 0)
1068                            (+ (match-end 0) 1)))
1069       (if (string= tmp "  ")
1070           (setq tmp " ")
1071         (setq tmp (car (rassoc tmp
1072                                elmo-filename-replace-string-alist))))
1073       (setq result
1074             (concat result
1075                     (substring filename 0 (match-beginning 0))
1076                     tmp))
1077       (setq filename (substring filename (+ (match-end 0) 1))))
1078     (concat result filename)))
1079
1080 (defsubst elmo-copy-file (src dst &optional ok-if-already-exists)
1081   (condition-case err
1082       (elmo-add-name-to-file src dst ok-if-already-exists)
1083     (error (copy-file src dst ok-if-already-exists t))))
1084
1085 (defsubst elmo-buffer-exists-p (buffer)
1086   (if (bufferp buffer)
1087       (buffer-live-p buffer)
1088     (get-buffer buffer)))
1089
1090 (defsubst elmo-kill-buffer (buffer)
1091   (when (elmo-buffer-exists-p buffer)
1092     (kill-buffer buffer)))
1093
1094 (defun elmo-delete-if (pred lst)
1095   "Return new list contain items which don't satisfy PRED in LST."
1096   (let (result)
1097     (while lst
1098       (unless (funcall pred (car lst))
1099         (setq result (cons (car lst) result)))
1100       (setq lst (cdr lst)))
1101     (nreverse result)))
1102
1103 (defun elmo-list-delete (list1 list2 &optional delete-function)
1104   "Delete by side effect any occurrences equal to elements of LIST1 from LIST2.
1105 Return the modified LIST2.  Deletion is done with `delete'.
1106 Write `(setq foo (elmo-list-delete bar foo))' to be sure of changing
1107 the value of `foo'.
1108 If optional DELETE-FUNCTION is speficied, it is used as delete procedure."
1109   (setq delete-function (or delete-function 'delete))
1110   (while list1
1111     (setq list2 (funcall delete-function (car list1) list2))
1112     (setq list1 (cdr list1)))
1113   list2)
1114
1115 (defun elmo-list-member (list1 list2)
1116   "If any element of LIST1 is member of LIST2, return t."
1117   (catch 'done
1118     (while list1
1119       (if (member (car list1) list2)
1120           (throw 'done t))
1121       (setq list1 (cdr list1)))))
1122
1123 (defun elmo-count-matches (regexp beg end)
1124   (let ((count 0))
1125     (save-excursion
1126       (goto-char beg)
1127       (while (re-search-forward regexp end t)
1128         (setq count (1+ count)))
1129       count)))
1130
1131 (if (fboundp 'display-error)
1132     (defalias 'elmo-display-error 'display-error)
1133   (defun elmo-display-error (error-object stream)
1134     "A tiny function to display ERROR-OBJECT to the STREAM."
1135     (let ((first t)
1136           (errobj error-object)
1137           err-mes)
1138       (while errobj
1139         (setq err-mes (concat err-mes (format
1140                                        (if (stringp (car errobj))
1141                                            "%s"
1142                                          "%S")
1143                                        (car errobj))))
1144         (setq errobj (cdr errobj))
1145         (if errobj (setq err-mes (concat err-mes (if first ": " ", "))))
1146         (setq first nil))
1147       (princ err-mes stream))))
1148
1149 (if (fboundp 'define-error)
1150     (defalias 'elmo-define-error 'define-error)
1151   (defun elmo-define-error (error doc &optional parents)
1152     (or parents
1153         (setq parents 'error))
1154     (let ((conds (get parents 'error-conditions)))
1155       (or conds
1156           (error "Not an error symbol: %s" error))
1157       (setplist error
1158                 (list 'error-message doc
1159                       'error-conditions (cons error conds))))))
1160
1161 (cond ((fboundp 'progress-feedback-with-label)
1162        (defalias 'elmo-display-progress 'progress-feedback-with-label))
1163       ((fboundp 'lprogress-display)
1164        (defalias 'elmo-display-progress 'lprogress-display))
1165       (t
1166        (defun elmo-display-progress (label format &optional value &rest args)
1167          "Print a progress message."
1168          (if (and (null format) (null args))
1169              (message nil)
1170            (apply (function message) (concat format " %d%%")
1171                   (nconc args (list value)))))))
1172
1173 (defvar elmo-progress-counter-alist nil)
1174
1175 (defmacro elmo-progress-counter-value (counter)
1176   (` (aref (cdr (, counter)) 0)))
1177
1178 (defmacro elmo-progress-counter-all-value (counter)
1179   (` (aref (cdr (, counter)) 1)))
1180
1181 (defmacro elmo-progress-counter-format (counter)
1182   (` (aref (cdr (, counter)) 2)))
1183
1184 (defmacro elmo-progress-counter-set-value (counter value)
1185   (` (aset (cdr (, counter)) 0 (, value))))
1186
1187 (defun elmo-progress-set (label all-value &optional format)
1188   (unless (assq label elmo-progress-counter-alist)
1189     (setq elmo-progress-counter-alist
1190           (cons (cons label (vector 0 all-value (or format "")))
1191                 elmo-progress-counter-alist))))
1192
1193 (defun elmo-progress-clear (label)
1194   (let ((counter (assq label elmo-progress-counter-alist)))
1195     (when counter
1196       (elmo-display-progress label
1197                              (elmo-progress-counter-format counter)
1198                              100)
1199       (setq elmo-progress-counter-alist
1200             (delq counter elmo-progress-counter-alist)))))
1201
1202 (defun elmo-progress-notify (label &optional value op &rest args)
1203   (let ((counter (assq label elmo-progress-counter-alist)))
1204     (when counter
1205       (let* ((value (or value 1))
1206              (cur-value (elmo-progress-counter-value counter))
1207              (all-value (elmo-progress-counter-all-value counter))
1208              (new-value (if (eq op 'set) value (+ cur-value value)))
1209              (cur-rate (/ (* cur-value 100) all-value))
1210              (new-rate (/ (* new-value 100) all-value)))
1211         (elmo-progress-counter-set-value counter new-value)
1212         (unless (= cur-rate new-rate)
1213           (apply 'elmo-display-progress
1214                  label
1215                  (elmo-progress-counter-format counter)
1216                  new-rate
1217                  args))
1218         (when (>= new-rate 100)
1219           (elmo-progress-clear label))))))
1220
1221 (put 'elmo-with-progress-display 'lisp-indent-function '2)
1222 (def-edebug-spec elmo-with-progress-display
1223   (form (symbolp form &optional form) &rest form))
1224
1225 (defmacro elmo-with-progress-display (condition spec &rest body)
1226   "Evaluate BODY with progress gauge if CONDITION is non-nil.
1227 SPEC is a list as followed (LABEL MAX-VALUE [FORMAT])."
1228   (let ((label (car spec))
1229         (max-value (cadr spec))
1230         (fmt (caddr spec)))
1231     `(unwind-protect
1232          (progn
1233            (when ,condition
1234              (elmo-progress-set (quote ,label) ,max-value ,fmt))
1235            ,@body)
1236        (elmo-progress-clear (quote ,label)))))
1237
1238 (defun elmo-time-expire (before-time diff-time)
1239   (let* ((current (current-time))
1240          (rest (when (< (nth 1 current) (nth 1 before-time))
1241                  (expt 2 16)))
1242          diff)
1243     (setq diff
1244           (list (- (+ (car current) (if rest -1 0)) (car before-time))
1245                 (- (+ (or rest 0) (nth 1 current)) (nth 1 before-time))))
1246     (and (eq (car diff) 0)
1247          (< diff-time (nth 1 diff)))))
1248
1249 (if (fboundp 'std11-fetch-field)
1250     (defalias 'elmo-field-body 'std11-fetch-field) ;;no narrow-to-region
1251   (defalias 'elmo-field-body 'std11-field-body))
1252
1253 (defun elmo-unfold-field-body (name)
1254   (let ((value (elmo-field-body name)))
1255     (and value
1256          (std11-unfold-string value))))
1257
1258 (defun elmo-decoded-field-body (field-name &optional mode)
1259   (let ((field-body (elmo-field-body field-name)))
1260     (and field-body
1261          (elmo-set-work-buf
1262           (mime-decode-field-body field-body field-name mode)))))
1263
1264 (defun elmo-address-quote-specials (word)
1265   "Make quoted string of WORD if needed."
1266   (let ((lal (std11-lexical-analyze word)))
1267     (if (or (assq 'specials lal)
1268             (assq 'domain-literal lal))
1269         (prin1-to-string word)
1270       word)))
1271
1272 (defmacro elmo-string (string)
1273   "STRING without text property."
1274   (` (let ((obj (copy-sequence (, string))))
1275        (and obj (set-text-properties 0 (length obj) nil obj))
1276        obj)))
1277
1278 (defun elmo-flatten (list-of-list)
1279   "Flatten LIST-OF-LIST."
1280   (unless (null list-of-list)
1281     (append (if (and (car list-of-list)
1282                      (listp (car list-of-list)))
1283                 (car list-of-list)
1284               (list (car list-of-list)))
1285             (elmo-flatten (cdr list-of-list)))))
1286
1287 (defun elmo-y-or-n-p (prompt &optional auto default)
1288   "Same as `y-or-n-p'.
1289 But if optional argument AUTO is non-nil, DEFAULT is returned."
1290   (if auto
1291       default
1292     (y-or-n-p prompt)))
1293
1294 (defun elmo-string-member (string slist)
1295   (catch 'found
1296     (while slist
1297       (if (and (stringp (car slist))
1298                (string= string (car slist)))
1299           (throw 'found t))
1300       (setq slist (cdr slist)))))
1301
1302 (static-cond ((fboundp 'member-ignore-case)
1303        (defalias 'elmo-string-member-ignore-case 'member-ignore-case))
1304       ((fboundp 'compare-strings)
1305        (defun elmo-string-member-ignore-case (elt list)
1306          "Like `member', but ignores differences in case and text representation.
1307 ELT must be a string.  Upper-case and lower-case letters are treated as equal.
1308 Unibyte strings are converted to multibyte for comparison."
1309          (while (and list (not (eq t (compare-strings elt 0 nil (car list) 0 nil t))))
1310            (setq list (cdr list)))
1311          list))
1312       (t
1313        (defun elmo-string-member-ignore-case (elt list)
1314          "Like `member', but ignores differences in case and text representation.
1315 ELT must be a string.  Upper-case and lower-case letters are treated as equal."
1316          (let ((str (downcase elt)))
1317            (while (and list (not (string= str (downcase (car list)))))
1318              (setq list (cdr list)))
1319            list))))
1320
1321 (defun elmo-string-match-member (str list &optional case-ignore)
1322   (let ((case-fold-search case-ignore))
1323     (catch 'member
1324       (while list
1325         (if (string-match (car list) str)
1326             (throw 'member (car list)))
1327         (setq list (cdr list))))))
1328
1329 (defun elmo-string-matched-member (str list &optional case-ignore)
1330   (let ((case-fold-search case-ignore))
1331     (catch 'member
1332       (while list
1333         (if (string-match str (car list))
1334             (throw 'member (car list)))
1335         (setq list (cdr list))))))
1336
1337 (defsubst elmo-string-delete-match (string pos)
1338   (concat (substring string
1339                      0 (match-beginning pos))
1340           (substring string
1341                      (match-end pos)
1342                      (length string))))
1343
1344 (defun elmo-string-match-assoc (key alist &optional case-ignore)
1345   (let ((case-fold-search case-ignore)
1346         a)
1347     (catch 'loop
1348       (while alist
1349         (setq a (car alist))
1350         (if (and (consp a)
1351                  (stringp (car a))
1352                  (string-match key (car a)))
1353             (throw 'loop a))
1354         (setq alist (cdr alist))))))
1355
1356 (defun elmo-string-matched-assoc (key alist &optional case-ignore)
1357   (let ((case-fold-search case-ignore)
1358         a)
1359     (catch 'loop
1360       (while alist
1361         (setq a (car alist))
1362         (if (and (consp a)
1363                  (stringp (car a))
1364                  (string-match (car a) key))
1365             (throw 'loop a))
1366         (setq alist (cdr alist))))))
1367
1368 (defun elmo-string-assoc (key alist)
1369   (let (a)
1370     (catch 'loop
1371       (while alist
1372         (setq a (car alist))
1373         (if (and (consp a)
1374                  (stringp (car a))
1375                  (string= key (car a)))
1376             (throw 'loop a))
1377         (setq alist (cdr alist))))))
1378
1379 (defun elmo-string-assoc-all (key alist)
1380   (let (matches)
1381     (while alist
1382       (if (string= key (car (car alist)))
1383           (setq matches
1384                 (cons (car alist)
1385                       matches)))
1386       (setq alist (cdr alist)))
1387     matches))
1388
1389 (defun elmo-string-rassoc (key alist)
1390   (let (a)
1391     (catch 'loop
1392       (while alist
1393         (setq a (car alist))
1394         (if (and (consp a)
1395                  (stringp (cdr a))
1396                  (string= key (cdr a)))
1397             (throw 'loop a))
1398         (setq alist (cdr alist))))))
1399
1400 (defun elmo-string-rassoc-all (key alist)
1401   (let (matches)
1402     (while alist
1403       (if (string= key (cdr (car alist)))
1404           (setq matches
1405                 (cons (car alist)
1406                       matches)))
1407       (setq alist (cdr alist)))
1408     matches))
1409
1410 (defun elmo-expand-newtext (newtext original)
1411   (let ((len (length newtext))
1412         (pos 0)
1413         c expanded beg N did-expand)
1414     (while (< pos len)
1415       (setq beg pos)
1416       (while (and (< pos len)
1417                   (not (= (aref newtext pos) ?\\)))
1418         (setq pos (1+ pos)))
1419       (unless (= beg pos)
1420         (push (substring newtext beg pos) expanded))
1421       (when (< pos len)
1422         ;; We hit a \; expand it.
1423         (setq did-expand t
1424               pos (1+ pos)
1425               c (aref newtext pos))
1426         (if (not (or (= c ?\&)
1427                      (and (>= c ?1)
1428                           (<= c ?9))))
1429             ;; \ followed by some character we don't expand.
1430             (push (char-to-string c) expanded)
1431           ;; \& or \N
1432           (if (= c ?\&)
1433               (setq N 0)
1434             (setq N (- c ?0)))
1435           (when (match-beginning N)
1436             (push (substring original (match-beginning N) (match-end N))
1437                   expanded))))
1438       (setq pos (1+ pos)))
1439     (if did-expand
1440         (apply (function concat) (nreverse expanded))
1441       newtext)))
1442
1443 ;;; Folder parser utils.
1444 (defun elmo-parse-token (string &optional seps)
1445   "Parse atom from STRING using SEPS as a string of separator char list."
1446   (let ((len (length string))
1447         (seps (and seps (string-to-char-list seps)))
1448         (i 0)
1449         (sep nil)
1450         content c in)
1451     (if (eq len 0)
1452         (cons "" "")
1453       (while (and (< i len) (or in (null sep)))
1454         (setq c (aref string i))
1455         (cond
1456          ((and in (eq c ?\\))
1457           (setq i (1+ i)
1458                 content (cons (aref string i) content)
1459                 i (1+ i)))
1460          ((eq c ?\")
1461           (setq in (not in)
1462                 i (1+ i)))
1463          (in (setq content (cons c content)
1464                    i (1+ i)))
1465          ((memq c seps)
1466           (setq sep c))
1467          (t (setq content (cons c content)
1468                   i (1+ i)))))
1469       (if in (error "Parse error in quoted"))
1470       (cons (if (null content) "" (char-list-to-string (nreverse content)))
1471             (substring string i)))))
1472
1473 (defun elmo-parse-prefixed-element (prefix string &optional seps)
1474   (if (and (not (eq (length string) 0))
1475            (eq (aref string 0) prefix))
1476       (elmo-parse-token (substring string 1) seps)
1477     (cons "" string)))
1478
1479 ;;; Number set defined by OKAZAKI Tetsurou <okazaki@be.to>
1480 ;;
1481 ;; number          ::= [0-9]+
1482 ;; beg             ::= number
1483 ;; end             ::= number
1484 ;; number-range    ::= "(" beg " . " end ")"      ;; cons cell
1485 ;; number-set-elem ::= number / number-range
1486 ;; number-set      ::= "(" *number-set-elem ")"   ;; list
1487
1488 (defun elmo-number-set-member (number number-set)
1489   "Return non-nil if NUMBER is an element of NUMBER-SET.
1490 The value is actually the tail of NUMBER-RANGE whose car contains NUMBER."
1491   (or (memq number number-set)
1492       (let (found)
1493         (while (and number-set (not found))
1494           (if (and (consp (car number-set))
1495                    (and (<= (car (car number-set)) number)
1496                         (<= number (cdr (car number-set)))))
1497               (setq found t)
1498             (setq number-set (cdr number-set))))
1499         number-set)))
1500
1501 (defun elmo-number-set-append-list (number-set list)
1502   "Append LIST of numbers to the NUMBER-SET.
1503 NUMBER-SET is altered."
1504   (let ((appended number-set))
1505     (while list
1506       (setq appended (elmo-number-set-append appended (car list)))
1507       (setq list (cdr list)))
1508     appended))
1509
1510 (defun elmo-number-set-append (number-set number)
1511   "Append NUMBER to the NUMBER-SET.
1512 NUMBER-SET is altered."
1513   (let ((number-set-1 number-set)
1514         found elem)
1515     (while (and number-set (not found))
1516       (setq elem (car number-set))
1517       (cond
1518        ((and (consp elem)
1519              (eq (+ 1 (cdr elem)) number))
1520         (setcdr elem number)
1521         (setq found t))
1522        ((and (integerp elem)
1523              (eq (+ 1 elem) number))
1524         (setcar number-set (cons elem number))
1525         (setq found t))
1526        ((or (and (integerp elem) (eq elem number))
1527             (and (consp elem)
1528                  (<= (car elem) number)
1529                  (<= number (cdr elem))))
1530         (setq found t)))
1531       (setq number-set (cdr number-set)))
1532     (if (not found)
1533         (setq number-set-1 (nconc number-set-1 (list number))))
1534     number-set-1))
1535
1536 (defun elmo-number-set-delete-list (number-set list)
1537   "Delete LIST of numbers from the NUMBER-SET.
1538 NUMBER-SET is altered."
1539   (let ((deleted number-set))
1540     (dolist (number list)
1541       (setq deleted (elmo-number-set-delete deleted number)))
1542     deleted))
1543
1544 (defun elmo-number-set-delete (number-set number)
1545   "Delete NUMBER from the NUMBER-SET.
1546 NUMBER-SET is altered."
1547   (let* ((curr number-set)
1548          (top (cons 'dummy number-set))
1549          (prev top)
1550          elem found)
1551     (while (and curr (not found))
1552       (setq elem (car curr))
1553       (if (consp elem)
1554           (cond
1555            ((eq (car elem) number)
1556             (if (eq (cdr elem) (1+ number))
1557                 (setcar curr (cdr elem))
1558               (setcar elem (1+ number)))
1559             (setq found t))
1560            ((eq (cdr elem) number)
1561             (if (eq (car elem) (1- number))
1562                 (setcar curr (car elem))
1563               (setcdr elem (1- number)))
1564             (setq found t))
1565            ((and (> number (car elem))
1566                  (< number (cdr elem)))
1567             (setcdr
1568              prev
1569              (nconc
1570               (list
1571                ;; (beg . (1- number))
1572                (let ((new (cons (car elem) (1- number))))
1573                  (if (eq (car new) (cdr new))
1574                      (car new)
1575                    new))
1576                ;; ((1+ number) . end)
1577                (let ((new (cons (1+ number) (cdr elem))))
1578                  (if (eq (car new) (cdr new))
1579                      (car new)
1580                    new)))
1581               (cdr curr)))))
1582         (when (eq elem number)
1583           (setcdr prev (cdr curr))
1584           (setq found t)))
1585       (setq prev curr
1586             curr (cdr curr)))
1587     (cdr top)))
1588
1589 (defun elmo-make-number-list (beg end)
1590   (let (number-list i)
1591     (setq i end)
1592     (while (>= i beg)
1593       (setq number-list (cons i number-list))
1594       (setq i (1- i)))
1595     number-list))
1596
1597 (defun elmo-number-set-to-number-list (number-set)
1598   "Return a number list which corresponds to NUMBER-SET."
1599   (let ((number-list (list 'dummy))
1600         elem)
1601     (while number-set
1602       (setq elem (car number-set))
1603       (cond
1604        ((consp elem)
1605         (nconc number-list (elmo-make-number-list (car elem) (cdr elem))))
1606        ((integerp elem)
1607         (nconc number-list (list elem))))
1608       (setq number-set (cdr number-set)))
1609     (cdr number-list)))
1610
1611 (defcustom elmo-list-subdirectories-ignore-regexp "^\\(\\.\\.?\\|[0-9]+\\)$"
1612   "*Regexp to filter subfolders."
1613   :type 'regexp
1614   :group 'elmo)
1615
1616 (defun elmo-list-subdirectories-1 (basedir curdir one-level)
1617   (let ((root (zerop (length curdir)))
1618         (w32-get-true-file-link-count t) ; for Meadow
1619         attr dirs dir)
1620     (catch 'done
1621       (dolist (file (directory-files (setq dir (expand-file-name curdir basedir))))
1622         (when (and (not (string-match
1623                          elmo-list-subdirectories-ignore-regexp
1624                          file))
1625                    (car (setq attr (file-attributes
1626                                     (expand-file-name file dir)))))
1627           (when (eq one-level 'check) (throw 'done t))
1628           (let ((relpath
1629                  (concat curdir (and (not root) elmo-path-sep) file))
1630                 subdirs)
1631             (setq dirs (nconc dirs
1632                               (if (if elmo-have-link-count (< 2 (nth 1 attr))
1633                                     (setq subdirs
1634                                           (elmo-list-subdirectories-1
1635                                            basedir
1636                                            relpath
1637                                            (if one-level 'check))))
1638                                   (if one-level
1639                                       (list (list relpath))
1640                                     (cons relpath
1641                                           (or subdirs
1642                                               (elmo-list-subdirectories-1
1643                                                basedir
1644                                                relpath
1645                                                nil))))
1646                                 (list relpath)))))))
1647       dirs)))
1648
1649 (defun elmo-list-subdirectories (directory file one-level)
1650   (let ((subdirs (elmo-list-subdirectories-1 directory file one-level)))
1651     (if (zerop (length file))
1652         subdirs
1653       (cons file subdirs))))
1654
1655 (defun elmo-mapcar-list-of-list (func list-of-list)
1656   (mapcar
1657    (lambda (x)
1658      (cond ((listp x) (elmo-mapcar-list-of-list func x))
1659            (t (funcall func x))))
1660    list-of-list))
1661
1662 (defun elmo-parse (string regexp &optional matchn)
1663   (or matchn (setq matchn 1))
1664   (let (list)
1665     (store-match-data nil)
1666     (while (string-match regexp string (match-end 0))
1667       (setq list (cons (substring string (match-beginning matchn)
1668                                   (match-end matchn)) list)))
1669     (nreverse list)))
1670
1671 ;;; File cache.
1672 (defmacro elmo-make-file-cache (path status)
1673   "PATH is the cache file name.
1674 STATUS is one of 'section, 'entire or nil.
1675  nil means no cache exists.
1676 'section means partial section cache exists.
1677 'entire means entire cache exists.
1678 If the cache is partial file-cache, TYPE is 'partial."
1679   (` (cons (, path) (, status))))
1680
1681 (defmacro elmo-file-cache-path (file-cache)
1682   "Returns the file path of the FILE-CACHE."
1683   (` (car (, file-cache))))
1684
1685 (defmacro elmo-file-cache-status (file-cache)
1686   "Returns the status of the FILE-CACHE."
1687   (` (cdr (, file-cache))))
1688
1689 (defsubst elmo-cache-to-msgid (filename)
1690   (concat "<" (elmo-recover-string-from-filename filename) ">"))
1691
1692 (defsubst elmo-cache-get-path-subr (msgid)
1693   (let ((chars '(?0 ?1 ?2 ?3 ?4 ?5 ?6 ?7 ?8 ?9 ?A ?B ?C ?D ?E ?F))
1694         (clist (string-to-char-list msgid))
1695         (sum 0))
1696     (while clist
1697       (setq sum (+ sum (car clist)))
1698       (setq clist (cdr clist)))
1699     (format "%c%c"
1700             (nth (% (/ sum 16) 2) chars)
1701             (nth (% sum 16) chars))))
1702
1703 ;;;
1704 (defun elmo-file-cache-get-path (msgid &optional section)
1705   "Get cache path for MSGID.
1706 If optional argument SECTION is specified, partial cache path is returned."
1707   (if (setq msgid (elmo-msgid-to-cache msgid))
1708       (expand-file-name
1709        (if section
1710            (format "%s/%s/%s/%s"
1711                    elmo-cache-directory
1712                    (elmo-cache-get-path-subr msgid)
1713                    msgid
1714                    section)
1715          (format "%s/%s/%s"
1716                  elmo-cache-directory
1717                  (elmo-cache-get-path-subr msgid)
1718                  msgid)))))
1719
1720 (defmacro elmo-file-cache-expand-path (path section)
1721   "Return file name for the file-cache corresponds to the section.
1722 PATH is the file-cache path.
1723 SECTION is the section string."
1724   (` (expand-file-name (or (, section) "") (, path))))
1725
1726 (defun elmo-file-cache-delete (path)
1727   "Delete a cache on PATH."
1728   (when (file-exists-p path)
1729     (if (file-directory-p path)
1730         (progn
1731           (dolist (file (directory-files path t "^[^\\.]"))
1732             (delete-file file))
1733           (delete-directory path))
1734       (delete-file path))
1735     t))
1736
1737 (defun elmo-file-cache-exists-p (msgid)
1738   "Returns 'section or 'entire if a cache which corresponds to MSGID exists."
1739   (elmo-file-cache-status (elmo-file-cache-get msgid)))
1740
1741 (defun elmo-file-cache-save (cache-path section)
1742   "Save current buffer as cache on PATH.
1743 Return t if cache is saved successfully."
1744   (condition-case nil
1745       (let ((path (if section (expand-file-name section cache-path)
1746                     cache-path))
1747             files dir)
1748         (if (and (null section)
1749                  (file-directory-p path))
1750             (progn
1751               (setq files (directory-files path t "^[^\\.]"))
1752               (while files
1753                 (delete-file (car files))
1754                 (setq files (cdr files)))
1755               (delete-directory path))
1756           (if (and section
1757                    (not (file-directory-p cache-path)))
1758               (delete-file cache-path)))
1759         (when path
1760           (setq dir (directory-file-name (file-name-directory path)))
1761           (if (not (file-exists-p dir))
1762               (elmo-make-directory dir))
1763           (write-region-as-binary (point-min) (point-max)
1764                                   path nil 'no-msg)
1765           t))
1766     ;; ignore error
1767     (error)))
1768
1769 (defun elmo-file-cache-load (cache-path section)
1770   "Load cache on PATH into the current buffer.
1771 Return t if cache is loaded successfully."
1772   (condition-case nil
1773       (let (cache-file)
1774         (when (and cache-path
1775                    (if (elmo-cache-path-section-p cache-path)
1776                        section
1777                      (null section))
1778                    (setq cache-file (elmo-file-cache-expand-path
1779                                      cache-path
1780                                      section))
1781                    (file-exists-p cache-file))
1782           (insert-file-contents-as-binary cache-file)
1783           t))
1784     ;; igore error
1785     (error)))
1786
1787 (defun elmo-cache-path-section-p (path)
1788   "Return non-nil when PATH is `section' cache path."
1789   (file-directory-p path))
1790
1791 (defun elmo-file-cache-get (msgid &optional section)
1792   "Returns the current file-cache object associated with MSGID.
1793 MSGID is the message-id of the message.
1794 If optional argument SECTION is specified, get partial file-cache object
1795 associated with SECTION."
1796   (if msgid
1797       (let ((path (elmo-cache-get-path msgid)))
1798         (if (and path (file-exists-p path))
1799             (if (elmo-cache-path-section-p path)
1800                 (if section
1801                     (if (file-exists-p (setq path (expand-file-name
1802                                                    section path)))
1803                         (cons path 'section))
1804                   ;; section is not specified but sectional.
1805                   (cons path 'section))
1806               ;; not directory.
1807               (unless section
1808                 (cons path 'entire)))
1809           ;; no cache.
1810           (cons path nil)))))
1811
1812 ;;;
1813 ;; Expire cache.
1814
1815 (defun elmo-cache-expire ()
1816   (interactive)
1817   (let* ((completion-ignore-case t)
1818          (method (completing-read (format "Expire by (%s): "
1819                                           elmo-cache-expire-default-method)
1820                                   '(("size" . "size")
1821                                     ("age" . "age"))
1822                                   nil t)))
1823     (when (string= method "")
1824       (setq method elmo-cache-expire-default-method))
1825     (funcall (intern (concat "elmo-cache-expire-by-" method)))))
1826
1827 (defun elmo-read-float-value-from-minibuffer (prompt &optional initial)
1828   (let ((str (read-from-minibuffer prompt initial)))
1829     (cond
1830      ((string-match "[0-9]*\\.[0-9]+" str)
1831       (string-to-number str))
1832      ((string-match "[0-9]+" str)
1833       (string-to-number (concat str ".0")))
1834      (t (error "%s is not number" str)))))
1835
1836 (defun elmo-cache-expire-by-size (&optional kbytes)
1837   "Expire cache file by size.
1838 If KBYTES is kilo bytes (This value must be float)."
1839   (interactive)
1840   (let ((size (or kbytes
1841                   (and (interactive-p)
1842                        (elmo-read-float-value-from-minibuffer
1843                         "Enter cache disk size (Kbytes): "
1844                         (number-to-string
1845                          (if (integerp elmo-cache-expire-default-size)
1846                              (float elmo-cache-expire-default-size)
1847                            elmo-cache-expire-default-size))))
1848                   (if (integerp elmo-cache-expire-default-size)
1849                       (float elmo-cache-expire-default-size))))
1850         (count 0)
1851         (Kbytes 1024)
1852         total beginning)
1853     (message "Checking disk usage...")
1854     (setq total (/ (elmo-disk-usage
1855                     elmo-cache-directory) Kbytes))
1856     (setq beginning total)
1857     (message "Checking disk usage...done")
1858     (let ((cfl (elmo-cache-get-sorted-cache-file-list))
1859           (deleted 0)
1860           oldest
1861           cur-size cur-file)
1862       (while (and (<= size total)
1863                   (setq oldest (elmo-cache-get-oldest-cache-file-entity cfl)))
1864         (setq cur-file (expand-file-name (car (cdr oldest)) (car oldest)))
1865         (setq cur-size (/ (elmo-disk-usage cur-file) Kbytes))
1866         (when (elmo-file-cache-delete cur-file)
1867           (setq count (+ count 1))
1868           (message "%d cache(s) are expired." count))
1869         (setq deleted (+ deleted cur-size))
1870         (setq total (- total cur-size)))
1871       (message "%d cache(s) are expired from disk (%d Kbytes/%d Kbytes)."
1872                count deleted beginning))))
1873
1874 (defun elmo-cache-make-file-entity (filename path)
1875   (cons filename (elmo-get-last-accessed-time filename path)))
1876
1877 (defun elmo-cache-get-oldest-cache-file-entity (cache-file-list)
1878   (let ((cfl cache-file-list)
1879         flist firsts oldest-entity wonlist)
1880     (while cfl
1881       (setq flist (cdr (car cfl)))
1882       (setq firsts (append firsts (list
1883                                    (cons (car (car cfl))
1884                                          (car flist)))))
1885       (setq cfl (cdr cfl)))
1886 ;;; (prin1 firsts)
1887     (while firsts
1888       (if (and (not oldest-entity)
1889                (cdr (cdr (car firsts))))
1890           (setq oldest-entity (car firsts)))
1891       (if (and (cdr (cdr (car firsts)))
1892                (cdr (cdr oldest-entity))
1893                (> (cdr (cdr oldest-entity)) (cdr (cdr (car firsts)))))
1894           (setq oldest-entity (car firsts)))
1895       (setq firsts (cdr firsts)))
1896     (setq wonlist (assoc (car oldest-entity) cache-file-list))
1897     (and wonlist
1898          (setcdr wonlist (delete (car (cdr wonlist)) (cdr wonlist))))
1899     oldest-entity))
1900
1901 (defun elmo-cache-get-sorted-cache-file-list ()
1902   (let ((dirs (directory-files
1903                elmo-cache-directory
1904                t "^[^\\.]"))
1905         (i 0) num
1906         elist
1907         ret-val)
1908     (setq num (length dirs))
1909     (message "Collecting cache info...")
1910     (while dirs
1911       (setq elist (mapcar (lambda (x)
1912                             (elmo-cache-make-file-entity x (car dirs)))
1913                           (directory-files (car dirs) nil "^[^\\.]")))
1914       (setq ret-val (append ret-val
1915                             (list (cons
1916                                    (car dirs)
1917                                    (sort
1918                                     elist
1919                                     (lambda (x y)
1920                                       (< (cdr x)
1921                                          (cdr y))))))))
1922       (when (> num elmo-display-progress-threshold)
1923         (setq i (+ i 1))
1924         (elmo-display-progress
1925          'elmo-cache-get-sorted-cache-file-list "Collecting cache info..."
1926          (/ (* i 100) num)))
1927       (setq dirs (cdr dirs)))
1928     (message "Collecting cache info...done")
1929     ret-val))
1930
1931 (defun elmo-cache-expire-by-age (&optional days)
1932   (let ((age (or (and days (int-to-string days))
1933                  (and (interactive-p)
1934                       (read-from-minibuffer
1935                        (format "Enter days (%s): "
1936                                elmo-cache-expire-default-age)))
1937                  (int-to-string elmo-cache-expire-default-age)))
1938         (dirs (directory-files
1939                elmo-cache-directory
1940                t "^[^\\.]"))
1941         (count 0)
1942         curtime)
1943     (if (string= age "")
1944         (setq age elmo-cache-expire-default-age)
1945       (setq age (string-to-int age)))
1946     (setq curtime (current-time))
1947     (setq curtime (+ (* (nth 0 curtime)
1948                         (float 65536)) (nth 1 curtime)))
1949     (while dirs
1950       (let ((files (directory-files (car dirs) t "^[^\\.]"))
1951             (limit-age (* age 86400)))
1952         (while files
1953           (when (> (- curtime (elmo-get-last-accessed-time (car files)))
1954                    limit-age)
1955             (when (elmo-file-cache-delete (car files))
1956               (setq count (+ 1 count))
1957               (message "%d cache file(s) are expired." count)))
1958           (setq files (cdr files))))
1959       (setq dirs (cdr dirs)))))
1960
1961 ;;;
1962 ;; msgid to path.
1963 (defun elmo-msgid-to-cache (msgid)
1964   (save-match-data
1965     (when (and msgid
1966                (string-match "<\\(.+\\)>$" msgid))
1967       (elmo-replace-string-as-filename (elmo-match-string 1 msgid)))))
1968
1969 (defun elmo-cache-get-path (msgid &optional folder number)
1970   "Get path for cache file associated with MSGID, FOLDER, and NUMBER."
1971   (if (setq msgid (elmo-msgid-to-cache msgid))
1972       (expand-file-name
1973        (expand-file-name
1974         (if folder
1975             (format "%s/%s/%s@%s"
1976                     (elmo-cache-get-path-subr msgid)
1977                     msgid
1978                     (or number "")
1979                     (elmo-safe-filename folder))
1980           (format "%s/%s"
1981                   (elmo-cache-get-path-subr msgid)
1982                   msgid))
1983         elmo-cache-directory))))
1984
1985 ;;;
1986 ;; Warnings.
1987
1988 (static-if (fboundp 'display-warning)
1989     (defmacro elmo-warning (&rest args)
1990       "Display a warning with `elmo' group."
1991       `(display-warning 'elmo (format ,@args)))
1992   (defconst elmo-warning-buffer-name "*elmo warning*")
1993   (defun elmo-warning (&rest args)
1994     "Display a warning. ARGS are passed to `format'."
1995     (with-current-buffer (get-buffer-create elmo-warning-buffer-name)
1996       (goto-char (point-max))
1997       (funcall 'insert (apply 'format (append args '("\n"))))
1998       (ignore-errors (recenter 1))
1999       (display-buffer elmo-warning-buffer-name))))
2000
2001 (defvar elmo-obsolete-variable-alist nil)
2002
2003 (defcustom elmo-obsolete-variable-show-warnings t
2004   "Show warning window if obsolete variable is treated."
2005   :type 'boolean
2006   :group 'elmo)
2007
2008 (defun elmo-define-obsolete-variable (obsolete var)
2009   "Define obsolete variable.
2010 OBSOLETE is a symbol for obsolete variable.
2011 VAR is a symbol for new variable.
2012 Definition is stored in `elmo-obsolete-variable-alist'."
2013   (let ((pair (assq var elmo-obsolete-variable-alist)))
2014     (if pair
2015         (setcdr pair obsolete)
2016       (setq elmo-obsolete-variable-alist
2017             (cons (cons var obsolete)
2018                   elmo-obsolete-variable-alist)))))
2019
2020 (defun elmo-resque-obsolete-variable (obsolete var)
2021   "Resque obsolete variable OBSOLETE as VAR.
2022 If `elmo-obsolete-variable-show-warnings' is non-nil, show warning message."
2023   (when (boundp obsolete)
2024     (static-if (and (fboundp 'defvaralias)
2025                     (subrp (symbol-function 'defvaralias)))
2026         (defvaralias var obsolete)
2027       (set var (symbol-value obsolete)))
2028     (if elmo-obsolete-variable-show-warnings
2029         (elmo-warning "%s is obsolete. Use %s instead."
2030                       (symbol-name obsolete)
2031                       (symbol-name var)))))
2032
2033 (defun elmo-resque-obsolete-variables (&optional alist)
2034   "Resque obsolete variables in ALIST.
2035 ALIST is a list of cons cell of
2036 \(OBSOLETE-VARIABLE-SYMBOL . NEW-VARIABLE-SYMBOL\).
2037 If ALIST is nil, `elmo-obsolete-variable-alist' is used."
2038   (dolist (pair elmo-obsolete-variable-alist)
2039     (elmo-resque-obsolete-variable (cdr pair)
2040                                    (car pair))))
2041
2042 (defsubst elmo-msgdb-get-last-message-id (string)
2043   (if string
2044       (save-match-data
2045         (let (beg)
2046           (elmo-set-work-buf
2047            (insert string)
2048            (goto-char (point-max))
2049            (when (search-backward "<" nil t)
2050              (setq beg (point))
2051              (if (search-forward ">" nil t)
2052                  (elmo-replace-in-string
2053                   (buffer-substring beg (point)) "\n[ \t]*" ""))))))))
2054
2055 (defun elmo-msgdb-get-message-id-from-buffer ()
2056   (let ((msgid (elmo-field-body "message-id")))
2057     (if msgid
2058         (if (string-match "<\\(.+\\)>$" msgid)
2059             msgid
2060           (concat "<" msgid ">"))       ; Invaild message-id.
2061       ;; no message-id, so put dummy msgid.
2062       (concat "<"
2063               (if (elmo-unfold-field-body "date")
2064                   (timezone-make-date-sortable (elmo-unfold-field-body "date"))
2065                 (md5 (string-as-unibyte (buffer-string))))
2066               (nth 1 (eword-extract-address-components
2067                       (or (elmo-field-body "from") "nobody"))) ">"))))
2068
2069 (defsubst elmo-msgdb-insert-file-header (file)
2070   "Insert the header of the article."
2071   (let ((beg 0)
2072         insert-file-contents-pre-hook   ; To avoid autoconv-xmas...
2073         insert-file-contents-post-hook
2074         format-alist)
2075     (when (file-exists-p file)
2076       ;; Read until header separator is found.
2077       (while (and (eq elmo-msgdb-file-header-chop-length
2078                       (nth 1
2079                            (insert-file-contents-as-binary
2080                             file nil beg
2081                             (incf beg elmo-msgdb-file-header-chop-length))))
2082                   (prog1 (not (search-forward "\n\n" nil t))
2083                     (goto-char (point-max))))))))
2084
2085 ;;
2086 ;; overview handling
2087 ;;
2088 (defun elmo-multiple-field-body (name &optional boundary)
2089   (save-excursion
2090     (save-restriction
2091       (std11-narrow-to-header boundary)
2092       (goto-char (point-min))
2093       (let ((case-fold-search t)
2094             (field-body nil))
2095         (while (re-search-forward (concat "^" name ":[ \t]*") nil t)
2096           (setq field-body
2097                 (nconc field-body
2098                        (list (buffer-substring-no-properties
2099                               (match-end 0) (std11-field-end))))))
2100         field-body))))
2101
2102 ;;; Queue.
2103 (defvar elmo-dop-queue-filename "queue"
2104   "*Disconnected operation queue is saved in this file.")
2105
2106 (defun elmo-dop-queue-load ()
2107   (setq elmo-dop-queue
2108         (elmo-object-load
2109          (expand-file-name elmo-dop-queue-filename
2110                            elmo-msgdb-directory))))
2111
2112 (defun elmo-dop-queue-save ()
2113   (elmo-object-save
2114    (expand-file-name elmo-dop-queue-filename
2115                      elmo-msgdb-directory)
2116    elmo-dop-queue))
2117
2118 (if (and (fboundp 'regexp-opt)
2119          (not (featurep 'xemacs)))
2120     (defalias 'elmo-regexp-opt 'regexp-opt)
2121   (defun elmo-regexp-opt (strings &optional paren)
2122     "Return a regexp to match a string in STRINGS.
2123 Each string should be unique in STRINGS and should not contain any regexps,
2124 quoted or not.  If optional PAREN is non-nil, ensure that the returned regexp
2125 is enclosed by at least one regexp grouping construct."
2126     (let ((open-paren (if paren "\\(" "")) (close-paren (if paren "\\)" "")))
2127       (concat open-paren (mapconcat 'regexp-quote strings "\\|")
2128               close-paren))))
2129
2130 (require 'product)
2131 (product-provide (provide 'elmo-util) (require 'elmo-version))
2132
2133 ;;; elmo-util.el ends here