1 ;;; elmo-msgdb.el -- Message Database for Elmo.
3 ;; Copyright 1998,1999,2000 Yuuichi Teranishi <teranisi@gohome.org>
5 ;; Author: Yuuichi Teranishi <teranisi@gohome.org>
6 ;; Keywords: mail, net news
7 ;; Time-stamp: <00/03/06 13:24:13 teranisi>
9 ;; This file is part of ELMO (Elisp Library for Message Orchestration).
11 ;; This program is free software; you can redistribute it and/or modify
12 ;; it under the terms of the GNU General Public License as published by
13 ;; the Free Software Foundation; either version 2, or (at your option)
16 ;; This program is distributed in the hope that it will be useful,
17 ;; but WITHOUT ANY WARRANTY; without even the implied warranty of
18 ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
19 ;; GNU General Public License for more details.
21 ;; You should have received a copy of the GNU General Public License
22 ;; along with GNU Emacs; see the file COPYING. If not, write to the
23 ;; Free Software Foundation, Inc., 59 Temple Place - Suite 330,
24 ;; Boston, MA 02111-1307, USA.
33 (eval-when-compile (require 'cl))
40 (defun elmo-msgdb-expand-path (folder &optional spec)
41 (convert-standard-filename
42 (let* ((spec (or spec (elmo-folder-get-spec folder)))
47 (setq fld (elmo-imap4-spec-mailbox spec))
48 (if (string= "inbox" (downcase fld))
50 (if (eq (string-to-char fld) ?/)
51 (setq fld (substring fld 1 (length fld))))
54 (expand-file-name (or (elmo-imap4-spec-username spec) "nobody")
56 (elmo-imap4-spec-hostname spec)
63 (elmo-nntp-spec-group spec)
64 (expand-file-name (or (elmo-nntp-spec-hostname spec) "nowhere")
65 (expand-file-name "nntp"
68 (expand-file-name (elmo-safe-filename (nth 1 spec))
69 (expand-file-name "maildir"
72 (expand-file-name (elmo-safe-filename (nth 1 spec))
73 (expand-file-name "folder"
76 (expand-file-name (elmo-safe-filename folder)
77 (expand-file-name "multi"
81 (elmo-safe-filename folder)
82 (expand-file-name "filter"
88 (elmo-replace-in-string
89 (elmo-replace-in-string
90 (elmo-replace-in-string
96 (expand-file-name (concat (symbol-name type) "/"
97 (symbol-name (nth 2 spec)))
101 (elmo-safe-filename (elmo-pop3-spec-username spec))
102 (expand-file-name (elmo-pop3-spec-hostname spec)
106 ((eq type 'localnews)
108 (elmo-replace-in-string (nth 1 spec) "/" ".")
109 (expand-file-name "localnews"
112 (expand-file-name (elmo-safe-filename (concat (symbol-name (nth 1 spec))
114 (expand-file-name "internal"
117 (expand-file-name (elmo-safe-filename (nth 1 spec))
118 (expand-file-name "internal/cache"
120 (t ; local dir or undefined type
122 (setq fld (nth 1 spec))
123 (if (file-name-absolute-p fld)
124 (setq fld (elmo-safe-filename fld)))
125 (expand-file-name fld
126 (expand-file-name (symbol-name type)
127 elmo-msgdb-dir)))))))
129 (defsubst elmo-msgdb-append-element (list element)
131 ;(append list (list element))
132 (nconc list (list element))
136 (defsubst elmo-msgdb-get-overview (msgdb)
138 (defsubst elmo-msgdb-get-number-alist (msgdb)
140 (defsubst elmo-msgdb-get-mark-alist (msgdb)
142 (defsubst elmo-msgdb-get-location (msgdb)
146 ;; number <-> Message-ID handling
148 (defsubst elmo-msgdb-number-add (alist number id)
149 (let ((ret-val alist))
151 (elmo-msgdb-append-element ret-val (cons number id)))
155 ;; parsistent mark handling
158 (defvar elmo-msgdb-global-mark-alist nil)
160 (defun elmo-msgdb-global-mark-delete (msgid)
161 (let* ((path (expand-file-name
162 elmo-msgdb-global-mark-filename
164 (malist (or elmo-msgdb-global-mark-alist
165 (setq elmo-msgdb-global-mark-alist
166 (elmo-object-load path))))
168 (when (setq match (assoc msgid malist))
169 (setq elmo-msgdb-global-mark-alist
170 (delete match elmo-msgdb-global-mark-alist))
171 (elmo-object-save path elmo-msgdb-global-mark-alist))))
173 (defun elmo-msgdb-global-mark-set (msgid mark)
174 (let* ((path (expand-file-name
175 elmo-msgdb-global-mark-filename
177 (malist (or elmo-msgdb-global-mark-alist
178 (setq elmo-msgdb-global-mark-alist
179 (elmo-object-load path))))
181 (if (setq match (assoc msgid malist))
183 (setq elmo-msgdb-global-mark-alist
184 (nconc elmo-msgdb-global-mark-alist
185 (list (cons msgid mark)))))
186 (elmo-object-save path elmo-msgdb-global-mark-alist)))
188 (defun elmo-msgdb-global-mark-get (msgid)
189 (cdr (assoc msgid (or elmo-msgdb-global-mark-alist
190 (setq elmo-msgdb-global-mark-alist
193 elmo-msgdb-global-mark-filename
194 elmo-msgdb-dir)))))))
197 ;; number <-> location handling
199 (defsubst elmo-msgdb-location-load (dir)
202 elmo-msgdb-location-filename
205 (defsubst elmo-msgdb-location-add (alist number location)
206 (let ((ret-val alist))
208 (elmo-msgdb-append-element ret-val (cons number location)))
211 (defsubst elmo-msgdb-location-save (dir alist)
214 elmo-msgdb-location-filename
217 (defun elmo-list-folder-by-location (spec locations &optional msgdb)
218 (let* ((path (elmo-msgdb-expand-path nil spec))
219 (location-alist (if msgdb
220 (elmo-msgdb-get-location msgdb)
221 (elmo-msgdb-location-load path)))
222 (locations-in-db (mapcar 'cdr location-alist))
223 result new-locs new-alist deleted-locs i
226 (elmo-delete-if (function
227 (lambda (x) (member x locations-in-db)))
230 (elmo-delete-if (function
231 (lambda (x) (member x locations)))
233 (setq modified new-locs)
234 (setq i (or (elmo-max-of-list (mapcar 'car location-alist)) 0))
239 (delq (rassoc x location-alist) location-alist))))
243 (setq new-alist (cons (cons i (car new-locs)) new-alist))
244 (setq new-locs (cdr new-locs)))
245 (setq result (nconc location-alist new-alist))
246 (setq result (sort result (lambda (x y) (< (car x)(car y)))))
247 (if modified (elmo-msgdb-location-save path result))
248 (mapcar 'car result)))
251 ;; persistent mark handling
253 (defun elmo-msgdb-mark-set (alist id mark)
254 (let ((ret-val alist)
256 (setq entity (assq id alist))
259 ;; delete this entity
260 (setq ret-val (delq entity alist))
262 (setcar (cdr entity) mark))
264 (setq ret-val (elmo-msgdb-append-element ret-val
268 (defun elmo-msgdb-mark-append (alist id mark)
270 (setq alist (elmo-msgdb-append-element alist
273 (defun elmo-msgdb-mark-alist-to-seen-list (number-alist mark-alist seen-marks)
274 "Make seen-list from mark-alist"
275 (let ((seen-mark-list (string-to-char-list seen-marks))
278 (if (setq ent (assq (car (car number-alist)) mark-alist))
280 (memq (string-to-char (cadr ent)) seen-mark-list))
281 (setq ret-val (cons (cdr (car number-alist)) ret-val)))
282 (setq ret-val (cons (cdr (car number-alist)) ret-val)))
283 (setq number-alist (cdr number-alist)))
290 (defsubst elmo-msgdb-get-field-value (field-name beg end buffer)
294 (narrow-to-region beg end)
295 (elmo-field-body field-name))))
297 (defun elmo-multiple-field-body (name &optional boundary)
300 (std11-narrow-to-header boundary)
301 (goto-char (point-min))
302 (let ((case-fold-search t)
304 (while (re-search-forward (concat "^" name ":[ \t]*") nil t)
307 (list (buffer-substring-no-properties
308 (match-end 0) (std11-field-end))))))
311 (defun elmo-multiple-fields-body-list (field-names &optional boundary)
312 "Return list of each field-bodies of FIELD-NAMES of the message header
313 in current buffer. If BOUNDARY is not nil, it is used as message
317 (std11-narrow-to-header boundary)
318 (let* ((case-fold-search t)
320 field-name field-body)
321 (while (setq field-name (car s-rest))
322 (goto-char (point-min))
323 (while (re-search-forward (concat "^" field-name ":[ \t]*") nil t)
326 (list (buffer-substring-no-properties
327 (match-end 0) (std11-field-end))))))
328 (setq s-rest (cdr s-rest)))
331 (defsubst elmo-msgdb-remove-field-string (string)
332 (if (string-match (concat std11-field-head-regexp "[ \t]*") string)
333 (substring string (match-end 0))
336 (defsubst elmo-msgdb-get-last-message-id (string)
342 (goto-char (point-max))
343 (when (search-backward "<" nil t)
345 (if (search-forward ">" nil t)
346 (elmo-replace-in-string
347 (buffer-substring beg (point)) "\n[ \t]*" ""))))))))
349 (defun elmo-msgdb-number-load (dir)
351 (expand-file-name elmo-msgdb-number-filename dir)))
353 (defun elmo-msgdb-overview-load (dir)
355 (expand-file-name elmo-msgdb-overview-filename dir)))
357 (defun elmo-msgdb-mark-load (dir)
359 (expand-file-name elmo-msgdb-mark-filename dir)))
361 (defsubst elmo-msgdb-seen-load (dir)
362 (elmo-object-load (expand-file-name
363 elmo-msgdb-seen-filename
366 (defun elmo-msgdb-number-save (dir obj)
368 (expand-file-name elmo-msgdb-number-filename dir)
371 (defun elmo-msgdb-mark-save (dir obj)
373 (expand-file-name elmo-msgdb-mark-filename dir)
376 (defsubst elmo-msgdb-seen-save (dir obj)
378 (expand-file-name elmo-msgdb-seen-filename dir)
381 (defsubst elmo-msgdb-overview-save (dir overview)
383 (expand-file-name elmo-msgdb-overview-filename dir)
386 (defun elmo-msgdb-delete-msgs (folder msgs msgdb &optional reserve-cache)
387 "Delete MSGS from FOLDER in MSGDB.
388 content of MSGDB is changed."
390 (let* ((msg-list msgs)
391 (dir (elmo-msgdb-expand-path folder))
392 (overview (or (car msgdb)
393 (elmo-msgdb-overview-load dir)))
394 (number-alist (or (cadr msgdb)
395 (elmo-msgdb-number-load dir)))
396 (mark-alist (or (caddr msgdb)
397 (elmo-msgdb-mark-load dir)))
399 ;; remove from current database.
401 (setq message-id (cdr (assq (car msg-list) number-alist)))
402 (if (and (not reserve-cache) message-id)
403 (elmo-cache-delete message-id
404 folder (car msg-list)))
405 ;; This is no good!!!!
406 ;(setq overview (delete (assoc message-id overview) overview))
409 (elmo-msgdb-overview-get-entity-by-number overview
413 (delq (assq (car msg-list) number-alist) number-alist))
414 (setq mark-alist (delq (assq (car msg-list) mark-alist) mark-alist))
415 (setq msg-list (cdr msg-list)))
416 (setcar msgdb overview)
417 (setcar (cdr msgdb) number-alist)
418 (setcar (cddr msgdb) mark-alist))
421 (defsubst elmo-msgdb-set-overview (msgdb overview)
422 (setcar msgdb overview))
424 (defsubst elmo-msgdb-set-number-alist (msgdb number-alist)
425 (setcar (cdr msgdb) number-alist))
427 (defsubst elmo-msgdb-set-mark-alist (msgdb mark-alist)
428 (setcar (cddr msgdb) mark-alist))
430 (defsubst elmo-msgdb-overview-entity-get-references (entity)
431 (and entity (aref (cdr entity) 1)))
433 ;; entity -> parent-entity
434 (defsubst elmo-msgdb-overview-get-parent-entity (entity database)
435 (setq entity (elmo-msgdb-overview-entity-get-references entity))
436 ;; entity is parent-id.
437 (and entity (assoc entity database)))
439 (defsubst elmo-msgdb-overview-entity-get-number (entity)
440 (and entity (aref (cdr entity) 0)))
442 (defsubst elmo-msgdb-overview-entity-get-from-no-decode (entity)
443 (and entity (aref (cdr entity) 2)))
445 (defsubst elmo-msgdb-overview-entity-get-from (entity)
447 (aref (cdr entity) 2)
448 (decode-mime-charset-string (aref (cdr entity) 2)
451 (defsubst elmo-msgdb-overview-entity-set-number (entity number)
452 (and entity (aset (cdr entity) 0 number))
454 ;(setcar (cadr entity) number) entity)
456 (defsubst elmo-msgdb-overview-entity-set-from (entity from)
457 (and entity (aset (cdr entity) 2 from))
460 (defsubst elmo-msgdb-overview-entity-get-subject (entity)
462 (aref (cdr entity) 3)
463 (decode-mime-charset-string (aref (cdr entity) 3)
466 (defsubst elmo-msgdb-overview-entity-get-subject-no-decode (entity)
467 (and entity (aref (cdr entity) 3)))
469 (defsubst elmo-msgdb-overview-entity-set-subject (entity subject)
470 (and entity (aset (cdr entity) 3 subject))
473 (defsubst elmo-msgdb-overview-entity-get-date (entity)
474 (and entity (aref (cdr entity) 4)))
476 (defsubst elmo-msgdb-overview-entity-get-to (entity)
477 (and entity (aref (cdr entity) 5)))
479 (defsubst elmo-msgdb-overview-entity-get-cc (entity)
480 (and entity (aref (cdr entity) 6)))
482 (defsubst elmo-msgdb-overview-entity-get-size (entity)
483 (and entity (aref (cdr entity) 7)))
485 (defsubst elmo-msgdb-overview-entity-get-id (entity)
486 (and entity (car entity)))
488 (defsubst elmo-msgdb-overview-entity-get-extra-field (entity field-name)
489 (let ((extra (and entity (aref (cdr entity) 8))))
491 (cdr (assoc field-name extra)))))
493 (defun elmo-msgdb-overview-get-entity-by-number (database number)
498 (if (eq (elmo-msgdb-overview-entity-get-number (car db)) number)
500 (setq entity (car db))
502 (setq db (cdr db)))))
506 ;; deleted message handling
508 (defun elmo-msgdb-killed-list-load (dir)
510 (expand-file-name elmo-msgdb-killed-filename dir)
513 (defun elmo-msgdb-killed-list-save (dir killed-list)
515 (expand-file-name elmo-msgdb-killed-filename dir)
518 (defun elmo-msgdb-killed-message-p (killed-list msg)
530 (if (and (<= (car entity) msg)
531 (<= msg (cdr entity)))
535 (defun elmo-msgdb-set-as-killed (killed-list msg)
536 "if cons cell, set car-cdr messages as killed.
537 if integer, set number th message as killed."
538 (let ((dlist killed-list)
539 (ret-val killed-list)
543 (while (and dlist (not found))
544 (setq entity (car dlist))
545 (if (or (and (integerp entity) (eq entity msg))
547 (<= (car entity) msg)
548 (<= msg (cdr entity))))
550 (setq dlist (cdr dlist))
553 (setq ret-val (elmo-msgdb-append-element killed-list msg)))
556 (while (and dlist (not found))
557 (setq entity (car dlist))
558 (if (integerp entity)
560 ((and (<= (car msg) entity)(<= entity (cdr msg)))
564 ((= (1- (car msg)) entity)
565 (setcar dlist (cons entity (cdr msg)))
568 ((= (1+ (cdr msg)) entity)
569 (setcar dlist (cons (car msg) entity))
573 (cond ; there are four patterns
574 ((and (<= (car msg) (car entity))
575 (<= (cdr entity) (cdr msg)))
578 ((and (< (car entity)(car msg))
579 (< (cdr msg) (cdr entity)))
581 ((and (<= (car msg) (car entity))
582 (<= (cdr msg) (cdr entity)))
583 (setcar dlist (cons (car msg) (cdr entity)))
585 ((and (<= (car entity) (car msg))
586 (<= (cdr entity) (cdr msg)))
587 (setcar dlist (cons (car entity) (cdr msg)))
589 (setq dlist (cdr dlist)))
591 (setq ret-val (elmo-msgdb-append-element killed-list msg)))))
594 (defun elmo-msgdb-finfo-load ()
595 (elmo-object-load (expand-file-name
596 elmo-msgdb-finfo-filename
598 elmo-mime-charset t))
600 (defun elmo-msgdb-finfo-save (finfo)
601 (elmo-object-save (expand-file-name
602 elmo-msgdb-finfo-filename
604 finfo elmo-mime-charset))
606 (defun elmo-msgdb-flist-load (folder)
607 (let ((flist-file (expand-file-name
608 elmo-msgdb-flist-filename
609 (elmo-msgdb-expand-path folder (list 'folder folder)))))
610 (elmo-object-load flist-file nil t)))
612 (defun elmo-msgdb-flist-save (folder flist)
613 (let ((flist-file (expand-file-name
614 elmo-msgdb-flist-filename
615 (elmo-msgdb-expand-path folder (list 'folder folder)))))
616 (elmo-object-save flist-file flist)))
618 (defun elmo-crosspost-alist-load ()
619 (elmo-object-load (expand-file-name
620 elmo-crosspost-alist-filename
624 (defun elmo-crosspost-alist-save (alist)
625 (elmo-object-save (expand-file-name
626 elmo-crosspost-alist-filename
630 (defsubst elmo-msgdb-create-overview-from-buffer (number &optional size time)
631 "Create overview entity from current buffer.
632 Header region is supposed to be narrowed."
634 (let ((extras elmo-msgdb-extra-fields)
635 message-id references from subject to cc date
637 (elmo-set-buffer-multibyte default-enable-multibyte-characters)
638 (setq message-id (elmo-field-body "message-id"))
640 (or (elmo-msgdb-get-last-message-id
641 (elmo-field-body "in-reply-to"))
642 (elmo-msgdb-get-last-message-id
643 (elmo-field-body "references"))))
644 (setq from (elmo-mime-string (elmo-delete-char
647 (elmo-field-body "from")
649 (setq subject (elmo-mime-string (or (elmo-field-body "subject")
651 (setq date (or (elmo-field-body "date") time))
652 (setq to (mapconcat 'identity (elmo-multiple-field-body "to") ","))
653 (setq cc (mapconcat 'identity (elmo-multiple-field-body "cc") ","))
655 (if (setq size (elmo-field-body "content-length"))
656 (setq size (string-to-int size))
657 (setq size 0)));; No mean...
659 (if (setq field-body (elmo-field-body (car extras)))
660 (setq extra (cons (cons (downcase (car extras))
662 (setq extras (cdr extras)))
663 (cons message-id (vector number references
664 from subject date to cc
668 (defun elmo-msgdb-overview-sort-by-date (overview)
674 (timezone-make-date-sortable
675 (elmo-msgdb-overview-entity-get-date x))
676 (timezone-make-date-sortable
677 (elmo-msgdb-overview-entity-get-date y)))
680 (defun elmo-msgdb-sort-by-date (msgdb)
681 (message "Sorting...")
682 (let ((overview (elmo-msgdb-get-overview msgdb)))
683 (setq overview (elmo-msgdb-overview-sort-by-date overview))
684 (message "Sorting...done.")
685 (list overview (nth 1 msgdb)(nth 2 msgdb))))
687 (defsubst elmo-msgdb-search-overview-entity (number number-alist overview)
688 (let ((message-id (cdr (assq number number-alist)))
691 (assoc message-id overview)
692 (elmo-msgdb-overview-get-entity-by-number overview number))))
694 (defsubst elmo-msgdb-append (msgdb msgdb-append)
696 (nconc (car msgdb) (car msgdb-append))
697 (nconc (cadr msgdb) (cadr msgdb-append))
698 (nconc (caddr msgdb) (caddr msgdb-append))
699 (nconc (cadddr msgdb) (cadddr msgdb-append))))
701 (defun elmo-msgdb-delete-path (folder &optional spec)
702 (let ((path (elmo-msgdb-expand-path folder spec)))
703 (if (file-directory-p path)
704 (elmo-delete-directory path t))))
706 (defun elmo-msgdb-rename-path (old-folder new-folder &optional old-spec new-spec)
707 (let* ((old (directory-file-name (elmo-msgdb-expand-path old-folder old-spec)))
708 (new (directory-file-name (elmo-msgdb-expand-path new-folder new-spec)))
709 (new-dir (directory-file-name (file-name-directory new))))
710 (if (not (file-directory-p old))
712 (if (file-exists-p new)
713 (error "already exists directory: %s" new)
714 (if (not (file-exists-p new-dir))
715 (elmo-make-directory new-dir))
716 (rename-file old new)))))
718 (provide 'elmo-msgdb)
720 ;;; elmo-msgdb.el ends here