1 ;;; elmo-cache.el -- Cache modules for Elmo.
3 ;; Copyright (C) 1998,1999,2000 Yuuichi Teranishi <teranisi@gohome.org>
4 ;; Copyright (C) 2000 Kenichi OKADA <okada@opaopa.org>
6 ;; Author: Yuuichi Teranishi <teranisi@gohome.org>
7 ;; Kenichi OKADA <okada@opaopa.org>
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.
36 (defsubst elmo-cache-to-msgid (filename)
37 (concat "<" (elmo-recover-string-from-filename filename) ">"))
41 (defun elmo-file-cache-get-path (msgid &optional section)
42 "Get cache path for MSGID.
43 If optional argument SECTION is specified, partial cache path is returned."
44 (if (setq msgid (elmo-msgid-to-cache msgid))
47 (format "%s/%s/%s/%s/%s"
50 (elmo-cache-get-path-subr msgid)
56 (elmo-cache-get-path-subr msgid)
59 (defmacro elmo-file-cache-expand-path (path section)
60 "Return file name for the file-cache corresponds to the section.
61 PATH is the file-cache path.
62 SECTION is the section string."
63 (` (expand-file-name (or (, section) "") (, path))))
65 (defun elmo-file-cache-delete (path)
66 "Delete a cache on PATH."
68 (when (file-exists-p path)
69 (if (file-directory-p path)
71 (setq files (directory-files path t "^[^\\.]"))
73 (delete-file (car files))
74 (setq files (cdr files)))
75 (delete-directory path))
76 (delete-file path)))))
78 (defun elmo-file-cache-exists-p (msgid)
79 "Returns 'section or 'entire if a cache which corresponds to MSGID exists."
80 (elmo-file-cache-status (elmo-file-cache-get msgid)))
82 (defun elmo-file-cache-save (cache-path section)
83 "Save current buffer as cache on PATH."
84 (let ((path (if section (expand-file-name section cache-path) cache-path))
86 (if (and (null section)
87 (file-directory-p path))
89 (setq files (directory-files path t "^[^\\.]"))
91 (delete-file (car files))
92 (setq files (cdr files)))
93 (delete-directory path))
95 (not (file-directory-p cache-path)))
96 (delete-file cache-path)))
98 (setq dir (directory-file-name (file-name-directory path)))
99 (if (not (file-exists-p dir))
100 (elmo-make-directory dir))
101 (write-region-as-binary (point-min) (point-max)
104 (defmacro elmo-make-file-cache (path status)
105 "PATH is the cache file name.
106 STATUS is one of 'section, 'entire or nil.
107 nil means no cache exists.
108 'section means partial section cache exists.
109 'entire means entire cache exists.
110 If the cache is partial file-cache, TYPE is 'partial."
111 (` (cons (, path) (, status))))
113 (defmacro elmo-file-cache-path (file-cache)
114 "Returns the file path of the FILE-CACHE."
115 (` (car (, file-cache))))
117 (defmacro elmo-file-cache-status (file-cache)
118 "Returns the status of the FILE-CACHE."
119 (` (cdr (, file-cache))))
121 (defun elmo-file-cache-get (msgid &optional section)
122 "Returns the current file-cache object associated with MSGID.
123 MSGID is the message-id of the message.
124 If optional argument SECTION is specified, get partial file-cache object
125 associated with SECTION."
127 (let ((path (elmo-cache-get-path msgid)))
128 (if (and path (file-exists-p path))
129 (if (file-directory-p path)
131 (if (file-exists-p (setq path (expand-file-name
133 (cons path 'section))
134 ;; section is not specified but sectional.
135 (cons path 'section))
138 (cons path 'entire)))
143 (defun elmo-cache-expire ()
145 (let* ((completion-ignore-case t)
146 (method (completing-read (format "Expire by (%s): "
147 elmo-cache-expire-default-method)
150 (if (string= method "")
151 (setq method elmo-cache-expire-default-method))
152 (funcall (intern (concat "elmo-cache-expire-by-" method)))))
154 (defun elmo-read-float-value-from-minibuffer (prompt &optional initial)
155 (let ((str (read-from-minibuffer prompt initial)))
157 ((string-match "[0-9]*\\.[0-9]+" str)
158 (string-to-number str))
159 ((string-match "[0-9]+" str)
160 (string-to-number (concat str ".0")))
161 (t (error "%s is not number" str)))))
163 (defun elmo-cache-expire-by-size (&optional kbytes)
164 "Expire cache file by size.
165 If KBYTES is kilo bytes (This value must be float)."
167 (let ((size (or kbytes
169 (elmo-read-float-value-from-minibuffer
170 "Enter cache disk size (Kbytes): "
172 (if (integerp elmo-cache-expire-default-size)
173 (float elmo-cache-expire-default-size)
174 elmo-cache-expire-default-size))))
175 (if (integerp elmo-cache-expire-default-size)
176 (float elmo-cache-expire-default-size))))
177 (locked (elmo-dop-lock-list-load))
181 (message "Checking disk usage...")
182 (setq total (/ (elmo-disk-usage
184 elmo-cache-dirname elmo-msgdb-dir)) Kbytes))
185 (setq beginning total)
186 (message "Checking disk usage...done")
187 (let ((cfl (elmo-cache-get-sorted-cache-file-list))
191 (while (and (<= size total)
192 (setq oldest (elmo-cache-get-oldest-cache-file-entity cfl)))
193 (setq cur-file (expand-file-name (car (cdr oldest)) (car oldest)))
194 (setq cur-size (/ (elmo-disk-usage cur-file) Kbytes))
195 (when (elmo-cache-force-delete cur-file locked)
196 (setq count (+ count 1))
197 (message "%d cache(s) are expired." count))
198 (setq deleted (+ deleted cur-size))
199 (setq total (- total cur-size)))
200 (message "%d cache(s) are expired from disk (%d Kbytes/%d Kbytes)."
201 count deleted beginning))))
203 (defun elmo-cache-make-file-entity (filename path)
204 (cons filename (elmo-get-last-accessed-time filename path)))
206 (defun elmo-cache-get-oldest-cache-file-entity (cache-file-list)
207 (let ((cfl cache-file-list)
208 flist firsts oldest-entity wonlist)
210 (setq flist (cdr (car cfl)))
211 (setq firsts (append firsts (list
212 (cons (car (car cfl))
214 (setq cfl (cdr cfl)))
217 (if (and (not oldest-entity)
218 (cdr (cdr (car firsts))))
219 (setq oldest-entity (car firsts)))
220 (if (and (cdr (cdr (car firsts)))
221 (cdr (cdr oldest-entity))
222 (> (cdr (cdr oldest-entity)) (cdr (cdr (car firsts)))))
223 (setq oldest-entity (car firsts)))
224 (setq firsts (cdr firsts)))
225 (setq wonlist (assoc (car oldest-entity) cache-file-list))
227 (setcdr wonlist (delete (car (cdr wonlist)) (cdr wonlist))))
230 (defun elmo-cache-get-sorted-cache-file-list ()
231 (let ((dirs (directory-files
232 (expand-file-name elmo-cache-dirname elmo-msgdb-dir)
237 (setq num (length dirs))
238 (message "Collecting cache info...")
240 (setq elist (mapcar (lambda (x)
241 (elmo-cache-make-file-entity x (car dirs)))
242 (directory-files (car dirs) nil "^[^\\.]")))
243 (setq ret-val (append ret-val
251 (when (> num elmo-display-progress-threshold)
253 (elmo-display-progress
254 'elmo-cache-get-sorted-cache-file-list "Collecting cache info..."
256 (setq dirs (cdr dirs)))
257 (message "Collecting cache info...done")
260 (defun elmo-cache-expire-by-age (&optional days)
261 (let ((age (or (and days (int-to-string days))
263 (read-from-minibuffer
264 (format "Enter days (%s): "
265 elmo-cache-expire-default-age)))
266 (int-to-string elmo-cache-expire-default-age)))
267 (dirs (directory-files
268 (expand-file-name elmo-cache-dirname elmo-msgdb-dir)
270 (locked (elmo-dop-lock-list-load))
274 (setq age elmo-cache-expire-default-age)
275 (setq age (string-to-int age)))
276 (setq curtime (current-time))
277 (setq curtime (+ (* (nth 0 curtime)
278 (float 65536)) (nth 1 curtime)))
280 (let ((files (directory-files (car dirs) t "^[^\\.]"))
281 (limit-age (* age 86400)))
283 (when (> (- curtime (elmo-get-last-accessed-time (car files)))
285 (when (elmo-cache-force-delete (car files) locked)
286 (setq count (+ 1 count))
287 (message "%d cache file(s) are expired." count)))
288 (setq files (cdr files))))
289 (setq dirs (cdr dirs)))))
291 (defun elmo-cache-search-all (folder condition from-msgs)
292 (let* ((number-alist (elmo-msgdb-number-load
293 (elmo-msgdb-expand-path folder)))
294 (number-list (or from-msgs (mapcar 'car number-alist)))
295 (num (length number-alist))
302 (if (and (memq (car (car number-alist)) number-list)
303 (setq cache-file (elmo-cache-exists-p (cdr (car
308 (elmo-file-field-condition-match cache-file condition
309 (car (car number-alist))
311 (setq ret-val (append ret-val (list (caar number-alist)))))
312 (when (> num elmo-display-progress-threshold)
314 (setq percent (/ (* i 100) num))
315 (elmo-display-progress
316 'elmo-cache-search-all "Searching..."
318 (setq number-alist (cdr number-alist)))
321 (defun elmo-cache-collect-sub-directories (init dir &optional recursively)
322 "Collect subdirectories under DIR."
324 (delete (expand-file-name elmo-cache-dirname
326 (directory-files dir t "^[^\\.]")))
328 (setq dirs (elmo-delete-if (lambda (x) (not (file-directory-p x))) dirs))
329 (setq ret-val (append init dirs))
330 (while (and recursively dirs)
332 (elmo-cache-collect-sub-directories
334 (car dirs) recursively))
335 (setq dirs (cdr dirs)))
338 (defun elmo-msgid-to-cache (msgid)
340 (string-match "<\\(.+\\)>$" msgid))
341 (elmo-replace-string-as-filename (elmo-match-string 1 msgid))))
343 (defun elmo-cache-get-path (msgid &optional folder number)
344 "Get path for cache file associated with MSGID, FOLDER, and NUMBER."
345 (if (setq msgid (elmo-msgid-to-cache msgid))
349 (format "%s/%s/%s@%s"
350 (elmo-cache-get-path-subr msgid)
353 (elmo-safe-filename folder))
355 (elmo-cache-get-path-subr msgid)
357 (expand-file-name elmo-cache-dirname
360 (defsubst elmo-cache-get-path-subr (msgid)
361 (let ((chars '(?0 ?1 ?2 ?3 ?4 ?5 ?6 ?7 ?8 ?9 ?A ?B ?C ?D ?E ?F))
362 (clist (string-to-char-list msgid))
365 (setq sum (+ sum (car clist)))
366 (setq clist (cdr clist)))
368 (nth (% (/ sum 16) 2) chars)
369 (nth (% sum 16) chars))))
372 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
375 ;; cache backend by Kenichi OKADA <okada@opaopa.org>
378 (defsubst elmo-cache-get-folder-directory (spec)
379 (if (file-name-absolute-p (nth 1 spec))
380 (nth 1 spec) ; already full path.
381 (expand-file-name (nth 1 spec)
382 (expand-file-name elmo-cache-dirname elmo-msgdb-dir))))
384 (defun elmo-cache-msgdb-expand-path (spec)
385 (let ((fld-name (nth 1 spec)))
386 (expand-file-name fld-name
387 (expand-file-name "internal/cache"
390 (defun elmo-cache-number-to-filename (spec number)
392 (elmo-cache-list-folder-subr spec nil t)))
394 (cdr (assq number number-alist)))))
396 (if (boundp 'nemacs-version)
397 (defsubst elmo-cache-insert-header (file)
398 "Insert the header of the article (Does not work on nemacs)."
399 (as-binary-input-file
400 (insert-file-contents file)))
401 (defsubst elmo-cache-insert-header (file)
402 "Insert the header of the article."
404 insert-file-contents-pre-hook ; To avoid autoconv-xmas...
405 insert-file-contents-post-hook
407 (when (file-exists-p file)
408 ;; Read until header separator is found.
409 (while (and (eq elmo-localdir-header-chop-length
411 (as-binary-input-file
412 (insert-file-contents
414 (incf beg elmo-localdir-header-chop-length)))))
415 (prog1 (not (search-forward "\n\n" nil t))
416 (goto-char (point-max)))))))))
418 (defsubst elmo-cache-msgdb-create-overview-entity-from-file (number file)
420 (let ((tmp-buffer (get-buffer-create " *ELMO Cache Temp*"))
421 insert-file-contents-pre-hook ; To avoid autoconv-xmas...
422 insert-file-contents-post-hook header-end
423 (attrib (file-attributes file))
425 (set-buffer tmp-buffer)
427 (if (not (file-exists-p file))
429 (setq size (nth 7 attrib))
430 (setq mtime (timezone-make-date-arpa-standard
431 (current-time-string (nth 5 attrib)) (current-time-zone)))
432 ;; insert header from file.
435 (elmo-cache-insert-header file)
436 (error (throw 'done nil)))
437 (goto-char (point-min))
439 (if (re-search-forward "\\(^--.*$\\)\\|\\(\n\n\\)" nil t)
442 (narrow-to-region (point-min) header-end)
443 (setq ret-val (elmo-msgdb-create-overview-from-buffer number size mtime))
444 (kill-buffer tmp-buffer))
447 (defun elmo-cache-msgdb-create-as-numlist (spec numlist new-mark
448 already-mark seen-mark
449 important-mark seen-list)
451 (let ((dir (elmo-cache-get-folder-directory spec))
452 (nalist (elmo-cache-list-folder-subr spec nil t))
453 overview number-alist mark-alist entity message-id
454 i percent len num seen gmark)
455 (setq len (length numlist))
457 (message "Creating msgdb...")
460 (elmo-cache-msgdb-create-overview-entity-from-file
464 (setq message-id (cdr (assq (car numlist) nalist)))) dir)))
467 (setq num (elmo-msgdb-overview-entity-get-number entity))
469 (elmo-msgdb-append-element
472 (elmo-msgdb-number-add number-alist num message-id))
473 (setq seen (member message-id seen-list))
474 (if (setq gmark (or (elmo-msgdb-global-mark-get message-id)
479 (elmo-msgdb-mark-append
483 (when (> len elmo-display-progress-threshold)
485 (setq percent (/ (* i 100) len))
486 (elmo-display-progress
487 'elmo-cache-msgdb-create-as-numlist "Creating msgdb..."
489 (setq numlist (cdr numlist)))
490 (message "Creating msgdb...done")
491 (list overview number-alist mark-alist))))
493 (defalias 'elmo-cache-msgdb-create 'elmo-cache-msgdb-create-as-numlist)
495 (defun elmo-cache-list-folders (spec &optional hierarchy)
496 (let ((folder (concat "'cache" (nth 1 spec))))
497 (elmo-cache-list-folders-subr folder hierarchy)))
499 (defun elmo-cache-list-folders-subr (folder &optional hierarchy)
500 (let ((case-fold-search t)
501 folders curdir dirent relpath abspath attr
507 (nth 1 (elmo-folder-get-spec folder))
508 (expand-file-name elmo-cache-dirname elmo-msgdb-dir)))
509 (if (string-match "^[+=$!]$" folder) ; localdir, archive, localnews
510 (setq subprefix folder)
511 (setq subprefix (concat folder elmo-path-sep)))
513 ;(setq folders (list folder)))
514 (setq dirent (directory-files curdir nil "^[01][0-9A-F]$"))
517 (setq relpath (car dirent))
518 (setq dirent (cdr dirent))
519 (setq abspath (expand-file-name relpath curdir))
521 (eq (nth 0 (setq attr (file-attributes abspath))) t)
522 (setq subfolder (concat subprefix relpath))
523 (setq folders (nconc folders (list subfolder))))))
525 (file-error folders))))
527 (defsubst elmo-cache-list-folder-subr (spec &optional nonsort nonalist)
528 (let* ((dir (elmo-cache-get-folder-directory spec))
529 (flist (mapcar 'file-name-nondirectory
530 (elmo-delete-if 'file-directory-p
532 dir t "^[^@]+@[^@]+$" t))))
533 (folder (concat "'cache/" (nth 1 spec)))
534 (number-alist (or (elmo-msgdb-number-load
535 (elmo-msgdb-expand-path folder))
539 (mapcar '(lambda (filename)
540 (elmo-cache-filename-to-number filename number-alist))
545 (cons (or (elmo-max-of-list nlist) 0) (length nlist))
548 (defsubst elmo-cache-filename-to-number (filename number-alist)
549 (let* ((msgid (elmo-cache-to-msgid filename))
551 (or (car (rassoc msgid number-alist))
553 (setq number (+ (or (caar (last number-alist))
555 (if (car number-alist)
557 (list (cons number msgid)))
558 (setcar number-alist (cons number msgid)))))))
560 (defun elmo-cache-append-msg (spec string message-id &optional msg no-see)
561 (let ((dir (elmo-cache-get-folder-directory spec))
562 (tmp-buffer (get-buffer-create " *ELMO Temp buffer*"))
565 (set-buffer tmp-buffer)
567 (setq filename (expand-file-name (elmo-msgid-to-cache message-id) dir))
569 (if (file-writable-p filename)
572 (as-binary-output-file
573 (write-region (point-min) (point-max) filename nil 'no-msg))
576 (kill-buffer tmp-buffer)))))
578 (defun elmo-cache-delete-msg (spec number locked)
579 (let* ((dir (elmo-cache-get-folder-directory spec))
580 (file (expand-file-name
581 (elmo-cache-number-to-filename spec number) dir)))
582 ;; return nil if failed.
583 (elmo-cache-force-delete file locked)))
585 (defun elmo-cache-read-msg (spec number outbuf &optional set-mark)
587 (let* ((dir (elmo-cache-get-folder-directory spec))
588 (file (expand-file-name
589 (elmo-cache-number-to-filename spec number) dir)))
592 (when (file-exists-p file)
593 (as-binary-input-file (insert-file-contents file))
594 (elmo-delete-cr-get-content-type)))))
596 (defun elmo-cache-delete-msgs (spec msgs)
597 (let ((locked (elmo-dop-lock-list-load)))
599 (mapcar '(lambda (msg) (elmo-cache-delete-msg spec msg locked))
602 (defun elmo-cache-list-folder (spec) ; called by elmo-cache-search()
603 (let ((killed (and elmo-use-killed-list
604 (elmo-msgdb-killed-list-load
605 (elmo-msgdb-expand-path spec))))
607 (setq numbers (elmo-cache-list-folder-subr spec))
608 (elmo-living-messages numbers killed)))
610 (defun elmo-cache-max-of-folder (spec)
611 (elmo-cache-list-folder-subr spec t))
613 (defun elmo-cache-check-validity (spec validity-file)
616 (defun elmo-cache-sync-validity (spec validity-file)
619 (defun elmo-cache-folder-exists-p (spec)
620 (file-directory-p (elmo-cache-get-folder-directory spec)))
622 (defun elmo-cache-folder-creatable-p (spec)
625 (defun elmo-cache-create-folder (spec)
628 (defun elmo-cache-search (spec condition &optional from-msgs)
629 (let* ((number-alist (elmo-cache-list-folder-subr spec nil t))
630 (msgs (or from-msgs (mapcar 'car number-alist)))
632 (i 0) case-fold-search ret-val)
634 (if (elmo-file-field-condition-match
637 (cdr (assq (car msgs) number-alist)))
638 (elmo-cache-get-folder-directory spec))
642 (setq ret-val (cons (car msgs) ret-val)))
643 (when (> num elmo-display-progress-threshold)
645 (elmo-display-progress
646 'elmo-cache-search "Searching..."
648 (setq msgs (cdr msgs)))
651 ;;; (localdir, maildir, localnews) -> cache
652 (defun elmo-cache-copy-msgs (dst-spec msgs src-spec
653 &optional loc-alist same-number)
655 (elmo-cache-get-folder-directory dst-spec))
656 (next-num (1+ (car (elmo-cache-list-folder-subr dst-spec t))))
658 (elmo-msgdb-number-load
659 (elmo-msgdb-expand-path src-spec))))
660 (if same-number (error "Not implemented"))
664 (elmo-call-func src-spec "get-msg-filename" (car msgs) loc-alist)
668 (cdr (assq (if same-number (car msgs) next-num) number-alist)))
670 (if (and (setq msgs (cdr msgs))
672 (setq next-num (1+ next-num))))
675 (defun elmo-cache-use-cache-p (spec number)
678 (defun elmo-cache-local-file-p (spec number)
681 (defun elmo-cache-get-msg-filename (spec number &optional loc-alist)
683 (elmo-cache-number-to-filename spec number)
684 (elmo-cache-get-folder-directory spec)))
686 (defalias 'elmo-cache-sync-number-alist
687 'elmo-generic-sync-number-alist)
688 (defalias 'elmo-cache-list-folder-unread
689 'elmo-generic-list-folder-unread)
690 (defalias 'elmo-cache-list-folder-important
691 'elmo-generic-list-folder-important)
692 (defalias 'elmo-cache-commit 'elmo-generic-commit)
693 (defalias 'elmo-cache-folder-diff 'elmo-generic-folder-diff)
696 (product-provide (provide 'elmo-cache) (require 'elmo-version))
698 ;;; elmo-cache.el ends here