;;; A biff like utility for Gnu Emacs.
;;; Oct 20, 1988 -- Sandy Wells
;;;   Notifies the user about the arrival of new mail by
;;;   popping up a temp buffer displaying it.
;;;   This isn't a general purpose mail reading program,
;;;   only an asynchronous notifier.
;;;   Detects new mail by watching an inbox file, so it will
;;;   work even if the mail is actually being received into
;;;   the inbox by another machine...
;;; bugs -- should only look at a limited extent of the inbox
;;;   so that if there is a huge amount of mail it doesn't thrash...
;;;   after all it isn't a mail reading program.
;;;   Could lose in some race conditions, but it won't damage anything.
;;;   Most of it should probably be written in C .  Then it could
;;;   just wait around for stuff to happen to the inbox, and it could
;;;   do a seek to the new unread part of a (potentially large) inbox.
;;;   It now uses the unix program tail, which reads the front part
;;;   of the file rather than seeking, and Goslings loadst program
;;;   (distributed with GNU) as a timer process.

;;; A pattern describing the inbox biff should watch.
;;; Before biff is activated, This can be reset to look in a different place.

;;; instead we could reset this at the start of the biff function
;;;(defvar *biff-inbox-file-pattern* "/usr/spool/mail/$USER")

(defvar *biff-inbox-file-pattern*
     (progn
       ;; should do this with an elegant looping mapcar, or a switch but ...
       (if (file-exists-p "/com/mail/" )
	   "/com/mail/${USER}"
	 (if (file-exists-p "/var/spool/mail/" )
	     "/var/spool/mail/${USER}"
	   (if (file-exists-p "/var/mail/" )
	       "/var/mail/${USER}"
	     "/usr/spool/mail/${USER}")))))


;;; How often should we wake up and check for new mail? (seconds)

(defvar *biff-polling-interval* 60)


;;; Should we beep the bell when we see new mail?

(defvar *noisy-biff* t)


;;; If the inbox file is huger than this on startup, we only look
;;; at part of it.

(defvar *biff-startup-trunc-length* 65536)

;;; This is how much we look at when inbox is huge on start up.

(defvar *biff-startup-trunc-lines* 30)


;;; The biff timer process:

(defvar *biff-process* nil)


;;; This stores the actual name of the inbox being watched.
;;; Set when biff is started.

(defvar *biff-inbox-file*)




;;; ----
;;; BIFF
;;; ----
;;;
;;;    Start or kill a process to watch for new mail.

(defun biff (arg)
  "With argument 1 (or none when called interactively)
starts up a process to look for new mail.

With other argument (or prefix argument when called interactively)
shuts down the process which is looking for new mail."
  (interactive "p")
  (if (processp *biff-process*) (delete-process *biff-process*))
  ;;@@ should null display-time-hook here
  (if (= arg 1)
      (progn
	(setq *biff-inbox-file*
	      (substitute-in-file-name *biff-inbox-file-pattern*))
	(setq
	 *biff-process*
	 (if (fboundp			;use timers starting with 19.31
	      'display-time-event-handler)
	     (add-hook 'display-time-hook
		       (lambda () (biff-filter "NO-PROCESS" "NO-STRING")))
	 ;; loadst replaced by wakeup in 18.56
	   (if (file-exists-p (concat exec-directory "wakeup"))
	       (start-process "biff" nil (concat exec-directory "wakeup")
			      (format "%d" *biff-polling-interval*))
	 ;; Start a copy of Goslings loadst program running to
	 ;; wake us up periodically.
	     (if (file-exists-p (concat exec-directory "loadst"))
		 (start-process "biff" nil "loadst" "-n"
				(format "%d" *biff-polling-interval*))
	       (progn
		 (message "biff: NO wakeup, loadst, or timers")
		 (sit-for 3)))
	     )))
	;;@@ inelegant to have the time code in two separate places ...
	(if (not (fboundp               ;use timers starting with 19.31
                  'display-time-event-handler))
	    (set-process-filter *biff-process* (symbol-function 'biff-filter)))
	(message "Biff activated..."))
    (message "Biff de-activated...")))


;;; How long was the inbox last time we checked?

(defvar *old-biff-inbox-length* 0)


;;; This is activated when Gosling's loadst process has output.

(defun biff-filter (process string)
  (let ((mfa (file-attributes *biff-inbox-file*)))
    (if mfa
	(let ((current-len (nth 7 mfa)))
	  (if (< current-len *old-biff-inbox-length*)
	      (setq *old-biff-inbox-length* 0))
	  (if (> current-len *old-biff-inbox-length*)
	      (progn
		(biff-announce-mail *old-biff-inbox-length* current-len)
		(setq *old-biff-inbox-length* current-len))))
	(setq *old-biff-inbox-length* 0))))


;;; This happens when we detect that new mail has arrived.
;;; The new mail should start at old-offset into the inbox.
;;; current-len is the current length of the inbox.

(defun biff-announce-mail (old-offset current-len)
  (if *noisy-biff* (beep))
  (let ((curbuffer "*"))
    (with-output-to-temp-buffer "*biff*"
      (buffer-flush-undo standard-output)
      (save-excursion
	(set-buffer standard-output)
	;; if we go away on vacation and come back and inbox is full
	;; don't copy the whole thing!
	(let ((truncate (and (= 0 old-offset)
			     (> current-len *biff-startup-trunc-length*))))
	  (if truncate
	      (call-process "head" *biff-inbox-file* t t
			    (format "-%d" *biff-startup-trunc-lines*))
	    (call-process "tail" *biff-inbox-file* t t
			  (format "+%dc" old-offset)))
	  (goto-char (point-min))
	  ;; Flush the first part of the first header.
	  (if (search-forward "Date:" (point-max) t)
	      (progn (beginning-of-line)
		     (delete-region (point-min) (point))))
	  (goto-char (point-max))
	  (if (not (bolp)) (insert-string "\n"))
	  (goto-char (point-max))
	  (insert-string
	   (concat "---------------------------- "
		   (if truncate
		       " MORE IN INBOX..... "
		     "    End Of Inbox    ")
		   "---------------------------- "))
	  (if (fboundp `rename-uniquely)
	      (rename-uniquely))
	  (setq curbuffer (buffer-name (current-buffer)))
	  (save-window-excursion
	    (view-buffer curbuffer)))))
    (switch-to-buffer-other-window curbuffer)
    (other-window -1)))
;;    (bury-buffer curbuffer)
;;    (switch-to-buffer curbuffer)))

