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