a3d97c4fe61b2fdefe7fecb498570453795b110f
[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 'std11)
36 (require 'eword-decode)
37 (require 'utf7)
38
39 (defmacro elmo-set-buffer-multibyte (flag)
40   "Set the multibyte flag of the current buffer to FLAG."
41   (cond ((boundp 'MULE)
42          (list 'setq 'mc-flag flag))
43         ((featurep 'xemacs)
44          flag)
45         ((and (boundp 'emacs-major-version) (>= emacs-major-version 20))
46          (list 'set-buffer-multibyte flag))
47         (t
48          flag)))
49
50 (defvar elmo-work-buf-name " *elmo work*")
51 (defvar elmo-temp-buf-name " *elmo temp*")
52
53 (or (boundp 'default-enable-multibyte-characters)
54     (defvar default-enable-multibyte-characters (featurep 'mule)
55       "The mock variable except for Emacs 20."))
56
57 (defun elmo-base64-encode-string (string &optional no-line-break))
58 (defun elmo-base64-decode-string (string))
59
60 ;; base64 encoding/decoding
61 (require 'mel)
62 (fset 'elmo-base64-encode-string
63       (mel-find-function 'mime-encode-string "base64"))
64 (fset 'elmo-base64-decode-string
65       (mel-find-function 'mime-decode-string "base64"))
66
67 ;; Any Emacsen may have add-name-to-file(), because loadup.el requires it. :-p
68 ;; Check make-symbolic-link() instead.  -- 981002 by Fuji
69 (if (fboundp 'make-symbolic-link)  ;; xxx
70     (defalias 'elmo-add-name-to-file 'add-name-to-file)
71   (defun elmo-add-name-to-file
72     (filename newname &optional ok-if-already-exists)
73     (copy-file filename newname ok-if-already-exists t)))
74
75 ;; Nemacs's `read' is different.
76 (static-if (fboundp 'nemacs-version)
77     (defun elmo-read (obj)
78       (prog1 (read obj)
79         (if (bufferp obj)
80             (or (bobp) (forward-char -1)))))
81   (defalias 'elmo-read 'read))
82
83 (defmacro elmo-set-work-buf (&rest body)
84   "Execute BODY on work buffer.  Work buffer remains."
85   (` (save-excursion
86        (set-buffer (get-buffer-create elmo-work-buf-name))
87        (elmo-set-buffer-multibyte default-enable-multibyte-characters)
88        (erase-buffer)
89        (,@ body))))
90
91 (defmacro elmo-bind-directory (dir &rest body)
92   "Set current directory DIR and execute BODY."
93   (` (let ((default-directory (file-name-as-directory (, dir))))
94        (,@ body))))
95
96 (defun elmo-object-load (filename &optional mime-charset no-err)
97   "Load OBJECT from the file specified by FILENAME.
98 File content is decoded with MIME-CHARSET."
99     (if (not (file-readable-p filename))
100         nil
101       (elmo-set-work-buf
102        (as-binary-input-file
103         (insert-file-contents filename))
104        (when mime-charset
105          (elmo-set-buffer-multibyte default-enable-multibyte-characters)
106          (decode-mime-charset-region (point-min) (point-max) mime-charset))
107        (condition-case nil
108            (read (current-buffer))
109          (error (unless no-err
110                   (message "Warning: Loading object from %s failed."
111                            filename)
112                   (elmo-object-save filename nil))
113                 nil)))))
114
115 (defsubst elmo-save-buffer (filename &optional mime-charset)
116   "Save current buffer to the file specified by FILENAME.
117 Directory of the file is created if it doesn't exist.
118 File content is encoded with MIME-CHARSET."
119   (let ((dir (directory-file-name (file-name-directory filename))))
120     (if (file-directory-p dir)
121         () ; ok.
122       (unless (file-exists-p dir)
123         (elmo-make-directory dir)))
124     (if (file-writable-p filename)
125         (progn
126           (when mime-charset
127 ;;;         (elmo-set-buffer-multibyte default-enable-multibyte-characters)
128             (encode-mime-charset-region (point-min) (point-max) mime-charset))
129           (as-binary-output-file
130            (write-region (point-min) (point-max) filename nil 'no-msg)))
131       (message (format "%s is not writable." filename)))))
132
133 (defun elmo-object-save (filename object &optional mime-charset)
134   "Save OBJECT to the file specified by FILENAME.
135 Directory of the file is created if it doesn't exist.
136 File content is encoded with MIME-CHARSET."
137   (elmo-set-work-buf
138    (prin1 object (current-buffer))
139 ;;;(princ "\n" (current-buffer))
140    (elmo-save-buffer filename mime-charset)))
141
142 (defun elmo-get-network-stream-type (stream-type stream-type-alist)
143   (catch 'found
144     (while stream-type-alist
145       (if (eq (nth 1 (car stream-type-alist)) stream-type)
146           (throw 'found (car stream-type-alist)))
147       (setq stream-type-alist (cdr stream-type-alist)))))
148
149 ;;; Search Condition
150
151 (defconst elmo-condition-atom-regexp "[^/ \")|&]*")
152
153 (defun elmo-read-search-condition (default)
154   "Read search condition string interactively."
155   (elmo-read-search-condition-internal "Search by" default))
156
157 (defun elmo-read-search-condition-internal (prompt default)
158   (let* ((completion-ignore-case t)
159          (field (completing-read
160                  (format "%s (%s): " prompt default)
161                  (mapcar 'list
162                          (append '("AND" "OR"
163                                    "Last" "First"
164                                    "From" "Subject" "To" "Cc" "Body"
165                                    "Since" "Before" "ToCc"
166                                    "!From" "!Subject" "!To" "!Cc" "!Body"
167                                    "!Since" "!Before" "!ToCc")
168                                  elmo-msgdb-extra-fields)) nil t))
169          value)
170     (setq field (if (string= field "")
171                     (setq field default)
172                   field))
173     (cond
174      ((or (string= field "AND") (string= field "OR"))
175       (concat "("
176               (elmo-read-search-condition-internal
177                (concat field "(1) Search by") default)
178               (if (string= field "AND") "&" "|")
179               (elmo-read-search-condition-internal
180                (concat field "(2) Search by") default)
181               ")"))
182      ((string-match "Since\\|Before" field)
183       (concat (downcase field) ":"
184               (completing-read (format "Value for '%s': " field)
185                                (mapcar (function
186                                         (lambda (x)
187                                           (list (format "%s" (car x)))))
188                                        elmo-date-descriptions))))
189      (t
190       (setq value (read-from-minibuffer (format "Value for '%s': " field)))
191       (unless (string-match (concat "^" elmo-condition-atom-regexp "$")
192                             value)
193         (setq value (prin1-to-string value)))
194       (concat (downcase field) ":" value)))))
195
196 (defsubst elmo-condition-parse-error ()
197   (error "Syntax error in '%s'" (buffer-string)))
198
199 (defun elmo-parse-search-condition (condition)
200   "Parse CONDITION.
201 Return value is a cons cell of (STRUCTURE . REST)"
202   (with-temp-buffer
203     (insert condition)
204     (goto-char (point-min))
205     (cons (elmo-condition-parse) (buffer-substring (point) (point-max)))))
206
207 ;; condition    ::= or-expr
208 (defun elmo-condition-parse ()
209   (or (elmo-condition-parse-or-expr)
210       (elmo-condition-parse-error)))
211
212 ;; or-expr      ::= and-expr /
213 ;;                  and-expr "|" or-expr
214 (defun elmo-condition-parse-or-expr ()
215   (let ((left (elmo-condition-parse-and-expr)))
216     (if (looking-at "| *")
217         (progn
218           (goto-char (match-end 0))
219           (list 'or left (elmo-condition-parse-or-expr)))
220       left)))
221
222 ;; and-expr     ::= primitive /
223 ;;                  primitive "&" and-expr
224 (defun elmo-condition-parse-and-expr ()
225   (let ((left (elmo-condition-parse-primitive)))
226     (if (looking-at "& *")
227         (progn
228           (goto-char (match-end 0))
229           (list 'and left (elmo-condition-parse-and-expr)))
230       left)))
231
232 ;; primitive    ::= "(" expr ")" /
233 ;;                  ["!"] search-key SPACE* ":" SPACE* search-value
234 (defun elmo-condition-parse-primitive ()
235   (cond
236    ((looking-at "( *")
237     (goto-char (match-end 0))
238     (prog1 (elmo-condition-parse)
239       (unless (looking-at ") *")
240         (elmo-condition-parse-error))
241       (goto-char (match-end 0))))
242 ;; search-key   ::= [A-Za-z-]+
243 ;;                 ;; "since" / "before" / "last" / "first" /
244 ;;                 ;; "body" / field-name
245    ((looking-at "\\(!\\)? *\\([A-Za-z-]+\\) *: *")
246     (goto-char (match-end 0))
247     (let ((search-key (vector
248                        (if (match-beginning 1) 'unmatch 'match)
249                        (elmo-match-buffer 2)
250                        (elmo-condition-parse-search-value))))
251       ;; syntax sugar.
252       (if (string= (aref search-key 1) "tocc")
253           (if (eq (aref search-key 0) 'match)
254               (list 'or
255                     (vector 'match "to" (aref search-key 2))
256                     (vector 'match "cc" (aref search-key 2)))
257             (list 'and
258                   (vector 'unmatch "to" (aref search-key 2))
259                   (vector 'unmatch "cc" (aref search-key 2))))
260         search-key)))))
261
262 ;; search-value ::= quoted / time / number / atom
263 ;; quoted       ::= <elisp string expression>
264 ;; time         ::= "yesterday" / "lastweek" / "lastmonth" / "lastyear" /
265 ;;                   number SPACE* "daysago" /
266 ;;                   number "-" month "-" number  ; ex. 10-May-2000
267 ;; number       ::= [0-9]+
268 ;; month        ::= "Jan" / "Feb" / "Mar" / "Apr" / "May" / "Jun" /
269 ;;                  "Jul" / "Aug" / "Sep" / "Oct" / "Nov" / "Dec"
270 ;; atom         ::= ATOM_CHARS*
271 ;; SPACE        ::= <ascii space character, 0x20>
272 ;; ATOM_CHARS   ::= <any character except specials>
273 ;; specials     ::= SPACE / <"> / </> / <)> / <|> / <&>
274 ;;                  ;; These characters should be quoted.
275 (defun elmo-condition-parse-search-value ()
276   (cond
277    ((looking-at "\"")
278     (elmo-read (current-buffer)))
279    ((or (looking-at "yesterday") (looking-at "lastweek")
280         (looking-at "lastmonth") (looking-at "lastyear")
281         (looking-at "[0-9]+ *daysago")
282         (looking-at "[0-9]+-[A-Za-z]+-[0-9]+")
283         (looking-at "[0-9]+")
284         (looking-at elmo-condition-atom-regexp))
285     (prog1 (elmo-match-buffer 0)
286       (goto-char (match-end 0))))
287    (t (error "Syntax error '%s'" (buffer-string)))))
288
289 ;;;
290 (defsubst elmo-buffer-replace (regexp &optional newtext)
291   (goto-char (point-min))
292   (while (re-search-forward regexp nil t)
293     (replace-match (or newtext ""))))
294
295 (defsubst elmo-delete-char (char string &optional unibyte)
296   (save-match-data
297     (elmo-set-work-buf
298      (let ((coding-system-for-read 'no-conversion)
299            (coding-system-for-write 'no-conversion))
300        (if unibyte (elmo-set-buffer-multibyte nil))
301        (insert string)
302        (goto-char (point-min))
303        (while (search-forward (char-to-string char) nil t)
304          (replace-match ""))
305        (buffer-string)))))
306
307 (defsubst elmo-delete-cr-buffer ()
308   "Delete CR from buffer."
309   (save-excursion
310     (goto-char (point-min))
311     (while (search-forward "\r\n" nil t)
312       (replace-match "\n")) ))
313
314 (defsubst elmo-delete-cr-get-content-type ()
315   (save-excursion
316     (goto-char (point-min))
317     (while (search-forward "\r\n" nil t)
318       (replace-match "\n"))
319     (goto-char (point-min))
320     (or (std11-field-body "content-type")
321         t)))
322
323 (defun elmo-delete-cr (string)
324   (save-match-data
325     (elmo-set-work-buf
326      (insert string)
327      (goto-char (point-min))
328      (while (search-forward "\r\n" nil t)
329        (replace-match "\n"))
330      (buffer-string))))
331
332 (defun elmo-uniq-list (lst)
333   "Distractively uniqfy elements of LST."
334   (let ((tmp lst))
335     (while tmp (setq tmp
336                      (setcdr tmp
337                              (and (cdr tmp)
338                                   (delete (car tmp)
339                                           (cdr tmp)))))))
340   lst)
341
342 (defun elmo-list-insert (list element after)
343   "Insert an ELEMENT to the LIST, just after AFTER."
344   (let ((li list)
345         (curn 0)
346         p pn)
347     (while li
348       (if (eq (car li) after)
349           (setq p li pn curn li nil)
350         (incf curn))
351       (setq li (cdr li)))
352     (if pn
353         (setcdr (nthcdr pn list) (cons element (cdr p)))
354       (nconc list (list element)))))
355
356 (defun elmo-string-partial-p (string)
357   (and (stringp string) (string-match "message/partial" string)))
358
359 (defun elmo-get-file-string (filename &optional remove-final-newline)
360   (elmo-set-work-buf
361    (let (insert-file-contents-pre-hook   ; To avoid autoconv-xmas...
362          insert-file-contents-post-hook)
363      (when (file-exists-p filename)
364        (if filename
365            (as-binary-input-file (insert-file-contents filename)))
366        (when (and remove-final-newline
367                   (> (buffer-size) 0)
368                   (= (char-after (1- (point-max))) ?\n))
369          (goto-char (point-max))
370          (delete-backward-char 1))
371        (buffer-string)))))
372
373 (defun elmo-save-string (string filename)
374   (if string
375       (elmo-set-work-buf
376        (as-binary-output-file
377         (insert string)
378         (write-region (point-min) (point-max)
379                       filename nil 'no-msg))
380        )))
381
382 (defun elmo-max-of-list (nlist)
383   (let ((l nlist)
384         (max-num 0))
385     (while l
386       (if (< max-num (car l))
387           (setq max-num (car l)))
388       (setq l (cdr l)))
389     max-num))
390
391 (defun elmo-concat-path (path filename)
392   (if (not (string= path ""))
393       (if (string= elmo-path-sep (substring path (- (length path) 1)))
394           (concat path filename)
395         (concat path elmo-path-sep filename))
396     filename))
397
398 (defvar elmo-passwd-alist nil)
399
400 (defun elmo-passwd-alist-load ()
401   (save-excursion
402     (let ((filename (expand-file-name elmo-passwd-alist-file-name
403                                       elmo-msgdb-dir))
404           (tmp-buffer (get-buffer-create " *elmo-passwd-alist-tmp*"))
405           insert-file-contents-pre-hook   ; To avoid autoconv-xmas...
406           insert-file-contents-post-hook
407           ret-val)
408       (if (not (file-readable-p filename))
409           ()
410         (set-buffer tmp-buffer)
411         (insert-file-contents filename)
412         (setq ret-val
413               (condition-case nil
414                   (read (current-buffer))
415                 (error nil nil))))
416       (kill-buffer tmp-buffer)
417       ret-val)))
418
419 (defun elmo-passwd-alist-clear ()
420   "Clear password cache."
421   (interactive)
422   (setq elmo-passwd-alist nil))
423   
424 (defun elmo-passwd-alist-save ()
425   "Save password into file."
426   (interactive)
427   (save-excursion
428     (let ((filename (expand-file-name elmo-passwd-alist-file-name
429                                       elmo-msgdb-dir))
430           (tmp-buffer (get-buffer-create " *elmo-passwd-alist-tmp*")))
431       (set-buffer tmp-buffer)
432       (erase-buffer)
433       (prin1 elmo-passwd-alist tmp-buffer)
434       (princ "\n" tmp-buffer)
435 ;;;   (if (and (file-exists-p filename)
436 ;;;            (not (equal 384 (file-modes filename))))
437 ;;;       (error "%s is not safe.chmod 600 %s!" filename filename))
438       (if (file-writable-p filename)
439          (progn
440            (write-region (point-min) (point-max)
441                          filename nil 'no-msg)
442            (set-file-modes filename 384))
443         (message (format "%s is not writable." filename)))
444       (kill-buffer tmp-buffer))))
445
446 (defun elmo-get-passwd (key)
447   "Get password from password pool."
448   (let (pair pass)
449     (if (not elmo-passwd-alist)
450         (setq elmo-passwd-alist (elmo-passwd-alist-load)))
451     (setq pair (assoc key elmo-passwd-alist))
452     (if pair
453         (elmo-base64-decode-string (cdr pair))
454       (setq pass (elmo-read-passwd (format "Password for %s: "
455                                            key) t))
456       (setq elmo-passwd-alist
457             (append elmo-passwd-alist
458                     (list (cons key
459                                 (elmo-base64-encode-string pass)))))
460       (if elmo-passwd-life-time
461           (run-with-timer elmo-passwd-life-time nil
462                           (` (lambda () (elmo-remove-passwd (, key))))))
463       pass)))
464
465 (defun elmo-remove-passwd (key)
466   "Remove password from password pool (for failure)."
467   (let (pass-cons)
468     (if (setq pass-cons (assoc key elmo-passwd-alist))
469         (progn
470           (unwind-protect
471               (fillarray (cdr pass-cons) 0))
472           (setq elmo-passwd-alist
473                 (delete pass-cons elmo-passwd-alist))))))
474
475 (defmacro elmo-read-char-exclusive ()
476   (cond ((featurep 'xemacs)
477          '(let ((table (quote ((backspace . ?\C-h) (delete . ?\C-?)
478                                (left . ?\C-h))))
479                 event key)
480             (while (not
481                     (and
482                      (key-press-event-p (setq event (next-command-event)))
483                      (setq key (or (event-to-character event)
484                                    (cdr (assq (event-key event) table)))))))
485             key))
486         ((fboundp 'read-char-exclusive)
487          '(read-char-exclusive))
488         (t
489          '(read-char))))
490
491 (defun elmo-read-passwd (prompt &optional stars)
492   "Read a single line of text from user without echoing, and return it."
493   (let ((ans "")
494         (c 0)
495         (echo-keystrokes 0)
496         (cursor-in-echo-area t)
497         (log-message-max-size 0)
498         message-log-max done msg truncate)
499     (while (not done)
500       (if (or (not stars) (string= "" ans))
501           (setq msg prompt)
502         (setq msg (concat prompt (make-string (length ans) ?.)))
503         (setq truncate
504               (1+ (- (length msg) (window-width (minibuffer-window)))))
505         (and (> truncate 0)
506              (setq msg (concat "$" (substring msg (1+ truncate))))))
507       (message "%s" msg)
508       (setq c (elmo-read-char-exclusive))
509       (cond ((= c ?\C-g)
510              (setq quit-flag t
511                    done t))
512             ((or (= c ?\r) (= c ?\n) (= c ?\e))
513              (setq done t))
514             ((= c ?\C-u)
515              (setq ans ""))
516             ((and (/= c ?\b) (/= c ?\177))
517              (setq ans (concat ans (char-to-string c))))
518             ((> (length ans) 0)
519              (setq ans (substring ans 0 -1)))))
520     (if quit-flag
521         (prog1
522             (setq quit-flag nil)
523           (message "Quit")
524           (beep t))
525       (message "")
526       ans)))
527
528 (defun elmo-string-to-list (string)
529   (elmo-set-work-buf
530    (insert string)
531    (goto-char (point-min))
532    (insert "(")
533    (goto-char (point-max))
534    (insert ")")
535    (goto-char (point-min))
536    (read (current-buffer))))
537
538 (defun elmo-list-to-string (list)
539   (let ((tlist list)
540         str)
541     (if (listp tlist)
542         (progn
543           (setq str "(")
544           (while (car tlist)
545             (setq str
546                   (concat str
547                           (if (symbolp (car tlist))
548                               (symbol-name (car tlist))
549                             (car tlist))))
550             (if (cdr tlist)
551                 (setq str
552                       (concat str " ")))
553             (setq tlist (cdr tlist)))
554           (setq str
555                 (concat str ")")))
556       (setq str 
557             (if (symbolp tlist)
558                 (symbol-name tlist)
559               tlist)))
560     str))
561  
562
563 (defun elmo-plug-on-by-servers (alist &optional servers)
564   (let ((server-list (or servers elmo-plug-on-servers)))
565     (catch 'plugged
566       (while server-list
567         (if (elmo-plugged-p (car server-list))
568             (throw 'plugged t))
569         (setq server-list (cdr server-list))))))
570
571 (defun elmo-plug-on-by-exclude-servers (alist &optional servers)
572   (let ((server-list (or servers elmo-plug-on-exclude-servers))
573         server other-servers)
574     (while alist
575       (when (and (not (member (setq server (caaar alist)) server-list))
576                  (not (member server other-servers)))
577         (push server other-servers))
578       (setq alist (cdr alist)))
579     (elmo-plug-on-by-servers alist other-servers)))
580
581 (defun elmo-plugged-p (&optional server port stream-type alist label-exp)
582   (let ((alist (or alist elmo-plugged-alist))
583         plugged-info)
584     (cond ((and (not port) (not server))
585            (cond ((eq elmo-plugged-condition 'one)
586                   (if alist
587                       (catch 'plugged
588                         (while alist
589                           (if (nth 2 (car alist))
590                               (throw 'plugged t))
591                           (setq alist (cdr alist))))
592                     elmo-plugged))
593                  ((eq elmo-plugged-condition 'all)
594                   (if alist
595                       (catch 'plugged
596                         (while alist
597                           (if (not (nth 2 (car alist)))
598                               (throw 'plugged nil))
599                           (setq alist (cdr alist)))
600                         t)
601                     elmo-plugged))
602                  ((functionp elmo-plugged-condition)
603                   (funcall elmo-plugged-condition alist))
604                  (t ;; independent
605                   elmo-plugged)))
606           ((not port) ;; server
607            (catch 'plugged
608              (while alist
609                (when (string= server (caaar alist))
610                  (if (nth 2 (car alist))
611                      (throw 'plugged t)))
612                (setq alist (cdr alist)))))
613           (t
614            (setq plugged-info (assoc (list server port stream-type) alist))
615            (if (not plugged-info)
616                ;; add elmo-plugged-alist automatically
617                (progn
618                  (elmo-set-plugged elmo-plugged server port stream-type
619                                    nil nil nil label-exp)
620                  elmo-plugged)
621              (if (and elmo-auto-change-plugged
622                       (> elmo-auto-change-plugged 0)
623                       (nth 3 plugged-info)  ;; time
624                       (elmo-time-expire (nth 3 plugged-info)
625                                         elmo-auto-change-plugged))
626                  t
627                (nth 2 plugged-info)))))))
628
629 (defun elmo-set-plugged (plugged &optional server port stream-type time
630                                  alist label-exp add)
631   (let ((alist (or alist elmo-plugged-alist))
632         label plugged-info)
633     (cond ((and (not port) (not server))
634            (setq elmo-plugged plugged)
635            ;; set plugged all element of elmo-plugged-alist.
636            (while alist
637              (setcdr (cdar alist) (list plugged time))
638              (setq alist (cdr alist))))
639           ((not port)
640            ;; set plugged all port of server
641            (while alist
642              (when (string= server (caaar alist))
643                (setcdr (cdar alist) (list plugged time)))
644              (setq alist (cdr alist))))
645           (t
646            ;; set plugged one port of server
647            (setq plugged-info (assoc (list server port stream-type) alist))
648            (setq label (if label-exp
649                            (eval label-exp)
650                          (nth 1 plugged-info)))
651            (if plugged-info
652                ;; if add is non-nil, don't reset plug state.
653                (unless add
654                  (setcdr plugged-info (list label plugged time)))
655              (setq alist
656                    (setq elmo-plugged-alist
657                          (nconc
658                           elmo-plugged-alist
659                           (list
660                            (list (list server port stream-type)
661                                  label plugged time))))))))
662     alist))
663
664 (defun elmo-delete-plugged (&optional server port alist)
665   (let* ((alist (or alist elmo-plugged-alist))
666          (alist2 alist))
667     (cond ((and (not port) (not server))
668            (setq alist nil))
669           ((not port)
670            ;; delete plugged all port of server
671            (while alist2
672              (when (string= server (caaar alist2))
673                (setq alist (delete (car alist2) alist)))
674              (setq alist2 (cdr alist2))))
675           (t
676            ;; delete plugged one port of server
677            (setq alist
678                  (delete (assoc (cons server port) alist) alist))))
679     alist))
680
681 (defun elmo-disk-usage (path)
682   "Get disk usage (bytes) in PATH."
683   (let ((file-attr
684          (condition-case () (file-attributes path) (error nil))))
685     (if file-attr
686         (if (nth 0 file-attr) ; directory
687             (let ((files (condition-case ()
688                              (directory-files path t "^[^\\.]")
689                            (error nil)))
690                   (result 0.0))
691               ;; (result (nth 7 file-attr))) ... directory size
692               (while files
693                 (setq result (+ result (or (elmo-disk-usage (car files)) 0)))
694                 (setq files (cdr files)))
695               result)
696           (float (nth 7 file-attr))))))
697
698 (defun elmo-get-last-accessed-time (path &optional dir)
699   "Return the last accessed time of PATH."
700   (let ((last-accessed (nth 4 (file-attributes (or (and dir
701                                                         (expand-file-name
702                                                          path dir))
703                                                    path)))))
704     (if last-accessed
705         (setq last-accessed (+ (* (nth 0 last-accessed)
706                                   (float 65536)) (nth 1 last-accessed)))
707       0)))
708
709 (defun elmo-get-last-modification-time (path &optional dir)
710   "Return the last accessed time of PATH."
711   (let ((last-modified (nth 5 (file-attributes (or (and dir
712                                                         (expand-file-name
713                                                          path dir))
714                                                    path)))))
715     (setq last-modified (+ (* (nth 0 last-modified)
716                               (float 65536)) (nth 1 last-modified)))))
717
718 (defun elmo-make-directory (path)
719   "Create directory recursively."
720   (let ((parent (directory-file-name (file-name-directory path))))
721     (if (null (file-directory-p parent))
722         (elmo-make-directory parent))
723     (make-directory path)
724     (if (string= path (expand-file-name elmo-msgdb-dir))
725         (set-file-modes path (+ (* 64 7) (* 8 0) 0))))) ; chmod 0700
726
727 (defun elmo-delete-directory (path &optional no-hierarchy)
728   "Delete directory recursively."
729   (if (stringp path) ; nil is not permitted.
730   (let ((dirent (directory-files path))
731         relpath abspath hierarchy)
732     (while dirent
733       (setq relpath (car dirent)
734             dirent (cdr dirent)
735             abspath (expand-file-name relpath path))
736       (when (not (string-match "^\\.\\.?$" relpath))
737         (if (eq (nth 0 (file-attributes abspath)) t)
738             (if no-hierarchy
739                 (setq hierarchy t)
740               (elmo-delete-directory abspath no-hierarchy))
741           (delete-file abspath))))
742     (unless hierarchy
743       (delete-directory path)))))
744
745 (defun elmo-list-filter (l1 l2)
746   "L1 is filter."
747   (if (eq l1 t)
748       ;; t means filter all.
749       nil
750     (if l1
751         (elmo-delete-if (lambda (x) (not (memq x l1))) l2)
752       ;; filter is nil
753       l2)))
754
755 (defsubst elmo-list-delete-if-smaller (list number)
756   (let ((ret-val (copy-sequence list)))
757     (while list
758       (if (< (car list) number)
759           (setq ret-val (delq (car list) ret-val)))
760       (setq list (cdr list)))
761     ret-val))
762
763 (defun elmo-list-diff (list1 list2 &optional mes)
764   (if mes
765       (message mes))
766   (let ((clist1 (copy-sequence list1))
767         (clist2 (copy-sequence list2)))
768     (while list2
769       (setq clist1 (delq (car list2) clist1))
770       (setq list2 (cdr list2)))
771     (while list1
772       (setq clist2 (delq (car list1) clist2))
773       (setq list1 (cdr list1)))
774     (if mes
775         (message (concat mes "done.")))
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-type (filter)
807   (` (aref (, filter) 0)))
808
809 (defmacro elmo-filter-key (filter)
810   (` (aref (, filter) 1)))
811
812 (defmacro elmo-filter-value (filter)
813   (` (aref (, filter) 2)))
814
815 (defsubst elmo-buffer-field-primitive-condition-match (condition
816                                                        number
817                                                        number-list)
818   (let (result)
819     (goto-char (point-min))
820     (cond
821      ((string= (elmo-filter-key condition) "last")
822       (setq result (<= (length (memq number number-list))
823                        (string-to-int (elmo-filter-value condition)))))
824      ((string= (elmo-filter-key condition) "first")
825       (setq result (< (- (length number-list)
826                          (length (memq number number-list)))
827                       (string-to-int (elmo-filter-value condition)))))
828      ((string= (elmo-filter-key condition) "since")
829       (let ((date (elmo-date-get-datevec (elmo-filter-value condition))))
830         (setq result
831               (string<
832                (timezone-make-sortable-date (aref date 0)
833                                             (aref date 1)
834                                             (aref date 2)
835                                             (timezone-make-time-string
836                                              (aref date 3)
837                                              (aref date 4)
838                                              (aref date 5)))
839                (timezone-make-date-sortable (std11-field-body "date"))))))
840      ((string= (elmo-filter-key condition) "before")
841       (let ((date (elmo-date-get-datevec (elmo-filter-value condition))))
842         (setq result
843               (string<
844                (timezone-make-date-sortable (std11-field-body "date"))
845                (timezone-make-sortable-date (aref date 0)
846                                             (aref date 1)
847                                             (aref date 2)
848                                             (timezone-make-time-string
849                                              (aref date 3)
850                                              (aref date 4)
851                                              (aref date 5)))))))
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       (let ((fval (std11-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 (and fval (string-match
861                                 (elmo-filter-value condition) fval))))))
862     (if (eq (elmo-filter-type condition) 'unmatch)
863         (setq result (not result)))
864     result))
865
866 (defun elmo-condition-find-key-internal (condition key)
867   (cond
868    ((vectorp condition)
869     (if (string= (elmo-filter-key condition) key)
870         (throw 'found t)))
871    ((or (eq (car condition) 'and)
872         (eq (car condition) 'or))
873     (elmo-condition-find-key-internal (nth 1 condition) key)
874     (elmo-condition-find-key-internal (nth 2 condition) key))))
875
876 (defun elmo-condition-find-key (condition key)
877   (catch 'found
878     (elmo-condition-find-key-internal condition key)))
879
880 (defun elmo-buffer-field-condition-match (condition number number-list)
881   (cond
882    ((vectorp condition)
883     (elmo-buffer-field-primitive-condition-match
884      condition number number-list))
885    ((eq (car condition) 'and)
886     (and (elmo-buffer-field-condition-match
887           (nth 1 condition) number number-list)
888          (elmo-buffer-field-condition-match
889           (nth 2 condition) number number-list)))
890    ((eq (car condition) 'or)
891     (or (elmo-buffer-field-condition-match
892          (nth 1 condition) number number-list)
893         (elmo-buffer-field-condition-match
894          (nth 2 condition) number number-list)))))
895
896 (defsubst elmo-file-field-condition-match (file condition number number-list)
897   (elmo-set-work-buf
898    (as-binary-input-file (insert-file-contents file))
899    (elmo-set-buffer-multibyte default-enable-multibyte-characters)
900    ;; Should consider charset?
901    (decode-mime-charset-region (point-min)(point-max) elmo-mime-charset)
902    (elmo-buffer-field-condition-match condition number number-list)))
903
904 (defmacro elmo-get-hash-val (string hashtable)
905   (let ((sym (list 'intern-soft string hashtable)))
906     (list 'if (list 'boundp sym)
907        (list 'symbol-value sym))))
908
909 (defmacro elmo-set-hash-val (string value hashtable)
910   (list 'set (list 'intern string hashtable) value))
911
912 (defmacro elmo-clear-hash-val (string hashtable)
913   (static-if (fboundp 'unintern)
914       (list 'unintern string hashtable)
915     (list 'makunbound (list 'intern string hashtable))))
916
917 (defmacro elmo-unintern (string)
918   "`unintern' symbol named STRING,  When can use `unintern'.
919 Emacs 19.28 or earlier does not have `unintern'."
920   (static-if (fboundp 'unintern)
921       (list 'unintern string)))
922
923 (defun elmo-make-hash (&optional hashsize)
924   "Make a new hash table which have HASHSIZE size."
925   (make-vector
926    (if hashsize 
927        (max
928         ;; Prime numbers as lengths tend to result in good
929         ;; hashing; lengths one less than a power of two are 
930         ;; also good.
931         (min
932          (let ((i 1))
933            (while (< (- i 1) hashsize)
934              (setq i (* 2 i)))
935            (- i 1))
936          elmo-hash-maximum-size)
937         elmo-hash-minimum-size)
938      elmo-hash-minimum-size)
939    0))
940
941 (defsubst elmo-mime-string (string)
942   "Normalize MIME encoded STRING."
943     (and string
944          (let (str)
945            (elmo-set-work-buf
946             (elmo-set-buffer-multibyte default-enable-multibyte-characters)
947             (setq str (eword-decode-string
948                        (decode-mime-charset-string string elmo-mime-charset)))
949             (setq str (encode-mime-charset-string str elmo-mime-charset))
950             (elmo-set-buffer-multibyte nil)
951             str))))
952
953 (defsubst elmo-collect-field (beg end downcase-field-name)
954   (save-excursion
955     (save-restriction
956       (narrow-to-region beg end)
957       (goto-char (point-min))
958       (let ((regexp (concat "\\(" std11-field-head-regexp "\\)[ \t]*"))
959             dest name body)
960         (while (re-search-forward regexp nil t)
961           (setq name (buffer-substring-no-properties
962                       (match-beginning 1)(1- (match-end 1))))
963           (if downcase-field-name
964               (setq name (downcase name)))
965           (setq body (buffer-substring-no-properties
966                       (match-end 0) (std11-field-end)))
967           (or (assoc name dest)
968               (setq dest (cons (cons name body) dest))))
969         dest))))
970
971 (defsubst elmo-collect-field-from-string (string downcase-field-name)
972   (with-temp-buffer
973     (insert string)
974     (goto-char (point-min))
975     (let ((regexp (concat "\\(" std11-field-head-regexp "\\)[ \t]*"))
976           dest name body)
977       (while (re-search-forward regexp nil t)
978         (setq name (buffer-substring-no-properties
979                     (match-beginning 1)(1- (match-end 1))))
980         (if downcase-field-name
981             (setq name (downcase name)))
982         (setq body (buffer-substring-no-properties
983                     (match-end 0) (std11-field-end)))
984         (or (assoc name dest)
985             (setq dest (cons (cons name body) dest))))
986       dest)))
987
988 (defun elmo-safe-filename (folder)
989   (elmo-replace-in-string
990    (elmo-replace-in-string
991     (elmo-replace-in-string folder "/" " ")
992     ":" "__")
993    "|" "_or_"))
994
995 (defvar elmo-filename-replace-chars nil)
996
997 (defsubst elmo-replace-string-as-filename (msgid)
998   "Replace string as filename."
999   (setq msgid (elmo-replace-in-string msgid " " "  "))
1000   (if (null elmo-filename-replace-chars)
1001       (setq elmo-filename-replace-chars
1002             (regexp-quote (mapconcat
1003                            'car elmo-filename-replace-string-alist ""))))
1004   (while (string-match (concat "[" elmo-filename-replace-chars "]")
1005                        msgid)
1006     (setq msgid (concat
1007                  (substring msgid 0 (match-beginning 0))
1008                  (cdr (assoc
1009                        (substring msgid
1010                                   (match-beginning 0) (match-end 0))
1011                        elmo-filename-replace-string-alist))
1012                  (substring msgid (match-end 0)))))
1013   msgid)
1014
1015 (defsubst elmo-recover-string-from-filename (filename)
1016   "Recover string from FILENAME."
1017   (let (tmp result)
1018     (while (string-match " " filename)
1019       (setq tmp (substring filename
1020                            (match-beginning 0)
1021                            (+ (match-end 0) 1)))
1022       (if (string= tmp "  ")
1023           (setq tmp " ")
1024         (setq tmp (car (rassoc tmp
1025                                elmo-filename-replace-string-alist))))
1026       (setq result
1027             (concat result
1028                     (substring filename 0 (match-beginning 0))
1029                     tmp))
1030       (setq filename (substring filename (+ (match-end 0) 1))))
1031     (concat result filename)))
1032
1033 (defsubst elmo-copy-file (src dst)
1034   (condition-case err
1035       (elmo-add-name-to-file src dst t)
1036     (error (copy-file src dst t))))
1037
1038 (defsubst elmo-buffer-exists-p (buffer)
1039   (if (bufferp buffer)
1040       (buffer-live-p buffer)
1041     (get-buffer buffer)))
1042
1043 (defsubst elmo-kill-buffer (buffer)
1044   (when (elmo-buffer-exists-p buffer)
1045     (kill-buffer buffer)))
1046
1047 (defun elmo-delete-if (pred lst)
1048   "Return new list contain items which don't satisfy PRED in LST."
1049   (let (result)
1050     (while lst
1051       (unless (funcall pred (car lst))
1052         (setq result (nconc result (list (car lst)))))
1053       (setq lst (cdr lst)))
1054     result))
1055
1056 (defun elmo-list-delete (list1 list2)
1057   "Delete by side effect any occurrences equal to elements of LIST1 from LIST2.
1058 Return the modified LIST2.  Deletion is done with `delete'.
1059 Write `(setq foo (elmo-list-delete bar foo))' to be sure of changing
1060 the value of `foo'."
1061   (while list1
1062     (setq list2 (delete (car list1) list2))
1063     (setq list1 (cdr list1)))
1064   list2)
1065
1066 (defun elmo-list-member (list1 list2)
1067   "If any element of LIST1 is member of LIST2, return t."
1068   (catch 'done
1069     (while list1
1070       (if (member (car list1) list2)
1071           (throw 'done t))
1072       (setq list1 (cdr list1)))))
1073
1074 (defun elmo-count-matches (regexp beg end)
1075   (let ((count 0))
1076     (save-excursion
1077       (goto-char beg)
1078       (while (re-search-forward regexp end t)
1079         (setq count (1+ count)))
1080       count)))
1081
1082 (if (fboundp 'display-error)
1083     (defalias 'elmo-display-error 'display-error)
1084   (defun elmo-display-error (error-object stream)
1085     "A tiny function to display ERROR-OBJECT to the STREAM."
1086     (let ((first t)
1087           (errobj error-object)
1088           err-mes)
1089       (while errobj
1090         (setq err-mes (concat err-mes (format
1091                                        (if (stringp (car errobj))
1092                                            "%s"
1093                                          (if (boundp 'nemacs-version)
1094                                              "%s"
1095                                            "%S")) (car errobj))))
1096         (setq errobj (cdr errobj))
1097         (if errobj (setq err-mes (concat err-mes (if first ": " ", "))))
1098         (setq first nil))
1099       (princ err-mes stream))))
1100
1101 (if (fboundp 'define-error)
1102     (defalias 'elmo-define-error 'define-error)
1103   (defun elmo-define-error (error doc &optional parents)
1104     (or parents
1105         (setq parents 'error))
1106     (let ((conds (get parents 'error-conditions)))
1107       (or conds
1108           (error "Not an error symbol: %s" error))
1109       (setplist error
1110                 (list 'error-message doc
1111                       'error-conditions (cons error conds))))))
1112
1113 (cond ((fboundp 'progress-feedback-with-label)
1114        (defalias 'elmo-display-progress 'progress-feedback-with-label))
1115       ((fboundp 'lprogress-display)
1116        (defalias 'elmo-display-progress 'lprogress-display))
1117       (t
1118        (defun elmo-display-progress (label format &optional value &rest args)
1119          "Print a progress message."
1120          (if (and (null format) (null args))
1121              (message nil)
1122            (apply (function message) (concat format " %d%%")
1123                   (nconc args (list value)))))))
1124
1125 (defun elmo-time-expire (before-time diff-time)
1126   (let* ((current (current-time))
1127          (rest (when (< (nth 1 current) (nth 1 before-time))
1128                  (expt 2 16)))
1129          diff)
1130     (setq diff
1131           (list (- (+ (car current) (if rest -1 0)) (car before-time))
1132                 (- (+ (or rest 0) (nth 1 current)) (nth 1 before-time))))
1133     (and (eq (car diff) 0)
1134          (< diff-time (nth 1 diff)))))
1135
1136 (if (fboundp 'std11-fetch-field)
1137     (defalias 'elmo-field-body 'std11-fetch-field) ;;no narrow-to-region
1138   (defalias 'elmo-field-body 'std11-field-body))
1139
1140 (defmacro elmo-string (string)
1141   "STRING without text property."
1142   (` (let ((obj (copy-sequence (, string))))
1143        (set-text-properties 0 (length obj) nil obj)
1144        obj)))
1145
1146 (defun elmo-flatten (list-of-list)
1147   "Flatten LIST-OF-LIST."
1148   (unless (null list-of-list)
1149     (append (if (and (car list-of-list)
1150                      (listp (car list-of-list)))
1151                 (car list-of-list)
1152               (list (car list-of-list)))
1153             (elmo-flatten (cdr list-of-list)))))
1154
1155 (defun elmo-y-or-n-p (prompt &optional auto default)
1156   "Same as `y-or-n-p'.
1157 But if optional argument AUTO is non-nil, DEFAULT is returned."
1158   (if auto
1159       default
1160     (y-or-n-p prompt)))
1161
1162 (defun elmo-string-member (string slist)
1163   "Return t if STRING is a member of the SLIST."
1164   (catch 'found
1165     (while slist
1166       (if (and (stringp (car slist))
1167                (string= string (car slist)))
1168           (throw 'found t))
1169       (setq slist (cdr slist)))))
1170
1171 (defun elmo-string-match-member (str list &optional case-ignore)
1172   (let ((case-fold-search case-ignore))
1173     (catch 'member
1174       (while list
1175         (if (string-match (car list) str)
1176             (throw 'member (car list)))
1177         (setq list (cdr list))))))
1178
1179 (defun elmo-string-matched-member (str list &optional case-ignore)
1180   (let ((case-fold-search case-ignore))
1181     (catch 'member
1182       (while list
1183         (if (string-match str (car list))
1184             (throw 'member (car list)))
1185         (setq list (cdr list))))))
1186
1187 (defsubst elmo-string-delete-match (string pos)
1188   (concat (substring string
1189                      0 (match-beginning pos))
1190           (substring string
1191                      (match-end pos)
1192                      (length string))))
1193
1194 (defun elmo-string-match-assoc (key alist &optional case-ignore)
1195   (let ((case-fold-search case-ignore)
1196         a)
1197     (catch 'loop
1198       (while alist
1199         (setq a (car alist))
1200         (if (and (consp a)
1201                  (stringp (car a))
1202                  (string-match key (car a)))
1203             (throw 'loop a))
1204         (setq alist (cdr alist))))))
1205
1206 (defun elmo-string-matched-assoc (key alist &optional case-ignore)
1207   (let ((case-fold-search case-ignore)
1208         a)
1209     (catch 'loop
1210       (while alist
1211         (setq a (car alist))
1212         (if (and (consp a)
1213                  (stringp (car a))
1214                  (string-match (car a) key))
1215             (throw 'loop a))
1216         (setq alist (cdr alist))))))
1217
1218 (defun elmo-string-assoc (key alist)
1219   (let (a)
1220     (catch 'loop
1221       (while alist
1222         (setq a (car alist))
1223         (if (and (consp a)
1224                  (stringp (car a))
1225                  (string= key (car a)))
1226             (throw 'loop a))
1227         (setq alist (cdr alist))))))
1228
1229 (defun elmo-string-rassoc (key alist)
1230   (let (a)
1231     (catch 'loop
1232       (while alist
1233         (setq a (car alist))
1234         (if (and (consp a)
1235                  (stringp (cdr a))
1236                  (string= key (cdr a)))
1237             (throw 'loop a))
1238         (setq alist (cdr alist))))))
1239
1240 (defun elmo-string-rassoc-all (key alist)
1241   (let (matches)
1242     (while alist
1243       (if (string= key (cdr (car alist)))
1244           (setq matches
1245                 (cons (car alist)
1246                       matches)))
1247       (setq alist (cdr alist)))
1248     matches))
1249
1250 ;;; Number set defined by OKAZAKI Tetsurou <okazaki@be.to>
1251 ;; 
1252 ;; number          ::= [0-9]+
1253 ;; beg             ::= number
1254 ;; end             ::= number
1255 ;; number-range    ::= "(" beg " . " end ")"      ;; cons cell
1256 ;; number-set-elem ::= number / number-range
1257 ;; number-set      ::= "(" *number-set-elem ")"   ;; list
1258
1259 (defun elmo-number-set-member (number number-set)
1260   "Return non-nil if NUMBER is an element of NUMBER-SET.
1261 The value is actually the tail of NUMBER-RANGE whose car contains NUMBER."
1262   (or (memq number number-set)
1263       (let (found)
1264         (while (and number-set (not found))
1265           (if (and (consp (car number-set))
1266                    (and (<= (car (car number-set)) number)
1267                         (<= number (cdr (car number-set)))))
1268               (setq found t)
1269             (setq number-set (cdr number-set))))
1270         number-set)))
1271
1272 (defun elmo-number-set-append-list (number-set list)
1273   "Append LIST of numbers to the NUMBER-SET.
1274 NUMBER-SET is altered."
1275   (let ((appended number-set))
1276     (while list
1277       (setq appended (elmo-number-set-append appended (car list)))
1278       (setq list (cdr list)))
1279     appended))
1280
1281 (defun elmo-number-set-append (number-set number)
1282   "Append NUMBER to the NUMBER-SET.
1283 NUMBER-SET is altered."
1284   (let ((number-set-1 number-set)
1285         found elem)
1286     (while (and number-set (not found))
1287       (setq elem (car number-set))
1288       (cond
1289        ((and (consp elem)
1290              (eq (+ 1 (cdr elem)) number))
1291         (setcdr elem number)
1292         (setq found t))
1293        ((and (integerp elem)
1294              (eq (+ 1 elem) number))
1295         (setcar number-set (cons elem number))
1296         (setq found t))
1297        ((or (and (integerp elem) (eq elem number))
1298             (and (consp elem)
1299                  (<= (car elem) number)
1300                  (<= number (cdr elem))))
1301         (setq found t)))
1302       (setq number-set (cdr number-set)))
1303     (if (not found)
1304         (setq number-set-1 (nconc number-set-1 (list number))))
1305     number-set-1))
1306
1307 (defun elmo-number-set-to-number-list (number-set)
1308   "Return a number list which corresponds to NUMBER-SET."
1309   (let (number-list elem i)
1310     (while number-set
1311       (setq elem (car number-set))
1312       (cond
1313        ((consp elem)
1314         (setq i (car elem))
1315         (while (<= i (cdr elem))
1316           (setq number-list (cons i number-list))
1317           (incf i)))
1318        ((integerp elem)
1319         (setq number-list (cons elem number-list))))
1320       (setq number-set (cdr number-set)))
1321     (nreverse number-list)))
1322
1323 (defcustom elmo-list-subdirectories-ignore-regexp "^\\(\\.\\.?\\|[0-9]+\\)$"
1324   "*Regexp to filter subfolders."
1325   :type 'regexp
1326   :group 'elmo)
1327
1328 (defun elmo-list-subdirectories (directory file one-level)
1329   (let ((root (zerop (length file)))
1330         (w32-get-true-file-link-count t) ; for Meadow
1331         files attr dirs dir)
1332     (setq files (directory-files (setq dir (expand-file-name file directory))))
1333     (while files
1334       (if (and (not (string-match elmo-list-subdirectories-ignore-regexp
1335                                   (car files)))
1336                (car (setq attr (file-attributes (expand-file-name 
1337                                                  (car files) dir)))))
1338           (if (and (not one-level)
1339                    (and elmo-have-link-count (< 2 (nth 1 attr))))
1340               (setq dirs
1341                     (nconc dirs
1342                            (elmo-list-subdirectories
1343                             directory
1344                             (concat file
1345                                     (and (not root) elmo-path-sep)
1346                                     (car files))
1347                             one-level)))
1348             (setq dirs (nconc dirs
1349                               (list
1350                                (concat file
1351                                        (and (not root) elmo-path-sep)
1352                                        (car files)))))))
1353       (setq files (cdr files)))
1354     (nconc (and (not root) (list file)) dirs)))
1355
1356 (defun elmo-parse (string regexp &optional matchn)
1357   (or matchn (setq matchn 1))
1358   (let (list)
1359     (store-match-data nil)
1360     (while (string-match regexp string (match-end 0))
1361       (setq list (cons (substring string (match-beginning matchn)
1362                                   (match-end matchn)) list)))
1363     (nreverse list)))
1364
1365 (defconst elmo-warning-buffer-name "*elmo warning*")
1366
1367 (defun elmo-warning (&rest args)
1368   "Display a warning, making warning message by passing all args to `insert'."
1369   (with-current-buffer (get-buffer-create elmo-warning-buffer-name)
1370     (goto-char (point-max))
1371     (apply 'insert (append args '("\n")))
1372     (recenter 1))
1373   (display-buffer elmo-warning-buffer-name))
1374
1375 (defvar elmo-obsolete-variable-alist nil)
1376 (defvar elmo-obsolete-variable-show-warnings nil)
1377
1378 (defun elmo-define-obsolete-variable (obsolete var)
1379   "Define obsolete variable.
1380 OBSOLETE is a symbol for obsolete variable.
1381 VAR is a symbol for new variable.
1382 Definition is stored in `elmo-obsolete-variable-alist'."
1383   (let ((pair (assq var elmo-obsolete-variable-alist)))
1384     (if pair
1385         (setcdr pair obsolete)
1386       (setq elmo-obsolete-variable-alist
1387             (cons (cons var obsolete)
1388                   elmo-obsolete-variable-alist)))))
1389
1390 (defun elmo-resque-obsolete-variable (obsolete var)
1391   "Resque obsolete variable OBSOLETE as VAR.
1392 If `elmo-obsolete-variable-show-warnings' is non-nil, show warning message."
1393   (when (boundp obsolete)
1394     (set var (symbol-value obsolete))
1395     (if elmo-obsolete-variable-show-warnings
1396         (elmo-warning (format "%s is obsolete. Use %s instead."
1397                               (symbol-name obsolete)
1398                               (symbol-name var))))))
1399
1400 (defun elmo-resque-obsolete-variables (&optional alist)
1401   "Resque obsolete variables in ALIST.
1402 ALIST is a list of cons cell of
1403 \(OBSOLETE-VARIABLE-SYMBOL . NEW-VARIABLE-SYMBOL\).
1404 If ALIST is nil, `elmo-obsolete-variable-alist' is used."
1405   (dolist (pair elmo-obsolete-variable-alist)
1406     (elmo-resque-obsolete-variable (cdr pair)
1407                                    (car pair))))
1408
1409
1410 (require 'product)
1411 (product-provide (provide 'elmo-util) (require 'elmo-version))
1412
1413 ;;; elmo-util.el ends here