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