c458262a771a4a8a3fca108f80d2de9e0e2affdd
[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          mark-modified)
213     (case status
214       (read
215        (when (eq cur-status 'read)
216          (elmo-msgdb-set-mark msgdb number
217                               (if (and cur-cached use-cache)
218                                   elmo-msgdb-unread-cached-mark
219                                 elmo-msgdb-unread-uncached-mark))
220          (setq mark-modified t)))
221       (important
222        (when (eq cur-status 'important)
223          (elmo-msgdb-set-mark msgdb number nil)
224          (setq mark-modified t)))
225       (answered
226        (when (eq cur-status 'answered)
227          (elmo-msgdb-set-mark msgdb number
228                               (if (and cur-cached (not use-cache))
229                                   elmo-msgdb-read-uncached-mark))
230          (setq mark-modified t))))
231     (if mark-modified (elmo-folder-set-mark-modified-internal folder t))))
232
233 (defvar elmo-msgdb-unread-marks-internal nil)
234 (defsubst elmo-msgdb-unread-marks ()
235   "Return an unread mark list"
236   (or elmo-msgdb-unread-marks-internal
237       (setq elmo-msgdb-unread-marks-internal
238             (list elmo-msgdb-new-mark
239                   elmo-msgdb-unread-uncached-mark
240                   elmo-msgdb-unread-cached-mark))))
241
242 (defvar elmo-msgdb-answered-marks-internal nil)
243 (defsubst elmo-msgdb-answered-marks ()
244   "Return an answered mark list"
245   (or elmo-msgdb-answered-marks-internal
246       (setq elmo-msgdb-answered-marks-internal
247             (list elmo-msgdb-answered-cached-mark
248                   elmo-msgdb-answered-uncached-mark))))
249
250 (defvar elmo-msgdb-uncached-marks-internal nil)
251 (defsubst elmo-msgdb-uncached-marks ()
252   (or elmo-msgdb-uncached-marks-internal
253       (setq elmo-msgdb-uncached-marks-internal
254             (list elmo-msgdb-new-mark
255                   elmo-msgdb-answered-uncached-mark
256                   elmo-msgdb-unread-uncached-mark
257                   elmo-msgdb-read-uncached-mark))))
258
259 (defsubst elmo-msgdb-count-marks (msgdb)
260   (let ((new 0)
261         (unreads 0)
262         (answered 0))
263     (dolist (elem (elmo-msgdb-get-mark-alist msgdb))
264       (cond
265        ((string= (cadr elem) elmo-msgdb-new-mark)
266         (incf new))
267        ((member (cadr elem) (elmo-msgdb-unread-marks))
268         (incf unreads))
269        ((member (cadr elem) (elmo-msgdb-answered-marks))
270         (incf answered))))
271     (list new unreads answered)))
272
273 (defsubst elmo-msgdb-get-number (msgdb message-id)
274   "Get number of the message which corrensponds to MESSAGE-ID from MSGDB."
275   (elmo-msgdb-overview-entity-get-number
276    (elmo-msgdb-overview-get-entity message-id msgdb)))
277
278 (defsubst elmo-msgdb-get-field (msgdb number field)
279   "Get FIELD value of the message with NUMBER from MSGDB."
280   (case field
281     (message-id (elmo-msgdb-overview-entity-get-id
282                  (elmo-msgdb-overview-get-entity
283                   number msgdb)))
284     (subject (elmo-msgdb-overview-entity-get-subject
285               (elmo-msgdb-overview-get-entity
286                number msgdb)))
287     (size (elmo-msgdb-overview-entity-get-size
288            (elmo-msgdb-overview-get-entity
289             number msgdb)))
290     (date (elmo-msgdb-overview-entity-get-date
291            (elmo-msgdb-overview-get-entity
292             number msgdb)))
293     (to (elmo-msgdb-overview-entity-get-to
294          (elmo-msgdb-overview-get-entity
295           number msgdb)))
296     (cc (elmo-msgdb-overview-entity-get-cc
297          (elmo-msgdb-overview-get-entity
298           number msgdb)))))
299
300 (defsubst elmo-msgdb-append (msgdb msgdb-append)
301   (list
302    (nconc (car msgdb) (car msgdb-append))
303    (nconc (cadr msgdb) (cadr msgdb-append))
304    (nconc (caddr msgdb) (caddr msgdb-append))
305    (elmo-msgdb-make-index
306     msgdb
307     (elmo-msgdb-get-overview msgdb-append)
308     (elmo-msgdb-get-mark-alist msgdb-append))))
309
310 (defsubst elmo-msgdb-clear (&optional msgdb)
311   (if msgdb
312       (list
313        (setcar msgdb nil)
314        (setcar (cdr msgdb) nil)
315        (setcar (cddr msgdb) nil)
316        (setcar (nthcdr 3 msgdb) nil))
317     (list nil nil nil nil)))
318
319 (defun elmo-msgdb-delete-msgs (msgdb msgs)
320   "Delete MSGS from MSGDB
321 content of MSGDB is changed."
322   (let* ((overview (car msgdb))
323          (number-alist (cadr msgdb))
324          (mark-alist (caddr msgdb))
325          (index (elmo-msgdb-get-index msgdb))
326          (newmsgdb (list overview number-alist mark-alist index))
327          ov-entity)
328     ;; remove from current database.
329     (while msgs
330       (setq overview
331             (delq
332              (setq ov-entity
333                    (elmo-msgdb-overview-get-entity (car msgs) newmsgdb))
334              overview))
335       (setq number-alist (delq (assq (car msgs) number-alist) number-alist))
336       (setq mark-alist (delq (assq (car msgs) mark-alist) mark-alist))
337       ;;
338       (when index (elmo-msgdb-clear-index msgdb ov-entity))
339       (setq msgs (cdr msgs)))
340     (setcar msgdb overview)
341     (setcar (cdr msgdb) number-alist)
342     (setcar (cddr msgdb) mark-alist)
343     (setcar (nthcdr 3 msgdb) index)
344     t)) ;return value
345
346 (defun elmo-msgdb-sort-by-date (msgdb)
347   (message "Sorting...")
348   (let ((overview (elmo-msgdb-get-overview msgdb)))
349     (setq overview (elmo-msgdb-overview-sort-by-date overview))
350     (message "Sorting...done")
351     (list overview (nth 1 msgdb)(nth 2 msgdb))))
352
353 (defun elmo-msgdb-make-entity (&rest args)
354   "Make an msgdb entity."
355   (cons (plist-get args :message-id)
356         (vector (plist-get args :number)
357                 (plist-get args :references)
358                 (plist-get args :from)
359                 (plist-get args :subject)
360                 (plist-get args :date)
361                 (plist-get args :to)
362                 (plist-get args :cc)
363                 (plist-get args :size)
364                 (plist-get args :extra))))
365
366 ;;;
367 (defsubst elmo-msgdb-append-element (list element)
368   (if list
369 ;;;   (append list (list element))
370       (nconc list (list element))
371     ;; list is nil
372     (list element)))
373
374 (defsubst elmo-msgdb-get-overview (msgdb)
375   (car msgdb))
376 (defsubst elmo-msgdb-get-number-alist (msgdb)
377   (cadr msgdb))
378 (defsubst elmo-msgdb-get-mark-alist (msgdb)
379   (caddr msgdb))
380 ;(defsubst elmo-msgdb-get-location (msgdb)
381 ;  (cadddr msgdb))
382
383 (defsubst elmo-msgdb-get-index (msgdb)
384   (nth 3 msgdb))
385
386 (defsubst elmo-msgdb-get-entity-hashtb (msgdb)
387   (car (nth 3 msgdb)))
388
389 (defsubst elmo-msgdb-get-mark-hashtb (msgdb)
390   (cdr (nth 3 msgdb)))
391
392 ;;
393 ;; number <-> Message-ID handling
394 ;;
395 (defsubst elmo-msgdb-number-add (alist number id)
396   (let ((ret-val alist))
397     (setq ret-val
398           (elmo-msgdb-append-element ret-val (cons number id)))
399     ret-val))
400
401 ;;;
402 ;; parsistent mark handling
403 ;; (for global!)
404
405 (defvar elmo-msgdb-global-mark-alist nil)
406
407 (defun elmo-msgdb-global-mark-delete (msgid)
408   (let* ((path (expand-file-name
409                 elmo-msgdb-global-mark-filename
410                 elmo-msgdb-directory))
411          (malist (or elmo-msgdb-global-mark-alist
412                      (setq elmo-msgdb-global-mark-alist
413                            (elmo-object-load path))))
414          match)
415     (when (setq match (assoc msgid malist))
416       (setq elmo-msgdb-global-mark-alist
417             (delete match elmo-msgdb-global-mark-alist))
418       (elmo-object-save path elmo-msgdb-global-mark-alist))))
419
420 (defun elmo-msgdb-global-mark-set (msgid mark)
421   (let* ((path (expand-file-name
422                 elmo-msgdb-global-mark-filename
423                 elmo-msgdb-directory))
424          (malist (or elmo-msgdb-global-mark-alist
425                      (setq elmo-msgdb-global-mark-alist
426                            (elmo-object-load path))))
427          match)
428     (if (setq match (assoc msgid malist))
429         (setcdr match mark)
430       (setq elmo-msgdb-global-mark-alist
431             (nconc elmo-msgdb-global-mark-alist
432                    (list (cons msgid mark)))))
433     (elmo-object-save path elmo-msgdb-global-mark-alist)))
434
435 (defun elmo-msgdb-global-mark-get (msgid)
436   (cdr (assoc msgid (or elmo-msgdb-global-mark-alist
437                         (setq elmo-msgdb-global-mark-alist
438                               (elmo-object-load
439                                (expand-file-name
440                                 elmo-msgdb-global-mark-filename
441                                 elmo-msgdb-directory)))))))
442
443 ;;;
444 ;; persistent mark handling
445 ;; (for each folder)
446
447 (defun elmo-msgdb-mark-append (alist id mark)
448   "Append mark."
449   (setq alist (elmo-msgdb-append-element alist
450                                          (list id mark))))
451
452 (defun elmo-msgdb-seen-list (msgdb)
453   "Get SEEN-MSGID-LIST from MSGDB."
454   (let ((ov (elmo-msgdb-get-overview msgdb))
455         mark seen-list)
456     (while ov
457       (if (setq mark (elmo-msgdb-get-mark
458                       msgdb
459                       (elmo-msgdb-overview-entity-get-number (car ov))))
460           (if (and mark (not (member mark
461                                      (elmo-msgdb-unread-marks))))
462               (setq seen-list (cons
463                                (elmo-msgdb-overview-entity-get-id (car ov))
464                                seen-list)))
465         (setq seen-list (cons
466                          (elmo-msgdb-overview-entity-get-id (car ov))
467                          seen-list)))
468       (setq ov (cdr ov)))
469     seen-list))
470
471 ;;
472 ;; mime decode cache
473
474 (defvar elmo-msgdb-decoded-cache-hashtb nil)
475 (make-variable-buffer-local 'elmo-msgdb-decoded-cache-hashtb)
476
477 (defsubst elmo-msgdb-get-decoded-cache (string)
478   (if elmo-use-decoded-cache
479       (let ((hashtb (or elmo-msgdb-decoded-cache-hashtb
480                         (setq elmo-msgdb-decoded-cache-hashtb
481                               (elmo-make-hash 2048))))
482             decoded)
483         (or (elmo-get-hash-val string hashtb)
484             (progn
485               (elmo-set-hash-val
486                string
487                (setq decoded
488                      (decode-mime-charset-string string elmo-mime-charset))
489                hashtb)
490               decoded)))
491     (decode-mime-charset-string string elmo-mime-charset)))
492
493 ;;
494 ;; overview handling
495 ;;
496
497 (defsubst elmo-msgdb-get-field-value (field-name beg end buffer)
498   (save-excursion
499     (save-restriction
500       (set-buffer buffer)
501       (narrow-to-region beg end)
502       (elmo-field-body field-name))))
503
504 (defun elmo-multiple-field-body (name &optional boundary)
505   (save-excursion
506     (save-restriction
507       (std11-narrow-to-header boundary)
508       (goto-char (point-min))
509       (let ((case-fold-search t)
510             (field-body nil))
511         (while (re-search-forward (concat "^" name ":[ \t]*") nil t)
512           (setq field-body
513                 (nconc field-body
514                        (list (buffer-substring-no-properties
515                               (match-end 0) (std11-field-end))))))
516         field-body))))
517
518 (defun elmo-multiple-fields-body-list (field-names &optional boundary)
519   "Return list of each field-bodies of FIELD-NAMES of the message header
520 in current buffer. If BOUNDARY is not nil, it is used as message
521 header separator."
522   (save-excursion
523     (save-restriction
524       (std11-narrow-to-header boundary)
525       (let* ((case-fold-search t)
526              (s-rest field-names)
527              field-name field-body)
528         (while (setq field-name (car s-rest))
529           (goto-char (point-min))
530           (while (re-search-forward (concat "^" field-name ":[ \t]*") nil t)
531             (setq field-body
532                   (nconc field-body
533                          (list (buffer-substring-no-properties
534                                 (match-end 0) (std11-field-end))))))
535           (setq s-rest (cdr s-rest)))
536         field-body))))
537
538 (defsubst elmo-msgdb-remove-field-string (string)
539   (if (string-match (concat std11-field-head-regexp "[ \t]*") string)
540       (substring string (match-end 0))
541     string))
542
543 (defsubst elmo-msgdb-get-last-message-id (string)
544   (if string
545       (save-match-data
546         (let (beg)
547           (elmo-set-work-buf
548            (insert string)
549            (goto-char (point-max))
550            (when (search-backward "<" nil t)
551              (setq beg (point))
552              (if (search-forward ">" nil t)
553                  (elmo-replace-in-string
554                   (buffer-substring beg (point)) "\n[ \t]*" ""))))))))
555
556 (defun elmo-msgdb-number-load (dir)
557   (elmo-object-load
558    (expand-file-name elmo-msgdb-number-filename dir)))
559
560 (defun elmo-msgdb-overview-load (dir)
561   (elmo-object-load
562    (expand-file-name elmo-msgdb-overview-filename dir)))
563
564 (defun elmo-msgdb-mark-load (dir)
565   (elmo-object-load
566    (expand-file-name elmo-msgdb-mark-filename dir)))
567
568 (defsubst elmo-msgdb-seen-load (dir)
569   (elmo-object-load (expand-file-name
570                      elmo-msgdb-seen-filename
571                      dir)))
572
573 (defun elmo-msgdb-number-save (dir obj)
574   (elmo-object-save
575    (expand-file-name elmo-msgdb-number-filename dir)
576    obj))
577
578 (defun elmo-msgdb-mark-save (dir obj)
579   (elmo-object-save
580    (expand-file-name elmo-msgdb-mark-filename dir)
581    obj))
582
583 (defun elmo-msgdb-change-mark (msgdb before after)
584   "Set the BEFORE marks to AFTER."
585   (let ((mark-alist (elmo-msgdb-get-mark-alist msgdb))
586         entity)
587     (while mark-alist
588       (setq entity (car mark-alist))
589       (when (string= (cadr entity) before)
590         (setcar (cdr entity) after))
591       (setq mark-alist (cdr mark-alist)))))
592
593 (defsubst elmo-msgdb-mark (status cached)
594   (case status
595     (unread
596      (if cached
597          elmo-msgdb-unread-cached-mark
598        elmo-msgdb-unread-uncached-mark))
599     (important
600      elmo-msgdb-important-mark)
601     (answered
602      (if cached
603          elmo-msgdb-answered-cached-mark
604        elmo-msgdb-answered-uncached-mark))))
605
606 (defsubst elmo-msgdb-seen-save (dir obj)
607   (elmo-object-save
608    (expand-file-name elmo-msgdb-seen-filename dir)
609    obj))
610
611 (defsubst elmo-msgdb-overview-save (dir overview)
612   (elmo-object-save
613    (expand-file-name elmo-msgdb-overview-filename dir)
614    overview))
615
616 (defun elmo-msgdb-match-condition-primitive (condition mark entity numbers)
617   (catch 'unresolved
618     (let ((key (elmo-filter-key condition))
619           (case-fold-search t)
620           result)
621       (cond
622        ((string= key "last")
623         (setq result (<= (length (memq
624                                   (elmo-msgdb-overview-entity-get-number
625                                    entity)
626                                   numbers))
627                          (string-to-int (elmo-filter-value condition)))))
628        ((string= key "first")
629         (setq result (< (-
630                          (length numbers)
631                          (length (memq
632                                   (elmo-msgdb-overview-entity-get-number
633                                    entity)
634                                   numbers)))
635                         (string-to-int (elmo-filter-value condition)))))
636        ((string= key "mark")
637         (setq result
638               (cond
639                ((string= (elmo-filter-value condition) "any")
640                 (not (or (null mark)
641                          (string= mark elmo-msgdb-read-uncached-mark))))
642 ;;        (member mark (append (elmo-msgdb-answered-marks)
643 ;;                             (list elmo-msgdb-important-mark)
644 ;;                             (elmo-msgdb-unread-marks))))
645                ((string= (elmo-filter-value condition) "unread")
646                 (member mark (elmo-msgdb-unread-marks)))
647                ((string= (elmo-filter-value condition) "important")
648                 (string= mark elmo-msgdb-important-mark))
649                ((string= (elmo-filter-value condition) "answered")
650                 (member mark (elmo-msgdb-answered-marks))))))
651        ((string= key "from")
652         (setq result (string-match
653                       (elmo-filter-value condition)
654                       (elmo-msgdb-overview-entity-get-from entity))))
655        ((string= key "subject")
656         (setq result (string-match
657                       (elmo-filter-value condition)
658                       (elmo-msgdb-overview-entity-get-subject entity))))
659        ((string= key "to")
660         (setq result (string-match
661                       (elmo-filter-value condition)
662                       (elmo-msgdb-overview-entity-get-to entity))))
663        ((string= key "cc")
664         (setq result (string-match
665                       (elmo-filter-value condition)
666                       (elmo-msgdb-overview-entity-get-cc entity))))
667        ((or (string= key "since")
668             (string= key "before"))
669         (let ((field-date (elmo-date-make-sortable-string
670                            (timezone-fix-time
671                             (elmo-msgdb-overview-entity-get-date entity)
672                             (current-time-zone) nil)))
673               (specified-date
674                (elmo-date-make-sortable-string
675                 (elmo-date-get-datevec
676                  (elmo-filter-value condition)))))
677           (setq result (if (string= key "since")
678                            (or (string= specified-date field-date)
679                                (string< specified-date field-date))
680                          (string< field-date specified-date)))))
681        ((member key elmo-msgdb-extra-fields)
682         (let ((extval (elmo-msgdb-overview-entity-get-extra-field entity key)))
683           (when (stringp extval)
684             (setq result (string-match
685                           (elmo-filter-value condition)
686                           extval)))))
687        (t
688         (throw 'unresolved condition)))
689       (if (eq (elmo-filter-type condition) 'unmatch)
690           (not result)
691         result))))
692
693 (defun elmo-msgdb-match-condition-internal (condition mark entity numbers)
694   (cond
695    ((vectorp condition)
696     (elmo-msgdb-match-condition-primitive condition mark entity numbers))
697    ((eq (car condition) 'and)
698     (let ((lhs (elmo-msgdb-match-condition-internal (nth 1 condition)
699                                                     mark entity numbers)))
700       (cond
701        ((elmo-filter-condition-p lhs)
702         (let ((rhs (elmo-msgdb-match-condition-internal
703                     (nth 2 condition) mark entity numbers)))
704           (cond ((elmo-filter-condition-p rhs)
705                  (list 'and lhs rhs))
706                 (rhs
707                  lhs))))
708        (lhs
709         (elmo-msgdb-match-condition-internal (nth 2 condition)
710                                              mark entity numbers)))))
711    ((eq (car condition) 'or)
712     (let ((lhs (elmo-msgdb-match-condition-internal (nth 1 condition)
713                                                     mark entity numbers)))
714       (cond
715        ((elmo-filter-condition-p lhs)
716         (let ((rhs (elmo-msgdb-match-condition-internal (nth 2 condition)
717                                                         mark entity numbers)))
718           (cond ((elmo-filter-condition-p rhs)
719                  (list 'or lhs rhs))
720                 (rhs
721                  t)
722                 (t
723                  lhs))))
724        (lhs
725         t)
726        (t
727         (elmo-msgdb-match-condition-internal (nth 2 condition)
728                                              mark entity numbers)))))))
729
730 (defun elmo-msgdb-match-condition (msgdb condition number numbers)
731   "Check whether the condition of the message is satisfied or not.
732 MSGDB is the msgdb to search from.
733 CONDITION is the search condition.
734 NUMBER is the message number to check.
735 NUMBERS is the target message number list.
736 Return CONDITION itself if no entity exists in msgdb."
737   (let ((entity (elmo-msgdb-overview-get-entity number msgdb)))
738     (if entity
739         (elmo-msgdb-match-condition-internal condition
740                                              (elmo-msgdb-get-mark msgdb number)
741                                              entity numbers)
742       condition)))
743
744 (defsubst elmo-msgdb-set-overview (msgdb overview)
745   (setcar msgdb overview))
746
747 (defsubst elmo-msgdb-set-number-alist (msgdb number-alist)
748   (setcar (cdr msgdb) number-alist))
749
750 (defsubst elmo-msgdb-set-mark-alist (msgdb mark-alist)
751   (setcar (cddr msgdb) mark-alist))
752
753 (defsubst elmo-msgdb-set-index (msgdb index)
754   (setcar (cdddr msgdb) index))
755
756 (defsubst elmo-msgdb-overview-entity-get-references (entity)
757   (and entity (aref (cdr entity) 1)))
758
759 (defsubst elmo-msgdb-overview-entity-set-references (entity references)
760   (and entity (aset (cdr entity) 1 references))
761   entity)
762
763 ;; entity -> parent-entity
764 (defsubst elmo-msgdb-overview-get-parent-entity (entity database)
765   (setq entity (elmo-msgdb-overview-entity-get-references entity))
766   ;; entity is parent-id.
767   (and entity (assoc entity database)))
768
769 (defsubst elmo-msgdb-get-parent-entity (entity msgdb)
770   (setq entity (elmo-msgdb-overview-entity-get-references entity))
771   ;; entity is parent-id.
772   (and entity (elmo-msgdb-overview-get-entity entity msgdb)))
773
774 (defsubst elmo-msgdb-overview-entity-get-number (entity)
775   (and entity (aref (cdr entity) 0)))
776
777 (defsubst elmo-msgdb-overview-entity-get-from-no-decode (entity)
778   (and entity (aref (cdr entity) 2)))
779
780 (defsubst elmo-msgdb-overview-entity-get-from (entity)
781   (and entity
782        (aref (cdr entity) 2)
783        (elmo-msgdb-get-decoded-cache (aref (cdr entity) 2))))
784
785 (defsubst elmo-msgdb-overview-entity-set-number (entity number)
786   (and entity (aset (cdr entity) 0 number))
787   entity)
788 ;;;(setcar (cadr entity) number) entity)
789
790 (defsubst elmo-msgdb-overview-entity-set-from (entity from)
791   (and entity (aset (cdr entity) 2 from))
792   entity)
793
794 (defsubst elmo-msgdb-overview-entity-get-subject (entity)
795   (and entity
796        (aref (cdr entity) 3)
797        (elmo-msgdb-get-decoded-cache (aref (cdr entity) 3))))
798
799 (defsubst elmo-msgdb-overview-entity-get-subject-no-decode (entity)
800   (and entity (aref (cdr entity) 3)))
801
802 (defsubst elmo-msgdb-overview-entity-set-subject (entity subject)
803   (and entity (aset (cdr entity) 3 subject))
804   entity)
805
806 (defsubst elmo-msgdb-overview-entity-get-date (entity)
807   (and entity (aref (cdr entity) 4)))
808
809 (defsubst elmo-msgdb-overview-entity-set-date (entity date)
810   (and entity (aset (cdr entity) 4 date))
811   entity)
812
813 (defsubst elmo-msgdb-overview-entity-get-to (entity)
814   (and entity (aref (cdr entity) 5)))
815
816 (defsubst elmo-msgdb-overview-entity-get-cc (entity)
817   (and entity (aref (cdr entity) 6)))
818
819 (defsubst elmo-msgdb-overview-entity-get-size (entity)
820   (and entity (aref (cdr entity) 7)))
821
822 (defsubst elmo-msgdb-overview-entity-set-size (entity size)
823   (and entity (aset (cdr entity) 7 size))
824   entity)
825
826 (defsubst elmo-msgdb-overview-entity-get-id (entity)
827   (and entity (car entity)))
828
829 (defsubst elmo-msgdb-overview-entity-get-extra-field (entity field-name)
830   (let ((extra (and entity (aref (cdr entity) 8))))
831     (and extra
832          (cdr (assoc field-name extra)))))
833
834 (defsubst elmo-msgdb-overview-entity-set-extra-field (entity field-name value)
835   (let ((extras (and entity (aref (cdr entity) 8)))
836         extra)
837     (if (setq extra (assoc field-name extras))
838         (setcdr extra value)
839       (elmo-msgdb-overview-entity-set-extra
840        entity
841        (cons (cons field-name value) extras)))))
842
843 (defsubst elmo-msgdb-overview-entity-get-extra (entity)
844   (and entity (aref (cdr entity) 8)))
845
846 (defsubst elmo-msgdb-overview-entity-set-extra (entity extra)
847   (and entity (aset (cdr entity) 8 extra))
848   entity)
849
850 (defun elmo-msgdb-overview-get-entity-by-number (database number)
851   (when number
852     (let ((db database)
853           entity)
854       (while db
855         (if (eq (elmo-msgdb-overview-entity-get-number (car db)) number)
856             (setq entity (car db)
857                   db nil) ; exit loop
858           (setq db (cdr db))))
859       entity)))
860
861 (defun elmo-msgdb-overview-get-entity (id msgdb)
862   (when id
863     (let ((ht (elmo-msgdb-get-entity-hashtb msgdb)))
864       (if ht
865           (if (stringp id) ;; ID is message-id
866               (elmo-get-hash-val id ht)
867             (elmo-get-hash-val (format "#%d" id) ht))))))
868
869 ;;
870 ;; deleted message handling
871 ;;
872 (defun elmo-msgdb-killed-list-load (dir)
873   (elmo-object-load
874    (expand-file-name elmo-msgdb-killed-filename dir)
875    nil t))
876
877 (defun elmo-msgdb-killed-list-save (dir killed-list)
878   (elmo-object-save
879    (expand-file-name elmo-msgdb-killed-filename dir)
880    killed-list))
881
882 (defun elmo-msgdb-killed-message-p (killed-list msg)
883   (elmo-number-set-member msg killed-list))
884
885 (defun elmo-msgdb-set-as-killed (killed-list msg)
886   (elmo-number-set-append killed-list msg))
887
888 (defun elmo-msgdb-append-to-killed-list (folder msgs)
889   (elmo-folder-set-killed-list-internal
890    folder
891    (elmo-number-set-append-list
892     (elmo-folder-killed-list-internal folder)
893     msgs)))
894
895 (defun elmo-msgdb-killed-list-length (killed-list)
896   (let ((killed killed-list)
897         (ret-val 0))
898     (while (car killed)
899       (if (consp (car killed))
900           (setq ret-val (+ ret-val 1 (- (cdar killed) (caar killed))))
901         (setq ret-val (+ ret-val 1)))
902       (setq killed (cdr killed)))
903     ret-val))
904
905 (defun elmo-msgdb-max-of-killed (killed-list)
906   (let ((klist killed-list)
907         (max 0)
908         k)
909     (while (car klist)
910       (if (< max
911              (setq k
912                    (if (consp (car klist))
913                        (cdar klist)
914                      (car klist))))
915           (setq max k))
916       (setq klist (cdr klist)))
917     max))
918
919 (defun elmo-living-messages (messages killed-list)
920   (if killed-list
921       (delq nil
922             (mapcar (lambda (number)
923                       (unless (elmo-number-set-member number killed-list)
924                         number))
925                     messages))
926     messages))
927
928 (defun elmo-msgdb-finfo-load ()
929   (elmo-object-load (expand-file-name
930                      elmo-msgdb-finfo-filename
931                      elmo-msgdb-directory)
932                     elmo-mime-charset t))
933
934 (defun elmo-msgdb-finfo-save (finfo)
935   (elmo-object-save (expand-file-name
936                      elmo-msgdb-finfo-filename
937                      elmo-msgdb-directory)
938                     finfo elmo-mime-charset))
939
940 (defun elmo-msgdb-flist-load (fname)
941   (let ((flist-file (expand-file-name
942                      elmo-msgdb-flist-filename
943                      (expand-file-name
944                       (elmo-safe-filename fname)
945                       (expand-file-name "folder" elmo-msgdb-directory)))))
946     (elmo-object-load flist-file elmo-mime-charset t)))
947
948 (defun elmo-msgdb-flist-save (fname flist)
949   (let ((flist-file (expand-file-name
950                      elmo-msgdb-flist-filename
951                      (expand-file-name
952                       (elmo-safe-filename fname)
953                       (expand-file-name "folder" elmo-msgdb-directory)))))
954     (elmo-object-save flist-file flist elmo-mime-charset)))
955
956 (defun elmo-crosspost-alist-load ()
957   (elmo-object-load (expand-file-name
958                      elmo-crosspost-alist-filename
959                      elmo-msgdb-directory)
960                     nil t))
961
962 (defun elmo-crosspost-alist-save (alist)
963   (elmo-object-save (expand-file-name
964                      elmo-crosspost-alist-filename
965                      elmo-msgdb-directory)
966                     alist))
967
968 (defun elmo-msgdb-add-msgs-to-seen-list (msgs msgdb seen-list)
969   ;; Add to seen list.
970   (let (mark)
971     (while msgs
972       (if (setq mark (elmo-msgdb-get-mark msgdb (car msgs)))
973           (unless (member mark (elmo-msgdb-unread-marks)) ;; not unread mark
974             (setq seen-list
975                   (cons
976                    (elmo-msgdb-get-field msgdb (car msgs) 'message-id)
977                    seen-list)))
978         ;; no mark ... seen...
979         (setq seen-list
980               (cons 
981                (elmo-msgdb-get-field msgdb (car msgs) 'message-id)
982                seen-list)))
983       (setq msgs (cdr msgs)))
984     seen-list))
985
986 (defun elmo-msgdb-get-message-id-from-buffer ()
987   (or (elmo-field-body "message-id")
988       ;; no message-id, so put dummy msgid.
989       (concat "<" (timezone-make-date-sortable
990                    (elmo-field-body "date"))
991               (nth 1 (eword-extract-address-components
992                       (or (elmo-field-body "from") "nobody"))) ">")))
993
994 (defsubst elmo-msgdb-create-overview-from-buffer (number &optional size time)
995   "Create overview entity from current buffer.
996 Header region is supposed to be narrowed."
997   (save-excursion
998     (let ((extras elmo-msgdb-extra-fields)
999           (default-mime-charset default-mime-charset)
1000           message-id references from subject to cc date
1001           extra field-body charset)
1002       (elmo-set-buffer-multibyte default-enable-multibyte-characters)
1003       (setq message-id (elmo-msgdb-get-message-id-from-buffer))
1004       (and (setq charset (cdr (assoc "charset" (mime-read-Content-Type))))
1005            (setq charset (intern-soft charset))
1006            (setq default-mime-charset charset))
1007       (setq references
1008             (or (elmo-msgdb-get-last-message-id
1009                  (elmo-field-body "in-reply-to"))
1010                 (elmo-msgdb-get-last-message-id
1011                  (elmo-field-body "references"))))
1012       (setq from (elmo-replace-in-string
1013                   (elmo-mime-string (or (elmo-field-body "from")
1014                                         elmo-no-from))
1015                   "\t" " ")
1016             subject (elmo-replace-in-string
1017                      (elmo-mime-string (or (elmo-field-body "subject")
1018                                            elmo-no-subject))
1019                      "\t" " "))
1020       (setq date (or (elmo-field-body "date") time))
1021       (setq to   (mapconcat 'identity (elmo-multiple-field-body "to") ","))
1022       (setq cc   (mapconcat 'identity (elmo-multiple-field-body "cc") ","))
1023       (or size
1024           (if (setq size (elmo-field-body "content-length"))
1025               (setq size (string-to-int size))
1026             (setq size 0)));; No mean...
1027       (while extras
1028         (if (setq field-body (elmo-field-body (car extras)))
1029             (setq extra (cons (cons (downcase (car extras))
1030                                     field-body) extra)))
1031         (setq extras (cdr extras)))
1032       (cons message-id (vector number references
1033                                from subject date to cc
1034                                size extra))
1035       )))
1036
1037 (defun elmo-msgdb-copy-overview-entity (entity)
1038   (cons (car entity)
1039         (copy-sequence (cdr entity))))
1040
1041 (defsubst elmo-msgdb-insert-file-header (file)
1042   "Insert the header of the article."
1043   (let ((beg 0)
1044         insert-file-contents-pre-hook   ; To avoid autoconv-xmas...
1045         insert-file-contents-post-hook
1046         format-alist)
1047     (when (file-exists-p file)
1048       ;; Read until header separator is found.
1049       (while (and (eq elmo-msgdb-file-header-chop-length
1050                       (nth 1
1051                            (insert-file-contents-as-binary
1052                             file nil beg
1053                             (incf beg elmo-msgdb-file-header-chop-length))))
1054                   (prog1 (not (search-forward "\n\n" nil t))
1055                     (goto-char (point-max))))))))
1056
1057 (defsubst elmo-msgdb-create-overview-entity-from-file (number file)
1058   (let (insert-file-contents-pre-hook   ; To avoid autoconv-xmas...
1059         insert-file-contents-post-hook header-end
1060         (attrib (file-attributes file))
1061         ret-val size mtime)
1062     (with-temp-buffer
1063       (if (not (file-exists-p file))
1064           ()
1065         (setq size (nth 7 attrib))
1066         (setq mtime (timezone-make-date-arpa-standard
1067                      (current-time-string (nth 5 attrib)) (current-time-zone)))
1068         ;; insert header from file.
1069         (catch 'done
1070           (condition-case nil
1071               (elmo-msgdb-insert-file-header file)
1072             (error (throw 'done nil)))
1073           (goto-char (point-min))
1074           (setq header-end
1075                 (if (re-search-forward "\\(^--.*$\\)\\|\\(\n\n\\)" nil t)
1076                     (point)
1077                   (point-max)))
1078           (narrow-to-region (point-min) header-end)
1079           (elmo-msgdb-create-overview-from-buffer number size mtime))))))
1080
1081 (defun elmo-msgdb-overview-sort-by-date (overview)
1082   (sort overview
1083         (function
1084          (lambda (x y)
1085            (condition-case nil
1086                (string<
1087                 (timezone-make-date-sortable
1088                  (elmo-msgdb-overview-entity-get-date x))
1089                 (timezone-make-date-sortable
1090                  (elmo-msgdb-overview-entity-get-date y)))
1091              (error))))))
1092
1093 (defun elmo-msgdb-clear-index (msgdb entity)
1094   (let ((ehash (elmo-msgdb-get-entity-hashtb msgdb))
1095         (mhash (elmo-msgdb-get-mark-hashtb msgdb))
1096         number)
1097     (when (and entity ehash)
1098       (and (setq number (elmo-msgdb-overview-entity-get-number entity))
1099            (elmo-clear-hash-val (format "#%d" number) ehash))
1100       (and (car entity) ;; message-id
1101            (elmo-clear-hash-val (car entity) ehash)))
1102     (when (and entity mhash)
1103       (and (setq number (elmo-msgdb-overview-entity-get-number entity))
1104            (elmo-clear-hash-val (format "#%d" number) mhash)))))
1105
1106 (defun elmo-msgdb-make-index (msgdb &optional overview mark-alist)
1107   "Append OVERVIEW and MARK-ALIST to the index of MSGDB.
1108 If OVERVIEW and MARK-ALIST are nil, make index for current MSGDB.
1109 Return the updated INDEX."
1110   (when msgdb
1111     (let* ((overview (or overview (elmo-msgdb-get-overview msgdb)))
1112            (mark-alist (or mark-alist (elmo-msgdb-get-mark-alist msgdb)))
1113            (index (elmo-msgdb-get-index msgdb))
1114            (ehash (or (car index) ;; append
1115                       (elmo-make-hash (length overview))))
1116            (mhash (or (cdr index) ;; append
1117                       (elmo-make-hash (length overview)))))
1118       (while overview
1119         ;; key is message-id
1120         (if (caar overview)
1121             (elmo-set-hash-val (caar overview) (car overview) ehash))
1122         ;; key is number
1123         (elmo-set-hash-val
1124          (format "#%d"
1125                  (elmo-msgdb-overview-entity-get-number (car overview)))
1126          (car overview) ehash)
1127         (setq overview (cdr overview)))
1128       (while mark-alist
1129         ;; key is number
1130         (elmo-set-hash-val
1131          (format "#%d" (car (car mark-alist)))
1132          (car mark-alist) mhash)
1133         (setq mark-alist (cdr mark-alist)))
1134       (setq index (or index (cons ehash mhash)))
1135       (elmo-msgdb-set-index msgdb index)
1136       index)))
1137
1138 (defsubst elmo-folder-get-info (folder &optional hashtb)
1139   (elmo-get-hash-val folder
1140                      (or hashtb elmo-folder-info-hashtb)))
1141
1142 (defun elmo-folder-get-info-max (folder)
1143   "Get folder info from cache."
1144   (nth 3 (elmo-folder-get-info folder)))
1145
1146 (defun elmo-folder-get-info-length (folder)
1147   (nth 2 (elmo-folder-get-info folder)))
1148
1149 (defun elmo-folder-get-info-unread (folder)
1150   (nth 1 (elmo-folder-get-info folder)))
1151
1152 (defsubst elmo-msgdb-location-load (dir)
1153   (elmo-object-load
1154    (expand-file-name
1155     elmo-msgdb-location-filename
1156     dir)))
1157
1158 (defsubst elmo-msgdb-location-add (alist number location)
1159   (let ((ret-val alist))
1160     (setq ret-val
1161           (elmo-msgdb-append-element ret-val (cons number location)))
1162     ret-val))
1163
1164 (defsubst elmo-msgdb-location-save (dir alist)
1165   (elmo-object-save
1166    (expand-file-name
1167     elmo-msgdb-location-filename
1168     dir) alist))
1169
1170 (put 'elmo-msgdb-do-each-entity 'lisp-indent-function '1)
1171 (def-edebug-spec elmo-msgdb-do-each-entity
1172   ((symbolp form &rest form) &rest form))
1173 (defmacro elmo-msgdb-do-each-entity (spec &rest form)
1174   `(dolist (,(car spec) (elmo-msgdb-get-overview ,(car (cdr spec))))
1175      ,@form))
1176
1177 (require 'product)
1178 (product-provide (provide 'elmo-msgdb) (require 'elmo-version))
1179
1180 ;;; elmo-msgdb.el ends here