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