1 ;;; elmo-msgdb.el --- Message Database for ELMO.
3 ;; Copyright (C) 1998,1999,2000 Yuuichi Teranishi <teranisi@gohome.org>
4 ;; Copyright (C) 2000 Masahiro MURATA <muse@ba2.so-net.ne.jp>
6 ;; Author: Yuuichi Teranishi <teranisi@gohome.org>
7 ;; Masahiro MURATA <muse@ba2.so-net.ne.jp>
8 ;; Keywords: mail, net news
10 ;; This file is part of ELMO (Elisp Library for Message Orchestration).
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)
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.
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.
34 (eval-when-compile (require 'cl))
41 (require 'modb-entity)
43 (defconst elmo-msgdb-new-mark "N"
44 "Mark for new message.")
46 (defconst elmo-msgdb-unread-uncached-mark "U"
47 "Mark for unread and uncached message.")
49 (defconst elmo-msgdb-unread-cached-mark "!"
50 "Mark for unread but already cached message.")
52 (defconst elmo-msgdb-read-uncached-mark "u"
53 "Mark for read but uncached message.")
55 (defconst elmo-msgdb-answered-cached-mark "&"
56 "Mark for answered and cached message.")
58 (defconst elmo-msgdb-answered-uncached-mark "A"
59 "Mark for answered but cached message.")
61 (defconst elmo-msgdb-important-mark "$"
62 "Mark for important message.")
66 ;; MSGDB elmo-load-msgdb PATH
68 ;; NUMBER elmo-msgdb-get-number MSGDB MESSAGE-ID
69 ;; FIELD-VALUE elmo-msgdb-get-field MSGDB NUMBER FIELD
70 ;; elmo-msgdb-sort-by-date MSGDB
72 ;; elmo-flag-table-load
73 ;; elmo-flag-table-set
74 ;; elmo-flag-table-get
75 ;; elmo-flag-table-save
77 ;; elmo-msgdb-overview-save DIR OBJ
80 ;; elmo-msgdb-get-parent-entity ENTITY MSGDB
82 ;; elmo-msgdb-killed-list-load DIR
83 ;; elmo-msgdb-killed-list-save DIR
84 ;; elmo-msgdb-append-to-killed-list FOLDER MSG
85 ;; elmo-msgdb-killed-list-length KILLED-LIST
86 ;; elmo-msgdb-max-of-killed KILLED-LIST
87 ;; elmo-msgdb-killed-message-p KILLED-LIST MSG
88 ;; elmo-living-messages MESSAGES KILLED-LIST
90 ;; elmo-msgdb-finfo-load
91 ;; elmo-msgdb-finfo-save
92 ;; elmo-msgdb-flist-load
93 ;; elmo-msgdb-flist-save
95 ;; elmo-crosspost-alist-load
96 ;; elmo-crosspost-alist-save
98 ;; elmo-msgdb-create-overview-from-buffer NUMBER SIZE TIME
99 ;; elmo-msgdb-create-overview-entity-from-file NUMBER FILE
101 ;; elmo-folder-get-info
102 ;; elmo-folder-get-info-max
103 ;; elmo-folder-get-info-length
104 ;; elmo-folder-get-info-unread
106 ;;; Helper functions for MSGDB
108 (defun elmo-load-msgdb (location)
109 "Load the MSGDB from PATH."
110 (let ((msgdb (elmo-make-msgdb location)))
111 (elmo-msgdb-load msgdb)
114 (defun elmo-make-msgdb (&optional location type)
116 (let* ((type (or type elmo-msgdb-default-type))
117 (class (intern (format "modb-%s" type))))
119 (luna-make-entity class
120 :location location)))
122 (defsubst elmo-msgdb-get-number (msgdb message-id)
123 "Get number of the message which corrensponds to MESSAGE-ID from MSGDB."
124 (elmo-msgdb-overview-entity-get-number
125 (elmo-msgdb-message-entity msgdb message-id)))
127 (defsubst elmo-msgdb-get-field (msgdb number field)
128 "Get FIELD value of the message with NUMBER from MSGDB."
130 (message-id (elmo-msgdb-overview-entity-get-id
131 (elmo-msgdb-message-entity
133 (subject (elmo-msgdb-overview-entity-get-subject
134 (elmo-msgdb-message-entity
136 (size (elmo-msgdb-overview-entity-get-size
137 (elmo-msgdb-message-entity
139 (date (elmo-msgdb-overview-entity-get-date
140 (elmo-msgdb-message-entity
142 (to (elmo-msgdb-overview-entity-get-to
143 (elmo-msgdb-message-entity
145 (cc (elmo-msgdb-overview-entity-get-cc
146 (elmo-msgdb-message-entity
149 (defun elmo-msgdb-sort-by-date (msgdb)
150 (elmo-msgdb-sort-entities
152 (lambda (x y app-data)
155 (timezone-make-date-sortable
156 (elmo-msgdb-overview-entity-get-date x))
157 (timezone-make-date-sortable
158 (elmo-msgdb-overview-entity-get-date y)))
162 (defsubst elmo-msgdb-append-element (list element)
164 ;;; (append list (list element))
165 (nconc list (list element))
170 ;; number <-> Message-ID handling
172 (defsubst elmo-msgdb-number-add (alist number id)
173 (let ((ret-val alist))
175 (elmo-msgdb-append-element ret-val (cons number id)))
180 (defvar elmo-flag-table-filename "flag-table")
181 (defun elmo-flag-table-load (dir)
182 "Load flag hashtable for MSGDB."
183 (let ((table (elmo-make-hash))
184 ;; For backward compatibility
185 (seen-file (expand-file-name elmo-msgdb-seen-filename dir))
187 (when (file-exists-p seen-file)
188 (dolist (msgid (elmo-object-load seen-file))
189 (elmo-set-hash-val msgid '(read) table))
190 (delete-file seen-file))
191 (dolist (pair (elmo-object-load
192 (expand-file-name elmo-flag-table-filename dir)))
193 (setq value (cdr pair))
194 (elmo-set-hash-val (car pair)
197 ;; Following cases for backward compatibility.
205 (defun elmo-flag-table-set (flag-table msg-id flags)
206 (elmo-set-hash-val msg-id (or flags '(read)) flag-table))
208 (defun elmo-flag-table-get (flag-table msg-id)
209 (let ((flags (elmo-get-hash-val msg-id flag-table)))
212 (and (elmo-msgdb-global-mark-get msg-id)
214 (and (elmo-file-cache-exists-p msg-id)
216 (elmo-list-delete '(important cached read)
217 (copy-sequence flags)
221 (defun elmo-flag-table-save (dir flag-table)
223 (expand-file-name elmo-flag-table-filename dir)
226 (mapatoms (lambda (atom)
227 (setq list (cons (cons (symbol-name atom)
233 ;; persistent mark handling
236 (defun elmo-msgdb-mark-append (alist id mark)
238 (setq alist (elmo-msgdb-append-element alist
241 (defun elmo-msgdb-flag-table (msgdb &optional flag-table)
242 ;; Make a table of msgid flag (read, answered)
243 (let ((flag-table (or flag-table
244 (elmo-make-hash (elmo-msgdb-length msgdb))))
246 (dolist (number (elmo-msgdb-list-messages msgdb))
247 (setq entity (elmo-msgdb-message-entity msgdb number))
250 (elmo-msgdb-overview-entity-get-id entity)
251 (elmo-msgdb-flags msgdb number)))
257 (defun elmo-multiple-field-body (name &optional boundary)
260 (std11-narrow-to-header boundary)
261 (goto-char (point-min))
262 (let ((case-fold-search t)
264 (while (re-search-forward (concat "^" name ":[ \t]*") nil t)
267 (list (buffer-substring-no-properties
268 (match-end 0) (std11-field-end))))))
271 (defun elmo-multiple-fields-body-list (field-names &optional boundary)
272 "Return list of each field-bodies of FIELD-NAMES of the message header
273 in current buffer. If BOUNDARY is not nil, it is used as message
277 (std11-narrow-to-header boundary)
278 (let* ((case-fold-search t)
280 field-name field-body)
281 (while (setq field-name (car s-rest))
282 (goto-char (point-min))
283 (while (re-search-forward (concat "^" field-name ":[ \t]*") nil t)
286 (list (buffer-substring-no-properties
287 (match-end 0) (std11-field-end))))))
288 (setq s-rest (cdr s-rest)))
291 (defsubst elmo-msgdb-remove-field-string (string)
292 (if (string-match (concat std11-field-head-regexp "[ \t]*") string)
293 (substring string (match-end 0))
296 (defsubst elmo-msgdb-get-last-message-id (string)
302 (goto-char (point-max))
303 (when (search-backward "<" nil t)
305 (if (search-forward ">" nil t)
306 (elmo-replace-in-string
307 (buffer-substring beg (point)) "\n[ \t]*" ""))))))))
309 (defun elmo-msgdb-number-load (dir)
311 (expand-file-name elmo-msgdb-number-filename dir)))
313 (defun elmo-msgdb-overview-load (dir)
315 (expand-file-name elmo-msgdb-overview-filename dir)))
317 (defun elmo-msgdb-mark-load (dir)
319 (expand-file-name elmo-msgdb-mark-filename dir)))
321 (defsubst elmo-msgdb-seen-load (dir)
322 (elmo-object-load (expand-file-name
323 elmo-msgdb-seen-filename
326 (defun elmo-msgdb-number-save (dir obj)
328 (expand-file-name elmo-msgdb-number-filename dir)
331 (defun elmo-msgdb-mark-save (dir obj)
333 (expand-file-name elmo-msgdb-mark-filename dir)
336 (defsubst elmo-msgdb-out-of-date-messages (msgdb)
337 (dolist (number (elmo-msgdb-list-flagged msgdb 'new))
338 (elmo-msgdb-unset-flag msgdb number 'new)))
340 (defsubst elmo-msgdb-overview-save (dir overview)
342 (expand-file-name elmo-msgdb-overview-filename dir)
345 (defun elmo-msgdb-match-condition (msgdb condition number numbers)
346 "Check whether the condition of the message is satisfied or not.
347 MSGDB is the msgdb to search from.
348 CONDITION is the search condition.
349 NUMBER is the message number to check.
350 NUMBERS is the target message number list.
351 Return CONDITION itself if no entity exists in msgdb."
352 (let ((entity (elmo-msgdb-message-entity msgdb number)))
354 (elmo-msgdb-match-condition-internal condition
356 (elmo-msgdb-flags msgdb number)
360 ;; entity -> parent-entity
361 (defsubst elmo-msgdb-overview-get-parent-entity (entity database)
362 (setq entity (elmo-msgdb-overview-entity-get-references entity))
363 ;; entity is parent-id.
364 (and entity (assoc entity database)))
366 (defsubst elmo-msgdb-get-parent-entity (entity msgdb)
367 (setq entity (elmo-msgdb-overview-entity-get-references entity))
368 ;; entity is parent-id.
369 (and entity (elmo-msgdb-message-entity msgdb entity)))
372 ;; deleted message handling
374 (defun elmo-msgdb-killed-list-load (dir)
376 (expand-file-name elmo-msgdb-killed-filename dir)
379 (defun elmo-msgdb-killed-list-save (dir killed-list)
381 (expand-file-name elmo-msgdb-killed-filename dir)
384 (defun elmo-msgdb-killed-message-p (killed-list msg)
385 (elmo-number-set-member msg killed-list))
387 (defun elmo-msgdb-set-as-killed (killed-list msg)
388 (elmo-number-set-append killed-list msg))
390 (defun elmo-msgdb-killed-list-length (killed-list)
391 (let ((killed killed-list)
394 (if (consp (car killed))
395 (setq ret-val (+ ret-val 1 (- (cdar killed) (caar killed))))
396 (setq ret-val (+ ret-val 1)))
397 (setq killed (cdr killed)))
400 (defun elmo-msgdb-max-of-killed (killed-list)
401 (let ((klist killed-list)
407 (if (consp (car klist))
411 (setq klist (cdr klist)))
414 (defun elmo-living-messages (messages killed-list)
417 (mapcar (lambda (number)
418 (unless (elmo-number-set-member number killed-list)
423 (defun elmo-msgdb-finfo-load ()
424 (elmo-object-load (expand-file-name
425 elmo-msgdb-finfo-filename
426 elmo-msgdb-directory)
427 elmo-mime-charset t))
429 (defun elmo-msgdb-finfo-save (finfo)
430 (elmo-object-save (expand-file-name
431 elmo-msgdb-finfo-filename
432 elmo-msgdb-directory)
433 finfo elmo-mime-charset))
435 (defun elmo-msgdb-flist-load (fname)
436 (let ((flist-file (expand-file-name
437 elmo-msgdb-flist-filename
439 (elmo-safe-filename fname)
440 (expand-file-name "folder" elmo-msgdb-directory)))))
441 (elmo-object-load flist-file elmo-mime-charset t)))
443 (defun elmo-msgdb-flist-save (fname flist)
444 (let ((flist-file (expand-file-name
445 elmo-msgdb-flist-filename
447 (elmo-safe-filename fname)
448 (expand-file-name "folder" elmo-msgdb-directory)))))
449 (elmo-object-save flist-file flist elmo-mime-charset)))
451 (defun elmo-crosspost-alist-load ()
452 (elmo-object-load (expand-file-name
453 elmo-crosspost-alist-filename
454 elmo-msgdb-directory)
457 (defun elmo-crosspost-alist-save (alist)
458 (elmo-object-save (expand-file-name
459 elmo-crosspost-alist-filename
460 elmo-msgdb-directory)
463 (defun elmo-msgdb-get-message-id-from-buffer ()
464 (let ((msgid (elmo-field-body "message-id")))
466 (if (string-match "<\\(.+\\)>$" msgid)
468 (concat "<" msgid ">")) ; Invaild message-id.
469 ;; no message-id, so put dummy msgid.
470 (concat "<" (timezone-make-date-sortable
471 (elmo-field-body "date"))
472 (nth 1 (eword-extract-address-components
473 (or (elmo-field-body "from") "nobody"))) ">"))))
475 (defsubst elmo-msgdb-create-overview-from-buffer (number &optional size time)
476 "Create overview entity from current buffer.
477 Header region is supposed to be narrowed."
479 (let ((extras elmo-msgdb-extra-fields)
480 (default-mime-charset default-mime-charset)
481 message-id references from subject to cc date
482 extra field-body charset)
483 (elmo-set-buffer-multibyte default-enable-multibyte-characters)
484 (setq message-id (elmo-msgdb-get-message-id-from-buffer))
485 (and (setq charset (cdr (assoc "charset" (mime-read-Content-Type))))
486 (setq charset (intern-soft charset))
487 (setq default-mime-charset charset))
489 (or (elmo-msgdb-get-last-message-id
490 (elmo-field-body "in-reply-to"))
491 (elmo-msgdb-get-last-message-id
492 (elmo-field-body "references"))))
493 (setq from (elmo-replace-in-string
494 (elmo-mime-string (or (elmo-field-body "from")
497 subject (elmo-replace-in-string
498 (elmo-mime-string (or (elmo-field-body "subject")
501 (setq date (or (elmo-field-body "date") time))
502 (setq to (mapconcat 'identity (elmo-multiple-field-body "to") ","))
503 (setq cc (mapconcat 'identity (elmo-multiple-field-body "cc") ","))
505 (if (setq size (elmo-field-body "content-length"))
506 (setq size (string-to-int size))
507 (setq size 0)));; No mean...
509 (if (setq field-body (elmo-field-body (car extras)))
510 (setq extra (cons (cons (downcase (car extras))
512 (setq extras (cdr extras)))
513 (cons message-id (vector number references
514 from subject date to cc
518 (defsubst elmo-msgdb-insert-file-header (file)
519 "Insert the header of the article."
521 insert-file-contents-pre-hook ; To avoid autoconv-xmas...
522 insert-file-contents-post-hook
524 (when (file-exists-p file)
525 ;; Read until header separator is found.
526 (while (and (eq elmo-msgdb-file-header-chop-length
528 (insert-file-contents-as-binary
530 (incf beg elmo-msgdb-file-header-chop-length))))
531 (prog1 (not (search-forward "\n\n" nil t))
532 (goto-char (point-max))))))))
534 (defsubst elmo-msgdb-create-overview-entity-from-file (number file)
535 (let (insert-file-contents-pre-hook ; To avoid autoconv-xmas...
536 insert-file-contents-post-hook header-end
537 (attrib (file-attributes file))
540 (if (not (file-exists-p file))
542 (setq size (nth 7 attrib))
543 (setq mtime (timezone-make-date-arpa-standard
544 (current-time-string (nth 5 attrib)) (current-time-zone)))
545 ;; insert header from file.
548 (elmo-msgdb-insert-file-header file)
549 (error (throw 'done nil)))
550 (goto-char (point-min))
552 (if (re-search-forward "\\(^--.*$\\)\\|\\(\n\n\\)" nil t)
555 (narrow-to-region (point-min) header-end)
556 (elmo-msgdb-create-overview-from-buffer number size mtime))))))
558 (defsubst elmo-folder-get-info (folder &optional hashtb)
559 (elmo-get-hash-val folder
560 (or hashtb elmo-folder-info-hashtb)))
562 (defun elmo-folder-get-info-max (folder)
563 "Get folder info from cache."
564 (nth 3 (elmo-folder-get-info folder)))
566 (defun elmo-folder-get-info-length (folder)
567 (nth 2 (elmo-folder-get-info folder)))
569 (defun elmo-folder-get-info-unread (folder)
570 (nth 1 (elmo-folder-get-info folder)))
572 (defsubst elmo-msgdb-location-load (dir)
575 elmo-msgdb-location-filename
578 (defsubst elmo-msgdb-location-add (alist number location)
579 (let ((ret-val alist))
581 (elmo-msgdb-append-element ret-val (cons number location)))
584 (defsubst elmo-msgdb-location-save (dir alist)
587 elmo-msgdb-location-filename
591 (product-provide (provide 'elmo-msgdb) (require 'elmo-version))
593 ;;; elmo-msgdb.el ends here