;; notmuch-cache.el --- a simple local filesystem cache of MIME parts
;;
;; Copyright © David Edmondson
;;
;; This file is not part of Notmuch.
;;
;; Notmuch is free software: you can redistribute it and/or modify it
;; under the terms of the GNU General Public License as published by
;; the Free Software Foundation, either version 3 of the License, or
;; (at your option) any later version.
;;
;; Notmuch is distributed in the hope that it will be useful, but
;; WITHOUT ANY WARRANTY; without even the implied warranty of
;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
;; General Public License for more details.
;;
;; You should have received a copy of the GNU General Public License
;; along with Notmuch.  If not, see <http://www.gnu.org/licenses/>.
;;
;; Authors: David Edmondson <dme@dme.org>

;; Things to consider:
;;
;; - Keep an in-memory cache of parts? What would be an appropriate
;;   way to limit the size of that cache?
;;
;; - In a long thread with mostly non-matching messages (i.e. new
;;   message in old thread), the default show output includes the text
;;   of all of the non-matching parts. Given that we can now insert
;;   previously unshown parts on the fly, maybe non-matching body
;;   parts should not be included in the default output, and code
;;   added to glue them in when a non-matching message is
;;   opened. (Would this be simpler if we convert simple text/plain
;;   messages to look like MIME messages with a single text/plain
;;   part?)
;;
;; - There is no cache management functionality. Just delete the
;;   directory and its' contents for now.

;; Stats:

(defvar notmuch-cache-stats [0 0 0]
  "Statistics about the notmuch part cache.

This is an array with three elements:
 - the number of requests to the cache,
 - the number of reads satisified from the cache,
 - the number of writes to the cache.")

(defun notmuch-cache-stat-incr (nth)
  (aset notmuch-cache-stats nth (+ 1 (aref notmuch-cache-stats nth))))

;; The cache:

(defcustom notmuch-cache-directory "~/.notmuch-cache/parts"
  "A directory used to store cached message parts."
  :type 'directory)

(defun notmuch-cache-filename (id part)
  (expand-file-name (format "%s/%s/%d" notmuch-cache-directory id part)))

(defun notmuch-cache-get (id part)
  "Return a cached copy of ID's MIME PART, or nil."

  (notmuch-cache-stat-incr 0) ;; Request.
  
  (let ((filename (notmuch-cache-filename id part)))
    (if (not (file-exists-p filename))
	nil
      (notmuch-cache-stat-incr 1) ;; Read.
      (let ((coding-system-for-read 'no-conversion))
	(with-temp-buffer
	  (insert-file-contents (notmuch-cache-filename id part))
	  (buffer-substring (point-min) (point-max)))))))

(defun notmuch-cache-put (id part content)
  "Cache a copy of ID's MIME PART using CONTENT."

  (let ((filename (notmuch-cache-filename id part)))
    (unless (file-exists-p filename)
      (notmuch-cache-stat-incr 2) ;; Write.
      (make-directory (file-name-directory filename) t)
      (let ((coding-system-for-write 'no-conversion))
	(with-temp-buffer
	  (insert content)
	  (write-region (point-min) (point-max) (notmuch-cache-filename id part)))))))

;; Integration:

(defadvice notmuch-get-bodypart-internal (around notmuch-bodypart-cache activate)
  "Use a local cache of notmuch body parts."

  (let ((content (notmuch-cache-get query part-number)))
    (if content
	(setq ad-return-value content)
      ad-do-it
      (when ad-return-value
	(notmuch-cache-put query part-number ad-return-value)))))
