(mime-compare-situation-with-example): Moved to mime-view.el.
[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-acting-situation-example-list nil)
47
48 (defvar mime-acting-situation-example-list-max-size 16)
49
50 (defun mime-save-acting-situation-examples ()
51   (let* ((file mime-acting-situation-examples-file)
52          (buffer (get-buffer-create " *mime-example*")))
53     (unwind-protect
54         (save-excursion
55           (set-buffer buffer)
56           (setq buffer-file-name file)
57           (erase-buffer)
58           (insert ";;; " (file-name-nondirectory file) "\n")
59           (insert "\n;; This file is generated automatically by "
60                   mime-view-version "\n\n")
61           (insert ";;; Code:\n\n")
62           (pp `(setq mime-acting-situation-example-list
63                      ',mime-acting-situation-example-list)
64               (current-buffer))
65           (insert "\n;;; "
66                   (file-name-nondirectory file)
67                   " ends here.\n")
68           (save-buffer))
69       (kill-buffer buffer))))
70
71 (add-hook 'kill-emacs-hook 'mime-save-acting-situation-examples)
72
73 (defun mime-reduce-acting-situation-examples ()
74   (let ((len (length mime-acting-situation-example-list))
75         i ir ic j jr jc ret
76         dest d-i d-j
77         (max-sim 0) sim
78         min-det-ret det-ret
79         min-det-org det-org
80         min-freq freq)
81     (setq i 0
82           ir mime-acting-situation-example-list)
83     (while (< i len)
84       (setq ic (car ir)
85             j 0
86             jr mime-acting-situation-example-list)
87       (while (< j len)
88         (unless (= i j)
89           (setq jc (car jr))
90           (setq ret (mime-compare-situation-with-example (car ic)(car jc))
91                 sim (car ret)
92                 det-ret (+ (length (car ic))(length (car jc)))
93                 det-org (length (cdr ret))
94                 freq (+ (cdr ic)(cdr jc)))
95           (cond ((< max-sim sim)
96                  (setq max-sim sim
97                        min-det-ret det-ret
98                        min-det-org det-org
99                        min-freq freq
100                        d-i i
101                        d-j j
102                        dest (cons (cdr ret) freq))
103                  )
104                 ((= max-sim sim)
105                  (cond ((> min-det-ret det-ret)
106                         (setq min-det-ret det-ret
107                               min-det-org det-org
108                               min-freq freq
109                               d-i i
110                               d-j j
111                               dest (cons (cdr ret) freq))
112                         )
113                        ((= min-det-ret det-ret)
114                         (cond ((> min-det-org det-org)
115                                (setq min-det-org det-org
116                                      min-freq freq
117                                      d-i i
118                                      d-j j
119                                      dest (cons (cdr ret) freq))
120                                )
121                               ((= min-det-org det-org)
122                                (cond ((> min-freq freq)
123                                       (setq min-freq freq
124                                             d-i i
125                                             d-j j
126                                             dest (cons (cdr ret) freq))
127                                       ))
128                                ))
129                         ))
130                  ))
131           )
132         (setq jr (cdr jr)
133               j (1+ j)))
134       (setq ir (cdr ir)
135             i (1+ i)))
136     (if (> d-i d-j)
137         (setq i d-i
138               d-i d-j
139               d-j i))
140     (setq jr (nthcdr (1- d-j) mime-acting-situation-example-list))
141     (setcdr jr (cddr jr))
142     (if (= d-i 0)
143         (setq mime-acting-situation-example-list
144               (cdr mime-acting-situation-example-list))
145       (setq ir (nthcdr (1- d-i) mime-acting-situation-example-list))
146       (setcdr ir (cddr ir))
147       )
148     (if (setq ir (assoc (car dest) mime-acting-situation-example-list))
149         (setcdr ir (+ (cdr ir)(cdr dest)))
150       (setq mime-acting-situation-example-list
151             (cons dest mime-acting-situation-example-list))
152       )))
153
154
155 ;;; @ content decoder
156 ;;;
157
158 ;;;###autoload
159 (defun mime-preview-play-current-entity (&optional ignore-examples mode)
160   "Play current entity.
161 It decodes current entity to call internal or external method.  The
162 method is selected from variable `mime-acting-condition'.
163 If IGNORE-EXAMPLES (C-u prefix) is specified, this function ignores
164 `mime-acting-situation-example-list'.
165 If MODE is specified, play as it.  Default MODE is \"play\"."
166   (interactive "P")
167   (let ((entity (get-text-property (point) 'mime-view-entity)))
168     (if entity
169         (let ((situation
170                (get-text-property (point) 'mime-view-situation)))
171           (or mode
172               (setq mode "play"))
173           (setq situation 
174                 (if (assq 'mode situation)
175                     (put-alist 'mode mode (copy-alist situation))
176                   (cons (cons 'mode mode)
177                         situation)))
178           (if ignore-examples
179               (setq situation
180                     (cons (cons 'ignore-examples ignore-examples)
181                           situation)))
182           (mime-play-entity entity situation)
183           ))))
184
185 ;;;###autoload
186 (defun mime-play-entity (entity &optional situation ignored-method)
187   "Play entity specified by ENTITY.
188 It decodes the entity to call internal or external method.  The method
189 is selected from variable `mime-acting-condition'.  If MODE is
190 specified, play as it.  Default MODE is \"play\"."
191   (let (method ret)
192     (in-calist-package 'mime-view)
193     (setq ret
194           (mime-delq-null-situation
195            (ctree-find-calist mime-acting-condition
196                               (mime-entity-situation entity situation)
197                               mime-view-find-every-acting-situation)
198            'method ignored-method))
199     (or (assq 'ignore-examples situation)
200         (if (cdr ret)
201             (let ((rest ret)
202                   (max-score 0)
203                   (max-escore 0)
204                   max-examples
205                   max-situations)
206               (while rest
207                 (let ((situation (car rest))
208                       (examples mime-acting-situation-example-list))
209                   (while examples
210                     (let* ((ret
211                             (mime-compare-situation-with-example
212                              situation (caar examples)))
213                            (ret-score (car ret)))
214                       (cond ((> ret-score max-score)
215                              (setq max-score ret-score
216                                    max-escore (cdar examples)
217                                    max-examples (list (cdr ret))
218                                    max-situations (list situation))
219                              )
220                             ((= ret-score max-score)
221                              (cond ((> (cdar examples) max-escore)
222                                     (setq max-escore (cdar examples)
223                                           max-examples (list (cdr ret))
224                                           max-situations (list situation))
225                                     )
226                                    ((= (cdar examples) max-escore)
227                                     (setq max-examples
228                                           (cons (cdr ret) max-examples))
229                                     (or (member situation max-situations)
230                                         (setq max-situations
231                                               (cons situation max-situations)))
232                                     )))))
233                     (setq examples (cdr examples))))
234                 (setq rest (cdr rest)))
235               (when max-situations
236                 (setq ret max-situations)
237                 (while max-examples
238                   (let* ((example (car max-examples))
239                          (cell
240                           (assoc example mime-acting-situation-example-list)))
241                     (if cell
242                         (setcdr cell (1+ (cdr cell)))
243                       (setq mime-acting-situation-example-list
244                             (cons (cons example 0)
245                                   mime-acting-situation-example-list))
246                       ))
247                   (setq max-examples (cdr max-examples))
248                   )))))
249     (cond ((cdr ret)
250            (setq ret (select-menu-alist
251                       "Methods"
252                       (mapcar (function
253                                (lambda (situation)
254                                  (cons
255                                   (format "%s"
256                                           (cdr (assq 'method situation)))
257                                   situation)))
258                               ret)))
259            (setq ret (mime-sort-situation ret))
260            (add-to-list 'mime-acting-situation-example-list (cons ret 0))
261            )
262           (t
263            (setq ret (car ret))
264            ))
265     (setq method (cdr (assq 'method ret)))
266     (cond ((and (symbolp method)
267                 (fboundp method))
268            (funcall method entity ret)
269            )
270           ((stringp method)
271            (mime-activate-mailcap-method entity ret)
272            )
273           ;; ((and (listp method)(stringp (car method)))
274           ;;  (mime-activate-external-method entity ret)
275           ;;  )
276           (t
277            (mime-show-echo-buffer "No method are specified for %s\n"
278                                   (mime-type/subtype-string
279                                    (cdr (assq 'type situation))
280                                    (cdr (assq 'subtype situation))))
281            (if (y-or-n-p "Do you want to save current entity to disk?")
282                (mime-save-content entity situation))
283            ))
284     ))
285
286
287 ;;; @ external decoder
288 ;;;
289
290 (defvar mime-mailcap-method-filename-alist nil)
291
292 (defun mime-activate-mailcap-method (entity situation)
293   (let ((method (cdr (assoc 'method situation)))
294         (name (mime-entity-safe-filename entity)))
295     (setq name
296           (if (and name (not (string= name "")))
297               (expand-file-name name temporary-file-directory)
298             (make-temp-name
299              (expand-file-name "EMI" temporary-file-directory))
300             ))
301     (mime-write-entity-content entity name)
302     (message "External method is starting...")
303     (let ((process
304            (let ((command
305                   (mailcap-format-command
306                    method
307                    (cons (cons 'filename name) situation))))
308              (start-process command mime-echo-buffer-name
309                             shell-file-name shell-command-switch command)
310              )))
311       (set-alist 'mime-mailcap-method-filename-alist process name)
312       (set-process-sentinel process 'mime-mailcap-method-sentinel)
313       )
314     ))
315
316 (defun mime-mailcap-method-sentinel (process event)
317   (let ((file (cdr (assq process mime-mailcap-method-filename-alist))))
318     (if (file-exists-p file)
319         (delete-file file)
320       ))
321   (remove-alist 'mime-mailcap-method-filename-alist process)
322   (message (format "%s %s" process event)))
323
324 (defvar mime-echo-window-is-shared-with-bbdb
325   (module-installed-p 'bbdb)
326   "*If non-nil, mime-echo window is shared with BBDB window.")
327
328 (defvar mime-echo-window-height
329   (function
330    (lambda ()
331      (/ (window-height) 5)
332      ))
333   "*Size of mime-echo window.
334 It allows function or integer.  If it is function,
335 `mime-show-echo-buffer' calls it to get height of mime-echo window.
336 Otherwise `mime-show-echo-buffer' uses it as height of mime-echo
337 window.")
338
339 (defun mime-show-echo-buffer (&rest forms)
340   "Show mime-echo buffer to display MIME-playing information."
341   (get-buffer-create mime-echo-buffer-name)
342   (let ((the-win (selected-window))
343         (win (get-buffer-window mime-echo-buffer-name)))
344     (unless win
345       (unless (and mime-echo-window-is-shared-with-bbdb
346                    (condition-case nil
347                        (setq win (get-buffer-window bbdb-buffer-name))
348                      (error nil)))
349         (select-window (get-buffer-window (or mime-preview-buffer
350                                               (current-buffer))))
351         (setq win (split-window-vertically
352                    (- (window-height)
353                       (if (functionp mime-echo-window-height)
354                           (funcall mime-echo-window-height)
355                         mime-echo-window-height)
356                       )))
357         )
358       (set-window-buffer win mime-echo-buffer-name)
359       )
360     (select-window win)
361     (goto-char (point-max))
362     (if forms
363         (let ((buffer-read-only nil))
364           (insert (apply (function format) forms))
365           ))
366     (select-window the-win)
367     ))
368
369
370 ;;; @ file name
371 ;;;
372
373 (defvar mime-view-file-name-char-regexp "[A-Za-z0-9+_-]")
374
375 (defvar mime-view-file-name-regexp-1
376   (concat mime-view-file-name-char-regexp "+\\."
377           mime-view-file-name-char-regexp "+"))
378
379 (defvar mime-view-file-name-regexp-2
380   (concat (regexp-* mime-view-file-name-char-regexp)
381           "\\(\\." mime-view-file-name-char-regexp "+\\)*"))
382
383 (defun mime-entity-safe-filename (entity)
384   (let ((filename
385          (or (mime-entity-filename entity)
386              (let ((subj
387                     (or (mime-entity-read-field entity 'Content-Description)
388                         (mime-entity-read-field entity 'Subject))))
389                (if (and subj
390                         (or (string-match mime-view-file-name-regexp-1 subj)
391                             (string-match mime-view-file-name-regexp-2 subj)))
392                    (substring subj (match-beginning 0)(match-end 0))
393                  )))))
394     (if filename
395         (replace-as-filename filename)
396       )))
397
398
399 ;;; @ file extraction
400 ;;;
401
402 (defun mime-save-content (entity situation)
403   (let ((name (or (mime-entity-safe-filename entity)
404                   (format "%s" (mime-entity-media-type entity))))
405         (dir (if (eq t mime-save-directory)
406                  default-directory
407                mime-save-directory))
408         filename)
409     (setq filename (read-file-name
410                     (concat "File name: (default "
411                             (file-name-nondirectory name) ") ")
412                     dir
413                     (concat (file-name-as-directory dir)
414                             (file-name-nondirectory name))))
415     (if (file-directory-p filename)
416         (setq filename (concat (file-name-as-directory filename)
417                                (file-name-nondirectory name))))
418     (if (file-exists-p filename)
419         (or (yes-or-no-p (format "File %s exists. Save anyway? " filename))
420             (error "")))
421     (mime-write-entity-content entity (expand-file-name filename))
422     ))
423
424
425 ;;; @ file detection
426 ;;;
427
428 (defvar mime-magic-type-alist
429   '(("^\377\330\377[\340\356]..JFIF"    image jpeg)
430     ("^\211PNG"                         image png)
431     ("^GIF8[79]"                        image gif)
432     ("^II\\*\000"                       image tiff)
433     ("^MM\000\\*"                       image tiff)
434     ("^MThd"                            audio midi)
435     ("^\000\000\001\263"                video mpeg)
436     )
437   "*Alist of regexp about magic-number vs. corresponding media-types.
438 Each element looks like (REGEXP TYPE SUBTYPE).
439 REGEXP is a regular expression to match against the beginning of the
440 content of entity.
441 TYPE is symbol to indicate primary type of media-type.
442 SUBTYPE is symbol to indicate subtype of media-type.")
443
444 (defun mime-detect-content (entity situation)
445   (let (type subtype)
446     (let ((mdata (mime-entity-content entity))
447           (rest mime-magic-type-alist))
448       (while (not (let ((cell (car rest)))
449                     (if cell
450                         (if (string-match (car cell) mdata)
451                             (setq type (nth 1 cell)
452                                   subtype (nth 2 cell))
453                           )
454                       t)))
455         (setq rest (cdr rest))))
456     (setq situation (del-alist 'method (copy-alist situation)))
457     (mime-play-entity entity
458                       (if type
459                           (put-alist 'type type
460                                      (put-alist 'subtype subtype
461                                                 situation))
462                         situation)
463                       'mime-detect-content)))
464
465
466 ;;; @ mail/news message
467 ;;;
468
469 (defun mime-preview-quitting-method-for-mime-show-message-mode ()
470   "Quitting method for mime-view.
471 It is registered to variable `mime-preview-quitting-method-alist'."
472   (let ((raw-buffer (mime-entity-buffer
473                      (get-text-property (point-min) 'mime-view-entity)))
474         (mother mime-mother-buffer)
475         (win-conf mime-preview-original-window-configuration))
476     (kill-buffer raw-buffer)
477     (mime-preview-kill-buffer)
478     (set-window-configuration win-conf)
479     (pop-to-buffer mother)
480     ))
481
482 (defun mime-view-message/rfc822 (entity situation)
483   (let* ((new-name
484           (format "%s-%s" (buffer-name) (mime-entity-number entity)))
485          (mother (current-buffer))
486          (children (car (mime-entity-children entity)))
487          (preview-buffer
488           (mime-display-message
489            children new-name mother nil
490            (cdr (assq 'major-mode
491                       (get-text-property (point) 'mime-view-situation))))))
492     (or (get-buffer-window preview-buffer)
493         (let ((m-win (get-buffer-window mother)))
494           (if m-win
495               (set-window-buffer m-win preview-buffer)
496             (switch-to-buffer preview-buffer)
497             )))))
498
499
500 ;;; @ message/partial
501 ;;;
502
503 (defun mime-store-message/partial-piece (entity cal)
504   (let* ((root-dir
505           (expand-file-name
506            (concat "m-prts-" (user-login-name)) temporary-file-directory))
507          (id (cdr (assoc "id" cal)))
508          (number (cdr (assoc "number" cal)))
509          (total (cdr (assoc "total" cal)))
510          file
511          (mother (current-buffer)))
512     (or (file-exists-p root-dir)
513         (make-directory root-dir))
514     (setq id (replace-as-filename id))
515     (setq root-dir (concat root-dir "/" id))
516     (or (file-exists-p root-dir)
517         (make-directory root-dir))
518     (setq file (concat root-dir "/FULL"))
519     (if (file-exists-p file)
520         (let ((full-buf (get-buffer-create "FULL"))
521               (pwin (or (get-buffer-window mother)
522                         (get-largest-window)))
523               pbuf)
524           (save-window-excursion
525             (set-buffer full-buf)
526             (erase-buffer)
527             (as-binary-input-file (insert-file-contents file))
528             (setq major-mode 'mime-show-message-mode)
529             (mime-view-buffer (current-buffer) nil mother)
530             (setq pbuf (current-buffer))
531             )
532           (set-window-buffer pwin pbuf)
533           (select-window pwin)
534           )
535       (setq file (concat root-dir "/" number))
536       (mime-write-entity-body entity file)
537       (let ((total-file (concat root-dir "/CT")))
538         (setq total
539               (if total
540                   (progn
541                     (or (file-exists-p total-file)
542                         (save-excursion
543                           (set-buffer
544                            (get-buffer-create mime-temp-buffer-name))
545                           (erase-buffer)
546                           (insert total)
547                           (write-region (point-min)(point-max) total-file)
548                           (kill-buffer (current-buffer))
549                           ))
550                     (string-to-number total)
551                     )
552                 (and (file-exists-p total-file)
553                      (save-excursion
554                        (set-buffer (find-file-noselect total-file))
555                        (prog1
556                            (and (re-search-forward "[0-9]+" nil t)
557                                 (string-to-number
558                                  (buffer-substring (match-beginning 0)
559                                                    (match-end 0)))
560                                 )
561                          (kill-buffer (current-buffer))
562                          )))
563                 )))
564       (if (and total (> total 0)
565                (>= (length (directory-files root-dir nil "^[0-9]+$" t))
566                    total))
567           (catch 'tag
568             (save-excursion
569               (set-buffer (get-buffer-create mime-temp-buffer-name))
570               (let ((full-buf (current-buffer)))
571                 (erase-buffer)
572                 (let ((i 1))
573                   (while (<= i total)
574                     (setq file (concat root-dir "/" (int-to-string i)))
575                     (or (file-exists-p file)
576                         (throw 'tag nil)
577                         )
578                     (as-binary-input-file (insert-file-contents file))
579                     (goto-char (point-max))
580                     (setq i (1+ i))
581                     ))
582                 (as-binary-output-file
583                  (write-region (point-min)(point-max)
584                                (expand-file-name "FULL" root-dir)))
585                 (let ((i 1))
586                   (while (<= i total)
587                     (let ((file (format "%s/%d" root-dir i)))
588                       (and (file-exists-p file)
589                            (delete-file file)
590                            ))
591                     (setq i (1+ i))
592                     ))
593                 (let ((file (expand-file-name "CT" root-dir)))
594                   (and (file-exists-p file)
595                        (delete-file file)
596                        ))
597                 (let ((pwin (or (get-buffer-window mother)
598                                 (get-largest-window)))
599                       (pbuf (mime-display-message
600                              (mime-open-entity 'buffer (current-buffer))
601                              nil mother nil 'mime-show-message-mode)))
602                   (set-window-buffer pwin pbuf)
603                   (select-window pwin)
604                   )))))
605       )))
606
607
608 ;;; @ message/external-body
609 ;;;
610
611 (defvar mime-raw-dired-function
612   (if (and (>= emacs-major-version 19) window-system)
613       (function dired-other-frame)
614     (function mime-raw-dired-function-for-one-frame)
615     ))
616
617 (defun mime-raw-dired-function-for-one-frame (dir)
618   (let ((win (or (get-buffer-window mime-preview-buffer)
619                  (get-largest-window))))
620     (select-window win)
621     (dired dir)
622     ))
623
624 (defun mime-view-message/external-anon-ftp (entity cal)
625   (let* ((site (cdr (assoc "site" cal)))
626          (directory (cdr (assoc "directory" cal)))
627          (name (cdr (assoc "name" cal)))
628          (pathname (concat "/anonymous@" site ":" directory)))
629     (message (concat "Accessing " (expand-file-name name pathname) " ..."))
630     (funcall mime-raw-dired-function pathname)
631     (goto-char (point-min))
632     (search-forward name)
633     ))
634
635 (defvar mime-raw-browse-url-function mime-browse-url-function)
636
637 (defun mime-view-message/external-url (entity cal)
638   (let ((url (cdr (assoc "url" cal))))
639     (message (concat "Accessing " url " ..."))
640     (funcall mime-raw-browse-url-function url)))
641
642
643 ;;; @ rot13-47
644 ;;;
645
646 (defun mime-view-caesar (entity situation)
647   "Internal method for mime-view to display ROT13-47-48 message."
648   (let ((buf (get-buffer-create
649               (format "%s-%s" (buffer-name) (mime-entity-number entity)))))
650     (with-current-buffer buf
651       (setq buffer-read-only nil)
652       (erase-buffer)
653       (mime-insert-text-content entity)
654       (mule-caesar-region (point-min) (point-max))
655       (set-buffer-modified-p nil)
656       )
657     (let ((win (get-buffer-window (current-buffer))))
658       (or (eq (selected-window) win)
659           (select-window (or win (get-largest-window)))
660           ))
661     (view-buffer buf)
662     (goto-char (point-min))
663     ))
664
665
666 ;;; @ end
667 ;;;
668
669 (provide 'mime-play)
670
671 (let* ((file mime-acting-situation-examples-file)
672        (buffer (get-buffer-create " *mime-example*")))
673   (if (file-readable-p file)
674       (unwind-protect
675           (save-excursion
676             (set-buffer buffer)
677             (erase-buffer)
678             (insert-file-contents file)
679             (eval-buffer)
680             ;; format check
681             (condition-case nil
682                 (let ((i 0))
683                   (while (and (> (length mime-acting-situation-example-list)
684                                  mime-acting-situation-example-list-max-size)
685                               (< i 16))
686                     (mime-reduce-acting-situation-examples)
687                     (setq i (1+ i))
688                     ))
689               (error (setq mime-acting-situation-example-list nil)))
690             )
691         (kill-buffer buffer))))
692
693 ;;; mime-play.el ends here