;;; muse-http.el --- publish HTML files over HTTP ;; Copyright (C) 2004, 2005, 2006, 2007, 2008, 2009, 2010 ;; Free Software Foundation, Inc. ;; This file is part of Emacs Muse. It is not part of GNU Emacs. ;; Emacs Muse 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, or (at your ;; option) any later version. ;; Emacs Muse 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 Emacs Muse; see the file COPYING. If not, write to the ;; Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, ;; Boston, MA 02110-1301, USA. ;;; Commentary: ;;; Contributors: ;;; Code: ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; ;; Publishing HTML over HTTP (using httpd.el) ;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; (require 'muse-html) (require 'muse-project) (require 'httpd) (require 'cgi) (defgroup muse-http nil "Options controlling the behavior of Emacs Muse over HTTP." :group 'press) (defcustom muse-http-maintainer (concat "webmaster@" (system-name)) "The maintainer address to use for the HTTP `From' field." :type 'string :group 'muse-http) (defcustom muse-http-publishing-style "html" "The style to use when publishing projects over http." :type 'string :group 'muse-http) (defcustom muse-http-max-cache-size 64 "The number of pages to cache when serving over HTTP. This only applies if set while running the persisted invocation server. See main documentation for the `muse-http' customization group." :type 'integer :group 'muse-http) (defvar muse-buffer-mtime nil) (make-variable-buffer-local 'muse-buffer-mtime) (defun muse-sort-buffers (l r) (let ((l-mtime (with-current-buffer l muse-buffer-mtime)) (r-mtime (with-current-buffer r muse-buffer-mtime))) (cond ((and (null l-mtime) (null r-mtime)) l) ((null l-mtime) r) ((null r-mtime) l) (t (muse-time-less-p r-mtime l-mtime))))) (defun muse-winnow-list (entries &optional predicate) "Return only those ENTRIES for which PREDICATE returns non-nil." (let ((flist (list t))) (let ((entry entries)) (while entry (if (funcall predicate (car entry)) (nconc flist (list (car entry)))) (setq entry (cdr entry)))) (cdr flist))) (defun muse-http-prune-cache () "If the page cache has become too large, prune it." (let* ((buflist (sort (muse-winnow-list (buffer-list) (function (lambda (buf) (with-current-buffer buf muse-buffer-mtime)))) 'muse-sort-buffers)) (len (length buflist))) (while (> len muse-http-max-cache-size) (kill-buffer (car buflist)) (setq len (1- len))))) (defvar muse-http-serving-p nil) (defun muse-http-send-buffer (&optional modified code msg) "Markup and send the contents of the current buffer via HTTP." (httpd-send (or code 200) (or msg "OK") "Server: muse.el/" muse-version httpd-endl "Connection: close" httpd-endl "MIME-Version: 1.0" httpd-endl "Date: " (format-time-string "%a, %e %b %Y %T %Z") httpd-endl "From: " muse-http-maintainer httpd-endl) (when modified (httpd-send-data "Last-Modified: " (format-time-string "%a, %e %b %Y %T %Z" modified) httpd-endl)) (httpd-send-data "Content-Type: text/html; charset=iso-8859-1" httpd-endl "Content-Length: " (number-to-string (1- (point-max))) httpd-endl httpd-endl (buffer-string)) (httpd-send-eof)) (defun muse-http-reject (title msg &optional annotation) (muse-with-temp-buffer (insert msg ".\n") (if annotation (insert annotation "\n")) (muse-publish-markup-buffer title muse-http-publishing-style) (muse-http-send-buffer nil 404 msg))) (defun muse-http-prepare-url (target explicit) (save-match-data (unless (or (not explicit) (string-match muse-url-regexp target) (string-match muse-image-regexp target) (string-match muse-file-regexp target)) (setq target (concat "page?" target "&project=" muse-http-serving-p)))) (muse-publish-read-only target)) (defun muse-http-render-page (name) "Render the Muse page identified by NAME. When serving from a dedicated Emacs process (see the httpd-serve script), a maximum of `muse-http-max-cache-size' pages will be cached in memory to speed up serving time." (let ((file (muse-project-page-file name muse-http-serving-p)) (muse-publish-url-transforms (cons 'muse-http-prepare-url muse-publish-url-transforms)) (inhibit-read-only t)) (when file (with-current-buffer (get-buffer-create file) (let ((modified-time (nth 5 (file-attributes file))) (muse-publishing-current-file file) muse-publishing-current-style) (when (or (null muse-buffer-mtime) (muse-time-less-p muse-buffer-mtime modified-time)) (erase-buffer) (setq muse-buffer-mtime modified-time)) (goto-char (point-max)) (when (bobp) (muse-insert-file-contents file t) (let ((styles (cddr (muse-project muse-http-serving-p))) style) (while (and styles (null style)) (let ((include-regexp (muse-style-element :include (car styles))) (exclude-regexp (muse-style-element :exclude (car styles)))) (when (and (or (and (null include-regexp) (null exclude-regexp)) (if include-regexp (string-match include-regexp file) (not (string-match exclude-regexp file)))) (not (muse-project-private-p file))) (setq style (car styles)) (while (muse-style-element :base style) (setq style (muse-style (muse-style-element :base style)))) (if (string= (car style) muse-http-publishing-style) (setq style (car styles)) (setq style nil)))) (setq styles (cdr styles))) (muse-publish-markup-buffer name (or style muse-http-publishing-style)))) (set-buffer-modified-p nil) (muse-http-prune-cache) (current-buffer)))))) (defun muse-http-transmit-page (name) "Render the Muse page identified by NAME. When serving from a dedicated Emacs process (see the httpd-serve script), a maximum of `muse-http-max-cache-size' pages will be cached in memory to speed up serving time." (let ((inhibit-read-only t) (buffer (muse-http-render-page name))) (if buffer (with-current-buffer buffer (muse-http-send-buffer muse-buffer-mtime))))) (defvar httpd-vars nil) (defsubst httpd-var (var) "Return value of VAR as a URL variable. If VAR doesn't exist, nil." (cdr (assoc var httpd-vars))) (defsubst httpd-var-p (var) "Return non-nil if VAR was passed as a URL variable." (not (null (assoc var httpd-vars)))) (defun muse-http-serve (page &optional content) "Serve the given PAGE from this press server." ;; index.html is really a reference to the project home page (if (and muse-project-alist (string-match "\\`index.html?\\'" page)) (setq page (concat "page?" (muse-get-keyword :default (cadr (car muse-project-alist)))))) ;; handle the actual request (let ((vc-follow-symlinks t) (muse-publish-report-threshhold nil) muse-http-serving-p httpd-vars) (save-excursion ;; process any CGI variables, if cgi.el is available (if (string-match "\\`\\([^&]+\\)&" page) (setq httpd-vars (cgi-decode (substring page (match-end 0))) page (match-string 1 page))) (unless (setq muse-http-serving-p (httpd-var "project")) (let ((project (car muse-project-alist))) (setq muse-http-serving-p (car project)) (setq httpd-vars (cons (cons "project" (car project)) httpd-vars)))) (if (and muse-http-serving-p (string-match "\\`page\\?\\(.+\\)" page)) (muse-http-transmit-page (match-string 1 page)))))) (if (featurep 'httpd) (httpd-add-handler "\\`\\(index\\.html?\\|page\\(\\?\\|\\'\\)\\)" 'muse-http-serve)) (provide 'muse-http) ;;; muse-http.el ends here