c8aec160647ba81b88a096a3ccf4446f06328b75
[gnus] / lisp / gnus-html.el
1 ;;; gnus-html.el --- Quoted-Printable functions
2
3 ;; Copyright (C) 2010  Free Software Foundation, Inc.
4
5 ;; Author: Lars Magne Ingebrigtsen <larsi@gnus.org>
6 ;; Keywords: html, web
7
8 ;; This file is part of GNU Emacs.
9
10 ;; GNU Emacs is free software: you can redistribute it and/or modify
11 ;; it under the terms of the GNU General Public License as published by
12 ;; the Free Software Foundation, either version 3 of the License, or
13 ;; (at your option) any later version.
14
15 ;; GNU Emacs is distributed in the hope that it will be useful,
16 ;; but WITHOUT ANY WARRANTY; without even the implied warranty of
17 ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
18 ;; GNU General Public License for more details.
19
20 ;; You should have received a copy of the GNU General Public License
21 ;; along with GNU Emacs.  If not, see <http://www.gnu.org/licenses/>.
22
23 ;;; Commentary:
24
25 ;; The idea is to provide a simple, fast and pretty minimal way to
26 ;; render HTML (including links and images) in a buffer, based on an
27 ;; external HTML renderer (i.e., w3m).
28
29 ;;; Code:
30
31 (defcustom gnus-html-cache-directory (nnheader-concat gnus-directory "html-cache/")
32   "Where Gnus will cache images it downloads from the web."
33   :group 'gnus-art
34   :type 'directory)
35
36 (defcustom gnus-html-cache-size 500000000
37   "The size of the Gnus image cache."
38   :group 'gnus-art
39   :type 'integer)
40
41 (defcustom gnus-html-frame-width 70
42   "What width to use when rendering HTML."
43   :group 'gnus-art
44   :type 'integer)
45
46 ;;;###autoload
47 (defun gnus-article-html (handle)
48   (let ((article-buffer (current-buffer)))
49     (save-restriction
50       (narrow-to-region (point) (point))
51       (save-excursion
52         (mm-with-part handle
53           (let* ((coding-system-for-read 'utf-8)
54                  (coding-system-for-write 'utf-8)
55                  (default-process-coding-system
56                    (cons coding-system-for-read coding-system-for-write)))
57             (call-process-region (point-min) (point-max)
58                                  "w3m" 
59                                  nil article-buffer nil
60                                  "-halfdump"
61                                  "-no-cookie"
62                                  "-O" "UTF-8"
63                                  "-o" "ext_halfdump=1"
64                                  "-t" (format "%s" tab-width)
65                                  "-cols" (format "%s" gnus-html-frame-width)
66                                  "-o" "display_image=off"
67                                  "-T" "text/html"))))
68       (gnus-html-wash-tags))))
69
70 (defun gnus-html-wash-tags ()
71   (let (tag parameters string start end images)
72     (mm-url-decode-entities)
73     (goto-char (point-min))
74     (while (re-search-forward "<\\([^ />]+\\)\\([^>]*\\)>" nil t)
75       (setq tag (match-string 1)
76             parameters (match-string 2)
77             start (match-beginning 0))
78       (when (plusp (length parameters))
79         (set-text-properties 0 (1- (length parameters)) nil parameters))
80       (delete-region start (point))
81       (when (search-forward (concat "</" tag ">") nil t)
82         (delete-region (match-beginning 0) (match-end 0)))
83       (setq end (point))
84       (cond
85        ;; Fetch and insert a picture.
86        ((equal tag "img_alt")
87         (when (string-match "src=\"\\([^\"]+\\)" parameters)
88           (setq parameters (match-string 1 parameters))
89           (when (or (null mm-w3m-safe-url-regexp)
90                     (string-match mm-w3m-safe-url-regexp parameters))
91             (let ((file (gnus-html-image-id parameters)))
92               (if (file-exists-p file)
93                   ;; It's already cached, so just insert it.
94                   (when (gnus-html-put-image file (point))
95                     ;; Delete the ALT text.
96                     (delete-region start end))
97                 ;; We don't have it, so schedule it for fetching
98                 ;; asynchronously.
99                 (push (list parameters
100                             (set-marker (make-marker) start)
101                             (point-marker))
102                       images))))))
103        ;; Add a link.
104        ((equal tag "a")
105         (when (string-match "href=\"\\([^\"]+\\)" parameters)
106           (setq parameters (match-string 1 parameters))
107           (gnus-article-add-button start end
108                                    'browse-url parameters)
109           (let ((overlay (gnus-make-overlay start end)))
110             (gnus-overlay-put overlay 'evaporate t)
111             (gnus-overlay-put overlay 'gnus-button-url parameters)
112             (when gnus-article-mouse-face
113               (gnus-overlay-put overlay 'mouse-face gnus-article-mouse-face)))))
114        ;; Whatever.  Just ignore the tag.
115        (t
116         ))
117       (goto-char start))
118     (goto-char (point-min))
119     ;; The output from -halfdump isn't totally regular, so strip
120     ;; off any </pre_int>s that were left over.
121     (while (re-search-forward "</pre_int>" nil t)
122       (replace-match "" t t))
123     (when images
124       (gnus-html-schedule-image-fetching (current-buffer) (nreverse images)))))
125
126 (defun gnus-html-schedule-image-fetching (buffer images)
127   (let* ((url (caar images))
128          (process (start-process
129                    "images" nil "curl"
130                    "-s" "--create-dirs"
131                    "--location"
132                    "-o" (gnus-html-image-id url)
133                    url)))
134     (set-process-sentinel process 'gnus-html-curl-sentinel)
135     (set-process-plist process (list 'images images
136                                      'buffer buffer))))
137
138 (defun gnus-html-image-id (url)
139   (expand-file-name (sha1 url) gnus-html-cache-directory))
140
141 (defun gnus-html-curl-sentinel (process event)
142   (when (string-match "finished" event)
143     (let* ((images (getf (process-plist process) 'images))
144            (buffer (getf (process-plist process) 'buffer))
145            (spec (pop images))
146            (file (gnus-html-image-id (car spec))))
147       (when (and (buffer-live-p buffer)
148                  ;; If the position of the marker is 1, then that
149                  ;; means that the text is was in has been deleted;
150                  ;; i.e., that the user has selected a different
151                  ;; article before the image arrived.
152                  (not (= (marker-position (cadr spec)) 1)))
153         (save-excursion
154           (set-buffer buffer)
155           (let ((buffer-read-only nil))
156             (when (gnus-html-put-image file (cadr spec))
157               (delete-region (cadr spec) (caddr spec))))))
158       (when images
159         (gnus-html-schedule-image-fetching buffer images)))))
160
161 (defun gnus-html-put-image (file point)
162   (let ((image (ignore-errors
163                  (create-image file))))
164     (if (and image
165              ;; Kludge to avoid displaying 30x30 gif images, which
166              ;; seems to be a signal of a broken image.
167              (not (and (eq (getf (cdr image) :type) 'gif)
168                        (= (car (image-size image t)) 30))))
169         (progn
170           (put-image image point)
171           t)
172       (put-image (find-image '((:type xpm :file "lock-broken.xpm")))
173                  point)
174       nil)))
175
176 (defun gnus-html-prune-cache ()
177   (let ((total-size 0)
178         files)
179     (dolist (file (directory-files gnus-html-cache-directory t nil t))
180       (let ((attributes (file-attributes file)))
181         (unless (nth 0 attributes)
182           (incf total-size (nth 7 attributes))
183           (push (list (time-to-seconds (nth 5 attributes))
184                       (nth 7 attributes) file)
185                 files))))
186     (when (> total-size gnus-html-cache-size)
187       (setq files (sort files (lambda (f1 f2)
188                                 (< (car f1) (car f2)))))
189       (dolist (file files)
190         (when (> total-size gnus-html-cache-size)
191           (decf total-size (cadr file))
192           (delete-file (nth 2 file)))))))
193
194 (defun gnus-html-prefetch-images ()
195   (save-match-data
196     (let (urls)
197       (while (re-search-forward "<img.*src=[\"']\\([^\"']+\\)" nil t)
198         (let ((url (match-string 1)))
199           (when (or (null mm-w3m-safe-url-regexp)
200                     (string-match mm-w3m-safe-url-regexp url))
201             (unless (file-exists-p (gnus-html-image-id url))
202               (push url urls)
203               (push (gnus-html-image-id url) urls)
204               (push "-o" urls)))))
205       (apply 'start-process 
206              "images" nil "curl"
207              "-s" "--create-dirs"
208              "--location"
209              urls))))
210
211 ;;; gnus-html.el ends here