(mime-store-message/partial-piece): Use `binary-insert-file-contents'
[elisp/semi.git] / mime-play.el
1 ;;; mime-play.el --- Playback processing module for mime-view.el
2
3 ;; Copyright (C) 1994,95,96,97,98,99,2000 Free Software Foundation, Inc.
4
5 ;; Author: MORIOKA Tomohiko <tomo@m17n.org>
6 ;; Created: 1995/9/26 (separated from tm-view.el)
7 ;;      Renamed: 1997/2/21 from tm-play.el
8 ;; Keywords: MIME, multimedia, mail, news
9
10 ;; This file is part of SEMI (Secretariat of Emacs MIME Interfaces).
11
12 ;; This program is free software; you can redistribute it and/or
13 ;; modify it under the terms of the GNU General Public License as
14 ;; published by the Free Software Foundation; either version 2, or (at
15 ;; your option) any later version.
16
17 ;; This program is distributed in the hope that it will be useful, but
18 ;; WITHOUT ANY WARRANTY; without even the implied warranty of
19 ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
20 ;; General Public License for more details.
21
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.
26
27 ;;; Code:
28
29 (require 'mime-view)
30 (require 'alist)
31 (require 'filename)
32
33 (eval-when-compile
34   (condition-case nil
35       (require 'bbdb)
36     (error (defvar bbdb-buffer-name nil)))
37   )
38
39 (defcustom mime-save-directory "~/"
40   "*Name of the directory where MIME entity will be saved in.
41 If t, it means current directory."
42   :group 'mime-view
43   :type '(choice (const :tag "Current directory" t)
44                  (directory)))
45
46 (defvar mime-play-find-every-situations t
47   "*Find every available situations if non-nil.")
48
49
50 ;;; @ content decoder
51 ;;;
52
53 ;;;###autoload
54 (defun mime-preview-play-current-entity (&optional ignore-examples mode)
55   "Play current entity.
56 It decodes current entity to call internal or external method.  The
57 method is selected from variable `mime-acting-condition'.
58 If IGNORE-EXAMPLES (C-u prefix) is specified, this function ignores
59 `mime-acting-situation-example-list'.
60 If MODE is specified, play as it.  Default MODE is \"play\"."
61   (interactive "P")
62   (let ((entity (get-text-property (point) 'mime-view-entity)))
63     (if entity
64         (let ((situation
65                (get-text-property (point) 'mime-view-situation)))
66           (or mode
67               (setq mode "play"))
68           (setq situation 
69                 (if (assq 'mode situation)
70                     (put-alist 'mode mode (copy-alist situation))
71                   (cons (cons 'mode mode)
72                         situation)))
73           (if ignore-examples
74               (setq situation
75                     (cons (cons 'ignore-examples ignore-examples)
76                           situation)))
77           (mime-play-entity entity situation)
78           ))))
79
80 ;;;###autoload
81 (defun mime-play-entity (entity &optional situation ignored-method)
82   "Play entity specified by ENTITY.
83 It decodes the entity to call internal or external method.  The method
84 is selected from variable `mime-acting-condition'.  If MODE is
85 specified, play as it.  Default MODE is \"play\"."
86   (let ((ret
87          (mime-unify-situations (mime-entity-situation entity situation)
88                                 mime-acting-condition
89                                 mime-acting-situation-example-list
90                                 'method ignored-method
91                                 mime-play-find-every-situations))
92         method)
93     (setq mime-acting-situation-example-list (cdr ret)
94           ret (car ret))
95     (cond ((cdr ret)
96            (setq ret (select-menu-alist
97                       "Methods"
98                       (mapcar (function
99                                (lambda (situation)
100                                  (cons
101                                   (format "%s"
102                                           (cdr (assq 'method situation)))
103                                   situation)))
104                               ret)))
105            (setq ret (mime-sort-situation ret))
106            (add-to-list 'mime-acting-situation-example-list (cons ret 0))
107            )
108           (t
109            (setq ret (car ret))
110            ))
111     (setq method (cdr (assq 'method ret)))
112     (cond ((and (symbolp method)
113                 (fboundp method))
114            (funcall method entity ret)
115            )
116           ((stringp method)
117            (mime-activate-mailcap-method entity ret)
118            )
119           ;; ((and (listp method)(stringp (car method)))
120           ;;  (mime-activate-external-method entity ret)
121           ;;  )
122           (t
123            (mime-show-echo-buffer "No method are specified for %s\n"
124                                   (mime-type/subtype-string
125                                    (cdr (assq 'type situation))
126                                    (cdr (assq 'subtype situation))))
127            (if (y-or-n-p "Do you want to save current entity to disk?")
128                (mime-save-content entity situation))
129            ))
130     ))
131
132
133 ;;; @ external decoder
134 ;;;
135
136 (defvar mime-mailcap-method-filename-alist nil)
137
138 (defun mime-activate-mailcap-method (entity situation)
139   (let ((method (cdr (assoc 'method situation)))
140         (name (mime-entity-safe-filename entity)))
141     (setq name
142           (if (and name (not (string= name "")))
143               (expand-file-name name temporary-file-directory)
144             (make-temp-name
145              (expand-file-name "EMI" temporary-file-directory))
146             ))
147     (mime-write-entity-content entity name)
148     (message "External method is starting...")
149     (let ((process
150            (let ((command
151                   (mime-format-mailcap-command
152                    method
153                    (cons (cons 'filename name) situation))))
154              (start-process command mime-echo-buffer-name
155                             shell-file-name shell-command-switch command)
156              )))
157       (set-alist 'mime-mailcap-method-filename-alist process name)
158       (set-process-sentinel process 'mime-mailcap-method-sentinel)
159       )
160     ))
161
162 (defun mime-mailcap-method-sentinel (process event)
163   (let ((file (cdr (assq process mime-mailcap-method-filename-alist))))
164     (if (file-exists-p file)
165         (delete-file file)
166       ))
167   (remove-alist 'mime-mailcap-method-filename-alist process)
168   (message (format "%s %s" process event)))
169
170 (defvar mime-echo-window-is-shared-with-bbdb
171   (module-installed-p 'bbdb)
172   "*If non-nil, mime-echo window is shared with BBDB window.")
173
174 (defvar mime-echo-window-height
175   (function
176    (lambda ()
177      (/ (window-height) 5)
178      ))
179   "*Size of mime-echo window.
180 It allows function or integer.  If it is function,
181 `mime-show-echo-buffer' calls it to get height of mime-echo window.
182 Otherwise `mime-show-echo-buffer' uses it as height of mime-echo
183 window.")
184
185 (defun mime-show-echo-buffer (&rest forms)
186   "Show mime-echo buffer to display MIME-playing information."
187   (get-buffer-create mime-echo-buffer-name)
188   (let ((the-win (selected-window))
189         (win (get-buffer-window mime-echo-buffer-name)))
190     (unless win
191       (unless (and mime-echo-window-is-shared-with-bbdb
192                    (condition-case nil
193                        (setq win (get-buffer-window bbdb-buffer-name))
194                      (error nil)))
195         (select-window (get-buffer-window (or mime-preview-buffer
196                                               (current-buffer))))
197         (setq win (split-window-vertically
198                    (- (window-height)
199                       (if (functionp mime-echo-window-height)
200                           (funcall mime-echo-window-height)
201                         mime-echo-window-height)
202                       )))
203         )
204       (set-window-buffer win mime-echo-buffer-name)
205       )
206     (select-window win)
207     (goto-char (point-max))
208     (if forms
209         (let ((buffer-read-only nil))
210           (insert (apply (function format) forms))
211           ))
212     (select-window the-win)
213     ))
214
215
216 ;;; @ file name
217 ;;;
218
219 (defvar mime-view-file-name-char-regexp "[A-Za-z0-9+_-]")
220
221 (defvar mime-view-file-name-regexp-1
222   (concat mime-view-file-name-char-regexp "+\\."
223           mime-view-file-name-char-regexp "+"))
224
225 (defvar mime-view-file-name-regexp-2
226   (concat (regexp-* mime-view-file-name-char-regexp)
227           "\\(\\." mime-view-file-name-char-regexp "+\\)*"))
228
229 (defun mime-entity-safe-filename (entity)
230   (let ((filename
231          (or (mime-entity-filename entity)
232              (let ((subj
233                     (or (mime-entity-read-field entity 'Content-Description)
234                         (mime-entity-read-field entity 'Subject))))
235                (if (and subj
236                         (or (string-match mime-view-file-name-regexp-1 subj)
237                             (string-match mime-view-file-name-regexp-2 subj)))
238                    (substring subj (match-beginning 0)(match-end 0))
239                  )))))
240     (if filename
241         (replace-as-filename filename)
242       )))
243
244
245 ;;; @ file extraction
246 ;;;
247
248 (defun mime-save-content (entity situation)
249   (let ((name (or (mime-entity-safe-filename entity)
250                   (format "%s" (mime-entity-media-type entity))))
251         (dir (if (eq t mime-save-directory)
252                  default-directory
253                mime-save-directory))
254         filename)
255     (setq filename (read-file-name
256                     (concat "File name: (default "
257                             (file-name-nondirectory name) ") ")
258                     dir
259                     (concat (file-name-as-directory dir)
260                             (file-name-nondirectory name))))
261     (if (file-directory-p filename)
262         (setq filename (concat (file-name-as-directory filename)
263                                (file-name-nondirectory name))))
264     (if (file-exists-p filename)
265         (or (yes-or-no-p (format "File %s exists. Save anyway? " filename))
266             (error "")))
267     (mime-write-entity-content entity (expand-file-name filename))
268     ))
269
270
271 ;;; @ file detection
272 ;;;
273
274 (defvar mime-magic-type-alist
275   '(("^\377\330\377[\340\356]..JFIF"    image jpeg)
276     ("^\211PNG"                         image png)
277     ("^GIF8[79]"                        image gif)
278     ("^II\\*\000"                       image tiff)
279     ("^MM\000\\*"                       image tiff)
280     ("^MThd"                            audio midi)
281     ("^\000\000\001\263"                video mpeg)
282     )
283   "*Alist of regexp about magic-number vs. corresponding media-types.
284 Each element looks like (REGEXP TYPE SUBTYPE).
285 REGEXP is a regular expression to match against the beginning of the
286 content of entity.
287 TYPE is symbol to indicate primary type of media-type.
288 SUBTYPE is symbol to indicate subtype of media-type.")
289
290 (defun mime-detect-content (entity situation)
291   (let (type subtype)
292     (let ((mdata (mime-entity-content entity))
293           (rest mime-magic-type-alist))
294       (while (not (let ((cell (car rest)))
295                     (if cell
296                         (if (string-match (car cell) mdata)
297                             (setq type (nth 1 cell)
298                                   subtype (nth 2 cell))
299                           )
300                       t)))
301         (setq rest (cdr rest))))
302     (setq situation (del-alist 'method (copy-alist situation)))
303     (mime-play-entity entity
304                       (if type
305                           (put-alist 'type type
306                                      (put-alist 'subtype subtype
307                                                 situation))
308                         situation)
309                       'mime-detect-content)))
310
311
312 ;;; @ mail/news message
313 ;;;
314
315 (defun mime-preview-quitting-method-for-mime-show-message-mode ()
316   "Quitting method for mime-view.
317 It is registered to variable `mime-preview-quitting-method-alist'."
318   (let ((mother mime-mother-buffer)
319         (win-conf mime-preview-original-window-configuration))
320     (if (and (boundp 'mime-view-temp-message-buffer)
321              (buffer-live-p mime-view-temp-message-buffer))
322         (kill-buffer mime-view-temp-message-buffer))
323     (mime-preview-kill-buffer)
324     (set-window-configuration win-conf)
325     (pop-to-buffer mother)))
326
327 (defun mime-view-message/rfc822 (entity situation)
328   (let* ((new-name
329           (format "%s-%s" (buffer-name) (mime-entity-number entity)))
330          (mother (current-buffer))
331          (children (car (mime-entity-children entity)))
332          (preview-buffer
333           (mime-display-message
334            children new-name mother nil
335            (cdr (assq 'major-mode
336                       (get-text-property (point) 'mime-view-situation))))))
337     (or (get-buffer-window preview-buffer)
338         (let ((m-win (get-buffer-window mother)))
339           (if m-win
340               (set-window-buffer m-win preview-buffer)
341             (switch-to-buffer preview-buffer)
342             )))))
343
344
345 ;;; @ message/partial
346 ;;;
347
348 (defun mime-store-message/partial-piece (entity cal)
349   (let* ((root-dir
350           (expand-file-name
351            (concat "m-prts-" (user-login-name)) temporary-file-directory))
352          (id (cdr (assoc "id" cal)))
353          (number (cdr (assoc "number" cal)))
354          (total (cdr (assoc "total" cal)))
355          file
356          (mother (current-buffer)))
357     (or (file-exists-p root-dir)
358         (make-directory root-dir))
359     (setq id (replace-as-filename id))
360     (setq root-dir (concat root-dir "/" id))
361     (or (file-exists-p root-dir)
362         (make-directory root-dir))
363     (setq file (concat root-dir "/FULL"))
364     (if (file-exists-p file)
365         (let ((full-buf (get-buffer-create "FULL"))
366               (pwin (or (get-buffer-window mother)
367                         (get-largest-window)))
368               pbuf)
369           (save-window-excursion
370             (set-buffer full-buf)
371             (erase-buffer)
372             (binary-insert-file-contents file)
373             (setq major-mode 'mime-show-message-mode)
374             (mime-view-buffer (current-buffer) nil mother)
375             (setq pbuf (current-buffer))
376             (make-local-variable 'mime-view-temp-message-buffer)
377             (setq mime-view-temp-message-buffer full-buf))
378           (set-window-buffer pwin pbuf)
379           (select-window pwin))
380       (setq file (concat root-dir "/" number))
381       (mime-write-entity-body entity file)
382       (let ((total-file (concat root-dir "/CT")))
383         (setq total
384               (if total
385                   (progn
386                     (or (file-exists-p total-file)
387                         (save-excursion
388                           (set-buffer
389                            (get-buffer-create mime-temp-buffer-name))
390                           (erase-buffer)
391                           (insert total)
392                           (write-region (point-min)(point-max) total-file)
393                           (kill-buffer (current-buffer))
394                           ))
395                     (string-to-number total)
396                     )
397                 (and (file-exists-p total-file)
398                      (save-excursion
399                        (set-buffer (find-file-noselect total-file))
400                        (prog1
401                            (and (re-search-forward "[0-9]+" nil t)
402                                 (string-to-number
403                                  (buffer-substring (match-beginning 0)
404                                                    (match-end 0)))
405                                 )
406                          (kill-buffer (current-buffer))
407                          )))
408                 )))
409       (if (and total (> total 0)
410                (>= (length (directory-files root-dir nil "^[0-9]+$" t))
411                    total))
412           (catch 'tag
413             (save-excursion
414               (set-buffer (get-buffer-create mime-temp-buffer-name))
415               (let ((full-buf (current-buffer)))
416                 (erase-buffer)
417                 (let ((i 1))
418                   (while (<= i total)
419                     (setq file (concat root-dir "/" (int-to-string i)))
420                     (or (file-exists-p file)
421                         (throw 'tag nil)
422                         )
423                     (binary-insert-file-contents file)
424                     (goto-char (point-max))
425                     (setq i (1+ i))
426                     ))
427                 (binary-write-region (point-min)(point-max)
428                                      (expand-file-name "FULL" root-dir))
429                 (let ((i 1))
430                   (while (<= i total)
431                     (let ((file (format "%s/%d" root-dir i)))
432                       (and (file-exists-p file)
433                            (delete-file file)
434                            ))
435                     (setq i (1+ i))
436                     ))
437                 (let ((file (expand-file-name "CT" root-dir)))
438                   (and (file-exists-p file)
439                        (delete-file file)
440                        ))
441                 (let ((buf (current-buffer))
442                       (pwin (or (get-buffer-window mother)
443                                 (get-largest-window)))
444                       (pbuf (mime-display-message
445                              (mime-open-entity 'buffer (current-buffer))
446                              nil mother nil 'mime-show-message-mode)))
447                   (with-current-buffer pbuf
448                     (make-local-variable 'mime-view-temp-message-buffer)
449                     (setq mime-view-temp-message-buffer buf))
450                   (set-window-buffer pwin pbuf)
451                   (select-window pwin)
452                   )))))
453       )))
454
455
456 ;;; @ message/external-body
457 ;;;
458
459 (defvar mime-raw-dired-function
460   (if (and (>= emacs-major-version 19) window-system)
461       (function dired-other-frame)
462     (function mime-raw-dired-function-for-one-frame)
463     ))
464
465 (defun mime-raw-dired-function-for-one-frame (dir)
466   (let ((win (or (get-buffer-window mime-preview-buffer)
467                  (get-largest-window))))
468     (select-window win)
469     (dired dir)
470     ))
471
472 (defun mime-view-message/external-anon-ftp (entity cal)
473   (let* ((site (cdr (assoc "site" cal)))
474          (directory (cdr (assoc "directory" cal)))
475          (name (cdr (assoc "name" cal)))
476          (pathname (concat "/anonymous@" site ":" directory)))
477     (message (concat "Accessing " (expand-file-name name pathname) " ..."))
478     (funcall mime-raw-dired-function pathname)
479     (goto-char (point-min))
480     (search-forward name)
481     ))
482
483 (defvar mime-raw-browse-url-function mime-browse-url-function)
484
485 (defun mime-view-message/external-url (entity cal)
486   (let ((url (cdr (assoc "url" cal))))
487     (message (concat "Accessing " url " ..."))
488     (funcall mime-raw-browse-url-function url)))
489
490
491 ;;; @ rot13-47
492 ;;;
493
494 (defun mime-view-caesar (entity situation)
495   "Internal method for mime-view to display ROT13-47-48 message."
496   (let ((buf (get-buffer-create
497               (format "%s-%s" (buffer-name) (mime-entity-number entity)))))
498     (with-current-buffer buf
499       (setq buffer-read-only nil)
500       (erase-buffer)
501       (mime-insert-text-content entity)
502       (mule-caesar-region (point-min) (point-max))
503       (set-buffer-modified-p nil)
504       )
505     (let ((win (get-buffer-window (current-buffer))))
506       (or (eq (selected-window) win)
507           (select-window (or win (get-largest-window)))
508           ))
509     (view-buffer buf)
510     (goto-char (point-min))
511     ))
512
513
514 ;;; @ end
515 ;;;
516
517 (provide 'mime-play)
518
519 ;;; mime-play.el ends here