* elmo-msgdb.el (elmo-msgdb-set-status): Fixed logic (new to read).
[elisp/wanderlust.git] / elmo / elmo-msgdb.el
1 ;;; elmo-msgdb.el --- Message Database for ELMO.
2
3 ;; Copyright (C) 1998,1999,2000 Yuuichi Teranishi <teranisi@gohome.org>
4 ;; Copyright (C) 2000           Masahiro MURATA <muse@ba2.so-net.ne.jp>
5
6 ;; Author: Yuuichi Teranishi <teranisi@gohome.org>
7 ;;      Masahiro MURATA <muse@ba2.so-net.ne.jp>
8 ;; Keywords: mail, net news
9
10 ;; This file is part of ELMO (Elisp Library for Message Orchestration).
11
12 ;; This program is free software; you can redistribute it and/or modify
13 ;; it under the terms of the GNU General Public License as published by
14 ;; the Free Software Foundation; either version 2, or (at your option)
15 ;; any later version.
16 ;;
17 ;; This program is distributed in the hope that it will be useful,
18 ;; but WITHOUT ANY WARRANTY; without even the implied warranty of
19 ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
20 ;; GNU General Public License for more details.
21 ;;
22 ;; You should have received a copy of the GNU General Public License
23 ;; along with GNU Emacs; see the file COPYING.  If not, write to the
24 ;; Free Software Foundation, Inc., 59 Temple Place - Suite 330,
25 ;; Boston, MA 02111-1307, USA.
26 ;;
27
28 ;;; Commentary:
29 ;;
30
31 ;;; Code:
32 ;;
33
34 (eval-when-compile (require 'cl))
35 (require 'elmo-vars)
36 (require 'elmo-util)
37 (require 'emu)
38 (require 'std11)
39 (require 'mime)
40
41 (defcustom elmo-msgdb-new-mark "N"
42   "Mark for new message."
43   :type '(string :tag "Mark")
44   :group 'elmo)
45
46 (defcustom elmo-msgdb-unread-uncached-mark "U"
47   "Mark for unread and uncached message."
48   :type '(string :tag "Mark")
49   :group 'elmo)
50
51 (defcustom elmo-msgdb-unread-cached-mark "!"
52   "Mark for unread but already cached message."
53   :type '(string :tag "Mark")
54   :group 'elmo)
55
56 (defcustom elmo-msgdb-read-uncached-mark "u"
57   "Mark for read but uncached message."
58   :type '(string :tag "Mark")
59   :group 'elmo)
60
61 ;; Not implemented yet.
62 (defcustom elmo-msgdb-answered-cached-mark "&"
63   "Mark for answered and cached message."
64   :type '(string :tag "Mark")
65   :group 'elmo)
66
67 (defcustom elmo-msgdb-answered-uncached-mark "A"
68   "Mark for answered but cached message."
69   :type '(string :tag "Mark")
70   :group 'elmo)
71
72 (defcustom elmo-msgdb-important-mark"$"
73   "Mark for important message."
74   :type '(string :tag "Mark")
75   :group 'elmo)
76
77 ;;; MSGDB interface.
78 (defun elmo-load-msgdb (path)
79   "Load the MSGDB from PATH."
80   (let ((inhibit-quit t))
81     (elmo-make-msgdb (elmo-msgdb-overview-load path)
82                      (elmo-msgdb-number-load path)
83                      (elmo-msgdb-mark-load path))))
84
85 (defun elmo-make-msgdb (&optional overview number-alist mark-alist)
86   "Make a MSGDB."
87   (let ((msgdb (list overview number-alist mark-alist nil)))
88     (elmo-msgdb-make-index msgdb)
89     msgdb))
90
91 (defsubst elmo-msgdb-get-mark (msgdb number)
92   "Get mark string from MSGDB which corresponds to the message with NUMBER."
93   (cadr (elmo-get-hash-val (format "#%d" number)
94                            (elmo-msgdb-get-mark-hashtb msgdb))))
95
96 (defsubst elmo-msgdb-set-mark (msgdb number mark)
97   "Set MARK of the message with NUMBER in the MSGDB.
98 if MARK is nil, mark is removed."
99   (let ((elem (elmo-get-hash-val (format "#%d" number)
100                                  (elmo-msgdb-get-mark-hashtb msgdb))))
101     (if elem
102         (if mark
103             ;; Set mark of the elem
104             (setcar (cdr elem) mark)
105           ;; Delete elem from mark-alist
106           (elmo-msgdb-set-mark-alist
107            msgdb
108            (delq elem (elmo-msgdb-get-mark-alist msgdb)))
109           (elmo-clear-hash-val (format "#%d" number)
110                                (elmo-msgdb-get-mark-hashtb msgdb)))
111       (when mark
112         ;; Append new element.
113         (elmo-msgdb-set-mark-alist
114          msgdb
115          (nconc
116           (elmo-msgdb-get-mark-alist msgdb)
117           (list (setq elem (list number mark)))))
118         (elmo-set-hash-val (format "#%d" number) elem
119                            (elmo-msgdb-get-mark-hashtb msgdb))))))
120
121 (defun elmo-msgdb-set-cached (msgdb number cached)
122   "Set message cache status."
123   (let* ((cur-mark (elmo-msgdb-get-mark msgdb number))
124          (cur-status (cond
125                       ((string= cur-mark elmo-msgdb-important-mark)
126                        'important)
127                       ((member cur-mark (elmo-msgdb-answered-marks))
128                        'answered)
129                       ((not (member cur-mark (elmo-msgdb-unread-marks)))
130                        'read)))
131          (cur-cached (not (member cur-mark (elmo-msgdb-uncached-marks)))))
132     (unless (eq (not cached) (not cur-cached))
133       (case cur-status
134         (read
135          (elmo-msgdb-set-mark msgdb number
136                               (unless cached
137                                 elmo-msgdb-read-uncached-mark)))
138         (important nil)
139         (answered
140          (elmo-msgdb-set-mark msgdb number
141                               (if cached
142                                   elmo-msgdb-answered-cached-mark
143                                 elmo-msgdb-answered-uncached-mark)))
144         (t
145          (elmo-msgdb-set-mark msgdb number
146                               (if cached
147                                   elmo-msgdb-unread-cached-mark
148                                 elmo-msgdb-unread-uncached-mark)))))))
149
150 (defun elmo-msgdb-set-status (msgdb folder number status)
151   "Set message status.
152 MSGDB is the ELMO msgdb.
153 FOLDER is a ELMO folder structure.
154 NUMBER is a message number to be set status.
155 STATUS is a symbol which is one of the following:
156 `read'      ... Messages which are already read.
157 `important' ... Messages which are marked as important.
158 `answered'  ... Messages which are marked as answered."
159   (let* ((cur-mark (elmo-msgdb-get-mark msgdb number))
160          (use-cache (elmo-message-use-cache-p folder number))
161          (cur-status (cond
162                       ((string= cur-mark elmo-msgdb-important-mark)
163                        'important)
164                       ((member cur-mark (elmo-msgdb-answered-marks))
165                        'answered)
166                       ((not (member cur-mark (elmo-msgdb-unread-marks)))
167                        'read)))
168          (cur-cached (not (member cur-mark (elmo-msgdb-uncached-marks))))
169          mark-modified)
170     (case status
171       (read
172        (case cur-status
173          ((read important answered))
174          (t (elmo-msgdb-set-mark msgdb number
175                                  (if (and use-cache (not cur-cached))
176                                      elmo-msgdb-read-uncached-mark))
177             (setq mark-modified t))))
178       (important
179        (unless (eq cur-status 'important)
180          (elmo-msgdb-set-mark msgdb number elmo-msgdb-important-mark)
181          (setq mark-modified t)))
182       (answered
183        (unless (or (eq cur-status 'answered) (eq cur-status 'important))
184          (elmo-msgdb-set-mark msgdb number
185                               (if cur-cached
186                                   (if use-cache
187                                       elmo-msgdb-answered-cached-mark
188                                     elmo-msgdb-answered-uncached-mark)
189                                 elmo-msgdb-answered-uncached-mark)))
190        (setq mark-modified t)))
191     (if mark-modified (elmo-folder-set-mark-modified-internal folder t))))
192
193 (defun elmo-msgdb-unset-status (msgdb folder number status)
194   "Unset message status.
195 MSGDB is the ELMO msgdb.
196 FOLDER is a ELMO folder structure.
197 NUMBER is a message number to be set status.
198 STATUS is a symbol which is one of the following:
199 `read'      ... Messages which are already read.
200 `important' ... Messages which are marked as important.
201 `answered'  ... Messages which are marked as answered."
202   (let* ((cur-mark (elmo-msgdb-get-mark msgdb number))
203          (use-cache (elmo-message-use-cache-p folder number))
204          (cur-status (cond
205                       ((string= cur-mark elmo-msgdb-important-mark)
206                        'important)
207                       ((member cur-mark (elmo-msgdb-answered-marks))
208                        'answered)
209                       ((not (member cur-mark (elmo-msgdb-unread-marks)))
210                        'read)))
211          (cur-cached (not (member cur-mark (elmo-msgdb-uncached-marks)))))
212     (case status
213       (read
214        (if (eq cur-status 'read)
215            (elmo-msgdb-set-mark msgdb number
216                                 (if (and cur-cached use-cache)
217                                     elmo-msgdb-unread-cached-mark
218                                   elmo-msgdb-unread-uncached-mark))))
219       (important
220        (if (eq cur-status 'important)
221            (elmo-msgdb-set-mark msgdb number nil)))
222       (answered
223        (if (eq cur-status 'answered)
224            (elmo-msgdb-set-mark msgdb number
225                                 (if (and cur-cached (not use-cache))
226                                     elmo-msgdb-read-uncached-mark)))))))
227
228 (defvar elmo-msgdb-unread-marks-internal nil)
229 (defsubst elmo-msgdb-unread-marks ()
230   "Return an unread mark list"
231   (or elmo-msgdb-unread-marks-internal
232       (setq elmo-msgdb-unread-marks-internal
233             (list elmo-msgdb-new-mark
234                   elmo-msgdb-unread-uncached-mark
235                   elmo-msgdb-unread-cached-mark))))
236
237 (defvar elmo-msgdb-answered-marks-internal nil)
238 (defsubst elmo-msgdb-answered-marks ()
239   "Return an answered mark list"
240   (or elmo-msgdb-answered-marks-internal
241       (setq elmo-msgdb-answered-marks-internal
242             (list elmo-msgdb-answered-cached-mark
243                   elmo-msgdb-answered-uncached-mark))))
244
245 (defvar elmo-msgdb-uncached-marks-internal nil)
246 (defsubst elmo-msgdb-uncached-marks ()
247   (or elmo-msgdb-uncached-marks-internal
248       (setq elmo-msgdb-uncached-marks-internal
249             (list elmo-msgdb-new-mark
250                   elmo-msgdb-answered-uncached-mark
251                   elmo-msgdb-unread-uncached-mark
252                   elmo-msgdb-read-uncached-mark))))
253
254 (defsubst elmo-msgdb-count-marks (msgdb)
255   (let ((new 0)
256         (unreads 0)
257         (answered 0))
258     (dolist (elem (elmo-msgdb-get-mark-alist msgdb))
259       (cond
260        ((string= (cadr elem) elmo-msgdb-new-mark)
261         (incf new))
262        ((member (cadr elem) (elmo-msgdb-unread-marks))
263         (incf unreads))
264        ((member (cadr elem) (elmo-msgdb-answered-marks))
265         (incf answered))))
266     (list new unreads answered)))
267
268 (defsubst elmo-msgdb-get-number (msgdb message-id)
269   "Get number of the message which corrensponds to MESSAGE-ID from MSGDB."
270   (elmo-msgdb-overview-entity-get-number
271    (elmo-msgdb-overview-get-entity message-id msgdb)))
272
273 (defsubst elmo-msgdb-get-field (msgdb number field)
274   "Get FIELD value of the message with NUMBER from MSGDB."
275   (case field
276     (message-id (elmo-msgdb-overview-entity-get-id
277                  (elmo-msgdb-overview-get-entity
278                   number msgdb)))
279     (subject (elmo-msgdb-overview-entity-get-subject
280               (elmo-msgdb-overview-get-entity
281                number msgdb)))
282     (size (elmo-msgdb-overview-entity-get-size
283            (elmo-msgdb-overview-get-entity
284             number msgdb)))
285     (date (elmo-msgdb-overview-entity-get-date
286            (elmo-msgdb-overview-get-entity
287             number msgdb)))
288     (to (elmo-msgdb-overview-entity-get-to
289          (elmo-msgdb-overview-get-entity
290           number msgdb)))
291     (cc (elmo-msgdb-overview-entity-get-cc
292          (elmo-msgdb-overview-get-entity
293           number msgdb)))))
294
295 (defsubst elmo-msgdb-append (msgdb msgdb-append)
296   (list
297    (nconc (car msgdb) (car msgdb-append))
298    (nconc (cadr msgdb) (cadr msgdb-append))
299    (nconc (caddr msgdb) (caddr msgdb-append))
300    (elmo-msgdb-make-index
301     msgdb
302     (elmo-msgdb-get-overview msgdb-append)
303     (elmo-msgdb-get-mark-alist msgdb-append))))
304
305 (defsubst elmo-msgdb-clear (&optional msgdb)
306   (if msgdb
307       (list
308        (setcar msgdb nil)
309        (setcar (cdr msgdb) nil)
310        (setcar (cddr msgdb) nil)
311        (setcar (nthcdr 3 msgdb) nil))
312     (list nil nil nil nil)))
313
314 (defun elmo-msgdb-delete-msgs (msgdb msgs)
315   "Delete MSGS from MSGDB
316 content of MSGDB is changed."
317   (let* ((overview (car msgdb))
318          (number-alist (cadr msgdb))
319          (mark-alist (caddr msgdb))
320          (index (elmo-msgdb-get-index msgdb))
321          (newmsgdb (list overview number-alist mark-alist index))
322          ov-entity)
323     ;; remove from current database.
324     (while msgs
325       (setq overview
326             (delq
327              (setq ov-entity
328                    (elmo-msgdb-overview-get-entity (car msgs) newmsgdb))
329              overview))
330       (setq number-alist (delq (assq (car msgs) number-alist) number-alist))
331       (setq mark-alist (delq (assq (car msgs) mark-alist) mark-alist))
332       ;;
333       (when index (elmo-msgdb-clear-index msgdb ov-entity))
334       (setq msgs (cdr msgs)))
335     (setcar msgdb overview)
336     (setcar (cdr msgdb) number-alist)
337     (setcar (cddr msgdb) mark-alist)
338     (setcar (nthcdr 3 msgdb) index)
339     t)) ;return value
340
341 (defun elmo-msgdb-sort-by-date (msgdb)
342   (message "Sorting...")
343   (let ((overview (elmo-msgdb-get-overview msgdb)))
344     (setq overview (elmo-msgdb-overview-sort-by-date overview))
345     (message "Sorting...done")
346     (list overview (nth 1 msgdb)(nth 2 msgdb))))
347
348 (defun elmo-msgdb-make-entity (&rest args)
349   "Make an msgdb entity."
350   (cons (plist-get args :message-id)
351         (vector (plist-get args :number)
352                 (plist-get args :references)
353                 (plist-get args :from)
354                 (plist-get args :subject)
355                 (plist-get args :date)
356                 (plist-get args :to)
357                 (plist-get args :cc)
358                 (plist-get args :size)
359                 (plist-get args :extra))))
360
361 ;;;
362 (defsubst elmo-msgdb-append-element (list element)
363   (if list
364 ;;;   (append list (list element))
365       (nconc list (list element))
366     ;; list is nil
367     (list element)))
368
369 (defsubst elmo-msgdb-get-overview (msgdb)
370   (car msgdb))
371 (defsubst elmo-msgdb-get-number-alist (msgdb)
372   (cadr msgdb))
373 (defsubst elmo-msgdb-get-mark-alist (msgdb)
374   (caddr msgdb))
375 ;(defsubst elmo-msgdb-get-location (msgdb)
376 ;  (cadddr msgdb))
377
378 (defsubst elmo-msgdb-get-index (msgdb)
379   (nth 3 msgdb))
380
381 (defsubst elmo-msgdb-get-entity-hashtb (msgdb)
382   (car (nth 3 msgdb)))
383
384 (defsubst elmo-msgdb-get-mark-hashtb (msgdb)
385   (cdr (nth 3 msgdb)))
386
387 ;;
388 ;; number <-> Message-ID handling
389 ;;
390 (defsubst elmo-msgdb-number-add (alist number id)
391   (let ((ret-val alist))
392     (setq ret-val
393           (elmo-msgdb-append-element ret-val (cons number id)))
394     ret-val))
395
396 ;;;
397 ;; parsistent mark handling
398 ;; (for global!)
399
400 (defvar elmo-msgdb-global-mark-alist nil)
401
402 (defun elmo-msgdb-global-mark-delete (msgid)
403   (let* ((path (expand-file-name
404                 elmo-msgdb-global-mark-filename
405                 elmo-msgdb-directory))
406          (malist (or elmo-msgdb-global-mark-alist
407                      (setq elmo-msgdb-global-mark-alist
408                            (elmo-object-load path))))
409          match)
410     (when (setq match (assoc msgid malist))
411       (setq elmo-msgdb-global-mark-alist
412             (delete match elmo-msgdb-global-mark-alist))
413       (elmo-object-save path elmo-msgdb-global-mark-alist))))
414
415 (defun elmo-msgdb-global-mark-set (msgid mark)
416   (let* ((path (expand-file-name
417                 elmo-msgdb-global-mark-filename
418                 elmo-msgdb-directory))
419          (malist (or elmo-msgdb-global-mark-alist
420                      (setq elmo-msgdb-global-mark-alist
421                            (elmo-object-load path))))
422          match)
423     (if (setq match (assoc msgid malist))
424         (setcdr match mark)
425       (setq elmo-msgdb-global-mark-alist
426             (nconc elmo-msgdb-global-mark-alist
427                    (list (cons msgid mark)))))
428     (elmo-object-save path elmo-msgdb-global-mark-alist)))
429
430 (defun elmo-msgdb-global-mark-get (msgid)
431   (cdr (assoc msgid (or elmo-msgdb-global-mark-alist
432                         (setq elmo-msgdb-global-mark-alist
433                               (elmo-object-load
434                                (expand-file-name
435                                 elmo-msgdb-global-mark-filename
436                                 elmo-msgdb-directory)))))))
437
438 ;;;
439 ;; persistent mark handling
440 ;; (for each folder)
441
442 (defun elmo-msgdb-mark-append (alist id mark)
443   "Append mark."
444   (setq alist (elmo-msgdb-append-element alist
445                                          (list id mark))))
446
447 (defun elmo-msgdb-seen-list (msgdb)
448   "Get SEEN-MSGID-LIST from MSGDB."
449   (let ((ov (elmo-msgdb-get-overview msgdb))
450         mark seen-list)
451     (while ov
452       (if (setq mark (elmo-msgdb-get-mark
453                       msgdb
454                       (elmo-msgdb-overview-entity-get-number (car ov))))
455           (if (and mark (not (member mark
456                                      (elmo-msgdb-unread-marks))))
457               (setq seen-list (cons
458                                (elmo-msgdb-overview-entity-get-id (car ov))
459                                seen-list)))
460         (setq seen-list (cons
461                          (elmo-msgdb-overview-entity-get-id (car ov))
462                          seen-list)))
463       (setq ov (cdr ov)))
464     seen-list))
465
466 ;;
467 ;; mime decode cache
468
469 (defvar elmo-msgdb-decoded-cache-hashtb nil)
470 (make-variable-buffer-local 'elmo-msgdb-decoded-cache-hashtb)
471
472 (defsubst elmo-msgdb-get-decoded-cache (string)
473   (if elmo-use-decoded-cache
474       (let ((hashtb (or elmo-msgdb-decoded-cache-hashtb
475                         (setq elmo-msgdb-decoded-cache-hashtb
476                               (elmo-make-hash 2048))))
477             decoded)
478         (or (elmo-get-hash-val string hashtb)
479             (progn
480               (elmo-set-hash-val
481                string
482                (setq decoded
483                      (decode-mime-charset-string string elmo-mime-charset))
484                hashtb)
485               decoded)))
486     (decode-mime-charset-string string elmo-mime-charset)))
487
488 ;;
489 ;; overview handling
490 ;;
491
492 (defsubst elmo-msgdb-get-field-value (field-name beg end buffer)
493   (save-excursion
494     (save-restriction
495       (set-buffer buffer)
496       (narrow-to-region beg end)
497       (elmo-field-body field-name))))
498
499 (defun elmo-multiple-field-body (name &optional boundary)
500   (save-excursion
501     (save-restriction
502       (std11-narrow-to-header boundary)
503       (goto-char (point-min))
504       (let ((case-fold-search t)
505             (field-body nil))
506         (while (re-search-forward (concat "^" name ":[ \t]*") nil t)
507           (setq field-body
508                 (nconc field-body
509                        (list (buffer-substring-no-properties
510                               (match-end 0) (std11-field-end))))))
511         field-body))))
512
513 (defun elmo-multiple-fields-body-list (field-names &optional boundary)
514   "Return list of each field-bodies of FIELD-NAMES of the message header
515 in current buffer. If BOUNDARY is not nil, it is used as message
516 header separator."
517   (save-excursion
518     (save-restriction
519       (std11-narrow-to-header boundary)
520       (let* ((case-fold-search t)
521              (s-rest field-names)
522              field-name field-body)
523         (while (setq field-name (car s-rest))
524           (goto-char (point-min))
525           (while (re-search-forward (concat "^" field-name ":[ \t]*") nil t)
526             (setq field-body
527                   (nconc field-body
528                          (list (buffer-substring-no-properties
529                                 (match-end 0) (std11-field-end))))))
530           (setq s-rest (cdr s-rest)))
531         field-body))))
532
533 (defsubst elmo-msgdb-remove-field-string (string)
534   (if (string-match (concat std11-field-head-regexp "[ \t]*") string)
535       (substring string (match-end 0))
536     string))
537
538 (defsubst elmo-msgdb-get-last-message-id (string)
539   (if string
540       (save-match-data
541         (let (beg)
542           (elmo-set-work-buf
543            (insert string)
544            (goto-char (point-max))
545            (when (search-backward "<" nil t)
546              (setq beg (point))
547              (if (search-forward ">" nil t)
548                  (elmo-replace-in-string
549                   (buffer-substring beg (point)) "\n[ \t]*" ""))))))))
550
551 (defun elmo-msgdb-number-load (dir)
552   (elmo-object-load
553    (expand-file-name elmo-msgdb-number-filename dir)))
554
555 (defun elmo-msgdb-overview-load (dir)
556   (elmo-object-load
557    (expand-file-name elmo-msgdb-overview-filename dir)))
558
559 (defun elmo-msgdb-mark-load (dir)
560   (elmo-object-load
561    (expand-file-name elmo-msgdb-mark-filename dir)))
562
563 (defsubst elmo-msgdb-seen-load (dir)
564   (elmo-object-load (expand-file-name
565                      elmo-msgdb-seen-filename
566                      dir)))
567
568 (defun elmo-msgdb-number-save (dir obj)
569   (elmo-object-save
570    (expand-file-name elmo-msgdb-number-filename dir)
571    obj))
572
573 (defun elmo-msgdb-mark-save (dir obj)
574   (elmo-object-save
575    (expand-file-name elmo-msgdb-mark-filename dir)
576    obj))
577
578 (defun elmo-msgdb-change-mark (msgdb before after)
579   "Set the BEFORE marks to AFTER."
580   (let ((mark-alist (elmo-msgdb-get-mark-alist msgdb))
581         entity)
582     (while mark-alist
583       (setq entity (car mark-alist))
584       (when (string= (cadr entity) before)
585         (setcar (cdr entity) after))
586       (setq mark-alist (cdr mark-alist)))))
587
588 (defsubst elmo-msgdb-mark (status cached)
589   (case status
590     (unread
591      (if cached
592          elmo-msgdb-unread-cached-mark
593        elmo-msgdb-unread-uncached-mark))
594     (important
595      elmo-msgdb-important-mark)
596     (answered
597      (if cached
598          elmo-msgdb-answered-cached-mark
599        elmo-msgdb-answered-uncached-mark))))
600
601 (defsubst elmo-msgdb-seen-save (dir obj)
602   (elmo-object-save
603    (expand-file-name elmo-msgdb-seen-filename dir)
604    obj))
605
606 (defsubst elmo-msgdb-overview-save (dir overview)
607   (elmo-object-save
608    (expand-file-name elmo-msgdb-overview-filename dir)
609    overview))
610
611 (defun elmo-msgdb-match-condition-primitive (condition mark entity numbers)
612   (catch 'unresolved
613     (let ((key (elmo-filter-key condition))
614           (case-fold-search t)
615           result)
616       (cond
617        ((string= key "last")
618         (setq result (<= (length (memq
619                                   (elmo-msgdb-overview-entity-get-number
620                                    entity)
621                                   numbers))
622                          (string-to-int (elmo-filter-value condition)))))
623        ((string= key "first")
624         (setq result (< (-
625                          (length numbers)
626                          (length (memq
627                                   (elmo-msgdb-overview-entity-get-number
628                                    entity)
629                                   numbers)))
630                         (string-to-int (elmo-filter-value condition)))))
631        ((string= key "mark")
632         (setq result
633               (cond
634                ((string= (elmo-filter-value condition) "any")
635                 (not (or (null mark)
636                          (string= mark elmo-msgdb-read-uncached-mark))))
637 ;;        (member mark (append (elmo-msgdb-answered-marks)
638 ;;                             (list elmo-msgdb-important-mark)
639 ;;                             (elmo-msgdb-unread-marks))))
640                ((string= (elmo-filter-value condition) "unread")
641                 (member mark (elmo-msgdb-unread-marks)))
642                ((string= (elmo-filter-value condition) "important")
643                 (string= mark elmo-msgdb-important-mark))
644                ((string= (elmo-filter-value condition) "answered")
645                 (member mark (elmo-msgdb-answered-marks))))))
646        ((string= key "from")
647         (setq result (string-match
648                       (elmo-filter-value condition)
649                       (elmo-msgdb-overview-entity-get-from entity))))
650        ((string= key "subject")
651         (setq result (string-match
652                       (elmo-filter-value condition)
653                       (elmo-msgdb-overview-entity-get-subject entity))))
654        ((string= key "to")
655         (setq result (string-match
656                       (elmo-filter-value condition)
657                       (elmo-msgdb-overview-entity-get-to entity))))
658        ((string= key "cc")
659         (setq result (string-match
660                       (elmo-filter-value condition)
661                       (elmo-msgdb-overview-entity-get-cc entity))))
662        ((or (string= key "since")
663             (string= key "before"))
664         (let ((field-date (elmo-date-make-sortable-string
665                            (timezone-fix-time
666                             (elmo-msgdb-overview-entity-get-date entity)
667                             (current-time-zone) nil)))
668               (specified-date
669                (elmo-date-make-sortable-string
670                 (elmo-date-get-datevec
671                  (elmo-filter-value condition)))))
672           (setq result (if (string= key "since")
673                            (or (string= specified-date field-date)
674                                (string< specified-date field-date))
675                          (string< field-date specified-date)))))
676        ((member key elmo-msgdb-extra-fields)
677         (let ((extval (elmo-msgdb-overview-entity-get-extra-field entity key)))
678           (when (stringp extval)
679             (setq result (string-match
680                           (elmo-filter-value condition)
681                           extval)))))
682        (t
683         (throw 'unresolved condition)))
684       (if (eq (elmo-filter-type condition) 'unmatch)
685           (not result)
686         result))))
687
688 (defun elmo-msgdb-match-condition-internal (condition mark entity numbers)
689   (cond
690    ((vectorp condition)
691     (elmo-msgdb-match-condition-primitive condition mark entity numbers))
692    ((eq (car condition) 'and)
693     (let ((lhs (elmo-msgdb-match-condition-internal (nth 1 condition)
694                                                     mark entity numbers)))
695       (cond
696        ((elmo-filter-condition-p lhs)
697         (let ((rhs (elmo-msgdb-match-condition-internal
698                     (nth 2 condition) mark entity numbers)))
699           (cond ((elmo-filter-condition-p rhs)
700                  (list 'and lhs rhs))
701                 (rhs
702                  lhs))))
703        (lhs
704         (elmo-msgdb-match-condition-internal (nth 2 condition)
705                                              mark entity numbers)))))
706    ((eq (car condition) 'or)
707     (let ((lhs (elmo-msgdb-match-condition-internal (nth 1 condition)
708                                                     mark entity numbers)))
709       (cond
710        ((elmo-filter-condition-p lhs)
711         (let ((rhs (elmo-msgdb-match-condition-internal (nth 2 condition)
712                                                         mark entity numbers)))
713           (cond ((elmo-filter-condition-p rhs)
714                  (list 'or lhs rhs))
715                 (rhs
716                  t)
717                 (t
718                  lhs))))
719        (lhs
720         t)
721        (t
722         (elmo-msgdb-match-condition-internal (nth 2 condition)
723                                              mark entity numbers)))))))
724
725 (defun elmo-msgdb-match-condition (msgdb condition number numbers)
726   "Check whether the condition of the message is satisfied or not.
727 MSGDB is the msgdb to search from.
728 CONDITION is the search condition.
729 NUMBER is the message number to check.
730 NUMBERS is the target message number list.
731 Return CONDITION itself if no entity exists in msgdb."
732   (let ((entity (elmo-msgdb-overview-get-entity number msgdb)))
733     (if entity
734         (elmo-msgdb-match-condition-internal condition
735                                              (elmo-msgdb-get-mark msgdb number)
736                                              entity numbers)
737       condition)))
738
739 (defsubst elmo-msgdb-set-overview (msgdb overview)
740   (setcar msgdb overview))
741
742 (defsubst elmo-msgdb-set-number-alist (msgdb number-alist)
743   (setcar (cdr msgdb) number-alist))
744
745 (defsubst elmo-msgdb-set-mark-alist (msgdb mark-alist)
746   (setcar (cddr msgdb) mark-alist))
747
748 (defsubst elmo-msgdb-set-index (msgdb index)
749   (setcar (cdddr msgdb) index))
750
751 (defsubst elmo-msgdb-overview-entity-get-references (entity)
752   (and entity (aref (cdr entity) 1)))
753
754 (defsubst elmo-msgdb-overview-entity-set-references (entity references)
755   (and entity (aset (cdr entity) 1 references))
756   entity)
757
758 ;; entity -> parent-entity
759 (defsubst elmo-msgdb-overview-get-parent-entity (entity database)
760   (setq entity (elmo-msgdb-overview-entity-get-references entity))
761   ;; entity is parent-id.
762   (and entity (assoc entity database)))
763
764 (defsubst elmo-msgdb-get-parent-entity (entity msgdb)
765   (setq entity (elmo-msgdb-overview-entity-get-references entity))
766   ;; entity is parent-id.
767   (and entity (elmo-msgdb-overview-get-entity entity msgdb)))
768
769 (defsubst elmo-msgdb-overview-entity-get-number (entity)
770   (and entity (aref (cdr entity) 0)))
771
772 (defsubst elmo-msgdb-overview-entity-get-from-no-decode (entity)
773   (and entity (aref (cdr entity) 2)))
774
775 (defsubst elmo-msgdb-overview-entity-get-from (entity)
776   (and entity
777        (aref (cdr entity) 2)
778        (elmo-msgdb-get-decoded-cache (aref (cdr entity) 2))))
779
780 (defsubst elmo-msgdb-overview-entity-set-number (entity number)
781   (and entity (aset (cdr entity) 0 number))
782   entity)
783 ;;;(setcar (cadr entity) number) entity)
784
785 (defsubst elmo-msgdb-overview-entity-set-from (entity from)
786   (and entity (aset (cdr entity) 2 from))
787   entity)
788
789 (defsubst elmo-msgdb-overview-entity-get-subject (entity)
790   (and entity
791        (aref (cdr entity) 3)
792        (elmo-msgdb-get-decoded-cache (aref (cdr entity) 3))))
793
794 (defsubst elmo-msgdb-overview-entity-get-subject-no-decode (entity)
795   (and entity (aref (cdr entity) 3)))
796
797 (defsubst elmo-msgdb-overview-entity-set-subject (entity subject)
798   (and entity (aset (cdr entity) 3 subject))
799   entity)
800
801 (defsubst elmo-msgdb-overview-entity-get-date (entity)
802   (and entity (aref (cdr entity) 4)))
803
804 (defsubst elmo-msgdb-overview-entity-set-date (entity date)
805   (and entity (aset (cdr entity) 4 date))
806   entity)
807
808 (defsubst elmo-msgdb-overview-entity-get-to (entity)
809   (and entity (aref (cdr entity) 5)))
810
811 (defsubst elmo-msgdb-overview-entity-get-cc (entity)
812   (and entity (aref (cdr entity) 6)))
813
814 (defsubst elmo-msgdb-overview-entity-get-size (entity)
815   (and entity (aref (cdr entity) 7)))
816
817 (defsubst elmo-msgdb-overview-entity-set-size (entity size)
818   (and entity (aset (cdr entity) 7 size))
819   entity)
820
821 (defsubst elmo-msgdb-overview-entity-get-id (entity)
822   (and entity (car entity)))
823
824 (defsubst elmo-msgdb-overview-entity-get-extra-field (entity field-name)
825   (let ((extra (and entity (aref (cdr entity) 8))))
826     (and extra
827          (cdr (assoc field-name extra)))))
828
829 (defsubst elmo-msgdb-overview-entity-set-extra-field (entity field-name value)
830   (let ((extras (and entity (aref (cdr entity) 8)))
831         extra)
832     (if (setq extra (assoc field-name extras))
833         (setcdr extra value)
834       (elmo-msgdb-overview-entity-set-extra
835        entity
836        (cons (cons field-name value) extras)))))
837
838 (defsubst elmo-msgdb-overview-entity-get-extra (entity)
839   (and entity (aref (cdr entity) 8)))
840
841 (defsubst elmo-msgdb-overview-entity-set-extra (entity extra)
842   (and entity (aset (cdr entity) 8 extra))
843   entity)
844
845 (defun elmo-msgdb-overview-get-entity-by-number (database number)
846   (when number
847     (let ((db database)
848           entity)
849       (while db
850         (if (eq (elmo-msgdb-overview-entity-get-number (car db)) number)
851             (setq entity (car db)
852                   db nil) ; exit loop
853           (setq db (cdr db))))
854       entity)))
855
856 (defun elmo-msgdb-overview-get-entity (id msgdb)
857   (when id
858     (let ((ht (elmo-msgdb-get-entity-hashtb msgdb)))
859       (if ht
860           (if (stringp id) ;; ID is message-id
861               (elmo-get-hash-val id ht)
862             (elmo-get-hash-val (format "#%d" id) ht))))))
863
864 ;;
865 ;; deleted message handling
866 ;;
867 (defun elmo-msgdb-killed-list-load (dir)
868   (elmo-object-load
869    (expand-file-name elmo-msgdb-killed-filename dir)
870    nil t))
871
872 (defun elmo-msgdb-killed-list-save (dir killed-list)
873   (elmo-object-save
874    (expand-file-name elmo-msgdb-killed-filename dir)
875    killed-list))
876
877 (defun elmo-msgdb-killed-message-p (killed-list msg)
878   (elmo-number-set-member msg killed-list))
879
880 (defun elmo-msgdb-set-as-killed (killed-list msg)
881   (elmo-number-set-append killed-list msg))
882
883 (defun elmo-msgdb-append-to-killed-list (folder msgs)
884   (elmo-folder-set-killed-list-internal
885    folder
886    (elmo-number-set-append-list
887     (elmo-folder-killed-list-internal folder)
888     msgs)))
889
890 (defun elmo-msgdb-killed-list-length (killed-list)
891   (let ((killed killed-list)
892         (ret-val 0))
893     (while (car killed)
894       (if (consp (car killed))
895           (setq ret-val (+ ret-val 1 (- (cdar killed) (caar killed))))
896         (setq ret-val (+ ret-val 1)))
897       (setq killed (cdr killed)))
898     ret-val))
899
900 (defun elmo-msgdb-max-of-killed (killed-list)
901   (let ((klist killed-list)
902         (max 0)
903         k)
904     (while (car klist)
905       (if (< max
906              (setq k
907                    (if (consp (car klist))
908                        (cdar klist)
909                      (car klist))))
910           (setq max k))
911       (setq klist (cdr klist)))
912     max))
913
914 (defun elmo-living-messages (messages killed-list)
915   (if killed-list
916       (delq nil
917             (mapcar (lambda (number)
918                       (unless (elmo-number-set-member number killed-list)
919                         number))
920                     messages))
921     messages))
922
923 (defun elmo-msgdb-finfo-load ()
924   (elmo-object-load (expand-file-name
925                      elmo-msgdb-finfo-filename
926                      elmo-msgdb-directory)
927                     elmo-mime-charset t))
928
929 (defun elmo-msgdb-finfo-save (finfo)
930   (elmo-object-save (expand-file-name
931                      elmo-msgdb-finfo-filename
932                      elmo-msgdb-directory)
933                     finfo elmo-mime-charset))
934
935 (defun elmo-msgdb-flist-load (fname)
936   (let ((flist-file (expand-file-name
937                      elmo-msgdb-flist-filename
938                      (expand-file-name
939                       (elmo-safe-filename fname)
940                       (expand-file-name "folder" elmo-msgdb-directory)))))
941     (elmo-object-load flist-file elmo-mime-charset t)))
942
943 (defun elmo-msgdb-flist-save (fname flist)
944   (let ((flist-file (expand-file-name
945                      elmo-msgdb-flist-filename
946                      (expand-file-name
947                       (elmo-safe-filename fname)
948                       (expand-file-name "folder" elmo-msgdb-directory)))))
949     (elmo-object-save flist-file flist elmo-mime-charset)))
950
951 (defun elmo-crosspost-alist-load ()
952   (elmo-object-load (expand-file-name
953                      elmo-crosspost-alist-filename
954                      elmo-msgdb-directory)
955                     nil t))
956
957 (defun elmo-crosspost-alist-save (alist)
958   (elmo-object-save (expand-file-name
959                      elmo-crosspost-alist-filename
960                      elmo-msgdb-directory)
961                     alist))
962
963 (defun elmo-msgdb-add-msgs-to-seen-list (msgs msgdb seen-list)
964   ;; Add to seen list.
965   (let (mark)
966     (while msgs
967       (if (setq mark (elmo-msgdb-get-mark msgdb (car msgs)))
968           (unless (member mark (elmo-msgdb-unread-marks)) ;; not unread mark
969             (setq seen-list
970                   (cons
971                    (elmo-msgdb-get-field msgdb (car msgs) 'message-id)
972                    seen-list)))
973         ;; no mark ... seen...
974         (setq seen-list
975               (cons 
976                (elmo-msgdb-get-field msgdb (car msgs) 'message-id)
977                seen-list)))
978       (setq msgs (cdr msgs)))
979     seen-list))
980
981 (defun elmo-msgdb-get-message-id-from-buffer ()
982   (or (elmo-field-body "message-id")
983       ;; no message-id, so put dummy msgid.
984       (concat "<" (timezone-make-date-sortable
985                    (elmo-field-body "date"))
986               (nth 1 (eword-extract-address-components
987                       (or (elmo-field-body "from") "nobody"))) ">")))
988
989 (defsubst elmo-msgdb-create-overview-from-buffer (number &optional size time)
990   "Create overview entity from current buffer.
991 Header region is supposed to be narrowed."
992   (save-excursion
993     (let ((extras elmo-msgdb-extra-fields)
994           (default-mime-charset default-mime-charset)
995           message-id references from subject to cc date
996           extra field-body charset)
997       (elmo-set-buffer-multibyte default-enable-multibyte-characters)
998       (setq message-id (elmo-msgdb-get-message-id-from-buffer))
999       (and (setq charset (cdr (assoc "charset" (mime-read-Content-Type))))
1000            (setq charset (intern-soft charset))
1001            (setq default-mime-charset charset))
1002       (setq references
1003             (or (elmo-msgdb-get-last-message-id
1004                  (elmo-field-body "in-reply-to"))
1005                 (elmo-msgdb-get-last-message-id
1006                  (elmo-field-body "references"))))
1007       (setq from (elmo-replace-in-string
1008                   (elmo-mime-string (or (elmo-field-body "from")
1009                                         elmo-no-from))
1010                   "\t" " ")
1011             subject (elmo-replace-in-string
1012                      (elmo-mime-string (or (elmo-field-body "subject")
1013                                            elmo-no-subject))
1014                      "\t" " "))
1015       (setq date (or (elmo-field-body "date") time))
1016       (setq to   (mapconcat 'identity (elmo-multiple-field-body "to") ","))
1017       (setq cc   (mapconcat 'identity (elmo-multiple-field-body "cc") ","))
1018       (or size
1019           (if (setq size (elmo-field-body "content-length"))
1020               (setq size (string-to-int size))
1021             (setq size 0)));; No mean...
1022       (while extras
1023         (if (setq field-body (elmo-field-body (car extras)))
1024             (setq extra (cons (cons (downcase (car extras))
1025                                     field-body) extra)))
1026         (setq extras (cdr extras)))
1027       (cons message-id (vector number references
1028                                from subject date to cc
1029                                size extra))
1030       )))
1031
1032 (defun elmo-msgdb-copy-overview-entity (entity)
1033   (cons (car entity)
1034         (copy-sequence (cdr entity))))
1035
1036 (defsubst elmo-msgdb-insert-file-header (file)
1037   "Insert the header of the article."
1038   (let ((beg 0)
1039         insert-file-contents-pre-hook   ; To avoid autoconv-xmas...
1040         insert-file-contents-post-hook
1041         format-alist)
1042     (when (file-exists-p file)
1043       ;; Read until header separator is found.
1044       (while (and (eq elmo-msgdb-file-header-chop-length
1045                       (nth 1
1046                            (insert-file-contents-as-binary
1047                             file nil beg
1048                             (incf beg elmo-msgdb-file-header-chop-length))))
1049                   (prog1 (not (search-forward "\n\n" nil t))
1050                     (goto-char (point-max))))))))
1051
1052 (defsubst elmo-msgdb-create-overview-entity-from-file (number file)
1053   (let (insert-file-contents-pre-hook   ; To avoid autoconv-xmas...
1054         insert-file-contents-post-hook header-end
1055         (attrib (file-attributes file))
1056         ret-val size mtime)
1057     (with-temp-buffer
1058       (if (not (file-exists-p file))
1059           ()
1060         (setq size (nth 7 attrib))
1061         (setq mtime (timezone-make-date-arpa-standard
1062                      (current-time-string (nth 5 attrib)) (current-time-zone)))
1063         ;; insert header from file.
1064         (catch 'done
1065           (condition-case nil
1066               (elmo-msgdb-insert-file-header file)
1067             (error (throw 'done nil)))
1068           (goto-char (point-min))
1069           (setq header-end
1070                 (if (re-search-forward "\\(^--.*$\\)\\|\\(\n\n\\)" nil t)
1071                     (point)
1072                   (point-max)))
1073           (narrow-to-region (point-min) header-end)
1074           (elmo-msgdb-create-overview-from-buffer number size mtime))))))
1075
1076 (defun elmo-msgdb-overview-sort-by-date (overview)
1077   (sort overview
1078         (function
1079          (lambda (x y)
1080            (condition-case nil
1081                (string<
1082                 (timezone-make-date-sortable
1083                  (elmo-msgdb-overview-entity-get-date x))
1084                 (timezone-make-date-sortable
1085                  (elmo-msgdb-overview-entity-get-date y)))
1086              (error))))))
1087
1088 (defun elmo-msgdb-clear-index (msgdb entity)
1089   (let ((ehash (elmo-msgdb-get-entity-hashtb msgdb))
1090         (mhash (elmo-msgdb-get-mark-hashtb msgdb))
1091         number)
1092     (when (and entity ehash)
1093       (and (setq number (elmo-msgdb-overview-entity-get-number entity))
1094            (elmo-clear-hash-val (format "#%d" number) ehash))
1095       (and (car entity) ;; message-id
1096            (elmo-clear-hash-val (car entity) ehash)))
1097     (when (and entity mhash)
1098       (and (setq number (elmo-msgdb-overview-entity-get-number entity))
1099            (elmo-clear-hash-val (format "#%d" number) mhash)))))
1100
1101 (defun elmo-msgdb-make-index (msgdb &optional overview mark-alist)
1102   "Append OVERVIEW and MARK-ALIST to the index of MSGDB.
1103 If OVERVIEW and MARK-ALIST are nil, make index for current MSGDB.
1104 Return the updated INDEX."
1105   (when msgdb
1106     (let* ((overview (or overview (elmo-msgdb-get-overview msgdb)))
1107            (mark-alist (or mark-alist (elmo-msgdb-get-mark-alist msgdb)))
1108            (index (elmo-msgdb-get-index msgdb))
1109            (ehash (or (car index) ;; append
1110                       (elmo-make-hash (length overview))))
1111            (mhash (or (cdr index) ;; append
1112                       (elmo-make-hash (length overview)))))
1113       (while overview
1114         ;; key is message-id
1115         (if (caar overview)
1116             (elmo-set-hash-val (caar overview) (car overview) ehash))
1117         ;; key is number
1118         (elmo-set-hash-val
1119          (format "#%d"
1120                  (elmo-msgdb-overview-entity-get-number (car overview)))
1121          (car overview) ehash)
1122         (setq overview (cdr overview)))
1123       (while mark-alist
1124         ;; key is number
1125         (elmo-set-hash-val
1126          (format "#%d" (car (car mark-alist)))
1127          (car mark-alist) mhash)
1128         (setq mark-alist (cdr mark-alist)))
1129       (setq index (or index (cons ehash mhash)))
1130       (elmo-msgdb-set-index msgdb index)
1131       index)))
1132
1133 (defsubst elmo-folder-get-info (folder &optional hashtb)
1134   (elmo-get-hash-val folder
1135                      (or hashtb elmo-folder-info-hashtb)))
1136
1137 (defun elmo-folder-get-info-max (folder)
1138   "Get folder info from cache."
1139   (nth 3 (elmo-folder-get-info folder)))
1140
1141 (defun elmo-folder-get-info-length (folder)
1142   (nth 2 (elmo-folder-get-info folder)))
1143
1144 (defun elmo-folder-get-info-unread (folder)
1145   (nth 1 (elmo-folder-get-info folder)))
1146
1147 (defsubst elmo-msgdb-location-load (dir)
1148   (elmo-object-load
1149    (expand-file-name
1150     elmo-msgdb-location-filename
1151     dir)))
1152
1153 (defsubst elmo-msgdb-location-add (alist number location)
1154   (let ((ret-val alist))
1155     (setq ret-val
1156           (elmo-msgdb-append-element ret-val (cons number location)))
1157     ret-val))
1158
1159 (defsubst elmo-msgdb-location-save (dir alist)
1160   (elmo-object-save
1161    (expand-file-name
1162     elmo-msgdb-location-filename
1163     dir) alist))
1164
1165 (put 'elmo-msgdb-do-each-entity 'lisp-indent-function '1)
1166 (def-edebug-spec elmo-msgdb-do-each-entity
1167   ((symbolp form &rest form) &rest form))
1168 (defmacro elmo-msgdb-do-each-entity (spec &rest form)
1169   `(dolist (,(car spec) (elmo-msgdb-get-overview ,(car (cdr spec))))
1170      ,@form))
1171
1172 (require 'product)
1173 (product-provide (provide 'elmo-msgdb) (require 'elmo-version))
1174
1175 ;;; elmo-msgdb.el ends here