让eww强制下载非纯文本文件

类似于这个个问题的反面。

上有的网站时,遇到要下载的非纯文本文件,eww默认的行为是显示(需要mailcap)。

但我不希望它显示,而是希望它下载。

通过上面的问题,我想可能的方式是把这些文件设置为application/force-download


P.S. eww-download的默认行为。。。不知道下载下来的网页文件名到底是怎么定的。。。

MIME Type

PDFs are viewed inline, by default, with doc-view-mode , but this can be customized by using the mailcap (see mailcap) mechanism, in particular mailcap-mime-data .

macilcap 是 Emacs 自帯的 MIME lib 的一部分,你是不是有什麼误解,以为是要另装东西。

Emacs 的 MIME 会 parse ~/.mailcap出一个 mailcap-mime-data alist,不过你是要 eww 下载,要在 Emacs 里用 mailcap-user-mime-data override。

为了给你节约時間 figure out 要怎么写,

(setq mailcap-user-mime-data
      '(((viewer . mailcap-save-binary-file)
         (type . "application/pdf")) ;; obviously, this is pdf
        ((viewer . mailcap-save-binary-file)
         (type . "image/.+")))) ;; This line is a regex matching "image/..."

思路是这样,不过直接这么来不行。


我最后貌似设置成功的方式长这样:

(dolist (lst mailcap-mime-data)                                                 
  (when (and (stringp (car lst)) (string-equal "application" (car lst)))        
    (setcdr lst (mapcan (lambda (elem)                                          
                          (unless (string-equal (with-output-to-string          
                                                  (princ (car elem)))           
                                                "pdf")                          
                            `(,elem)))                      
                        (cdr lst)))                                             
    (setcdr lst (append (cdr lst) `(("pdf" (viewer . mailcap-save-binary-file)    
                                     (type . "application/pdf")))))))

不过还差文件名。。。

你搞错了,mailcap-mime-data 不能直接设置,和mailcap-user-mime-data 格式也不一样,后者是会根据正则对前者修改的。实际上你做的相当于重新发明 mailcap-user-mike-data 的功能。


(mailcap-mime-info "application/pdf")
;; doc-view-mode
(setq mailcap-user-mime-data
      '(((viewer . mailcap-save-binary-file)
         (type . "application/.+"))))
(mailcap-mime-info "application/pdf")
;; mailcap-save-binary-file
(defun eww-download-callback (status url)
  (unless (plist-get status :error)
    (let* ((obj (url-generic-parse-url url))
           (path (car (url-path-and-query obj)))
           (file (eww-make-unique-file-name
                  (eww-decode-url-file-name (file-name-nondirectory path))
                  eww-download-directory)))
      (goto-char (point-min))
      (re-search-forward "\r?\n\r?\n")
      (let ((coding-system-for-write 'no-conversion))
        (write-region (point) (point-max) file))
      (message "Saved %s" file))))
(setq obj (url-generic-parse-url "https://faculty.washington.edu/smcohen/120/SecondOrder.pdf"))
;; #s(url "https" nil nil "faculty.washington.edu" nil "/smcohen/120/SecondOrder.pdf" nil nil t nil t t)
(setq path (car (url-path-and-query obj)))
;; "/smcohen/120/SecondOrder.pdf"
(setq path (file-name-nondirectory path))
;; "SecondOrder.pdf"

我这里这么改mailcap-user-mime-data的话是nil,改之前也是nil。。。

改之前是nil原因应该是没有图形界面也没有pdf2text之类的。

那可能是我用的 mac 系统环境自带了 MIME 的关系。

我又试了一下,放配置文件里是没问题的,但单独求值就不行。。。

这个是直链的情况吧,直接按d就这么下载了。但是不是直链的话,调用mailcap-save-binary-file会问你文件名。

因为不是直链就是直接传文件数据过来,然后就交由 mime 处理了,不是经过 eww-download 做的。

我说还差文件名就是说这种情况。

这种我觉得不大面积改写就没办法,比如给 mailcap save binary file 加个参数接受文件名,然后自己实现命名。因为这个函数只接受数据没有文件名

有办法在这种情况下获得文件名吗?能的话写个my/eww-save-binary-file之类的应该就可以了。

我现在倒是很好奇w3m是怎么获得文件名的了。首先headers里没有。于是我看w3m的代码。w3m获取文件名和获取其它信息是写一块的,不好看懂。

于是我就去查非直链是怎么提供文件名的,结果查到了download属性

然而ACM的下载链接没有指定这个属性。。。

同时这个未解决问题提到了cfm文件。ACM也用的是这个。

w3m wget 下载链接文件的命名都有问题,我建议看 aria2 的实现,这个比较靠谱

我看了一下,获取文件名可能不外乎就是headers里有、URL里有,或者就是我之前提到的:

不过这个会不会表现在headers里面我就不知道了,我不是搞HTTP的。

至于为什么eww的不到ACM下载链接文件名,原因在于它没有考虑有一次重定向。ACM的文件名是在重定向的URL里的。。。

获取这个重定向的URL的方法,我采用的是给eww-renderadvice。具体的做法,看eww-render的代码就知道了。

(require 'eww)
(defun my/eww-save-binary-file () ".")
(lexical-let* ((filename nil)
               (origin eww-download-directory)
               (pwd (lambda ()
                      (substring (shell-command-to-string "pwd") 0 -1)))
               (dir-or-pwd (lambda ()
                             (if (file-exists-p origin) origin (funcall pwd)))))
  (let ((aux (lambda (args)
               (let ((status (car args)) (url (cadr args)) (rest (cddr args)))
                 (let* ((cd (assoc "content-disposition" (eww-parse-headers)))
                        (cd (when cd (cdr cd))))
                   (setq filename nil)
                   (when (and cd (string-match "filename=\"\\(.*\\)\"" cd))
                     (setq filename (match-string 1))))
                 (let ((redirect (plist-get status :redirect)))
                   (unless filename
                     (let* ((o (if redirect redirect url))
                            (o (url-generic-parse-url o))
                            (o (car (url-path-and-query o)))
                            (o (directory-file-name o))
                            (o (file-name-nondirectory o)))
                       (setq filename (eww-decode-url-file-name o))))
                   `(,status ,(if redirect redirect url) ,@rest))))))
    (advice-add 'eww-render :filter-args aux)
    (advice-add 'eww-download-callback :filter-args aux))
  (setq eww-download-directory "~")
  (advice-add 'eww-make-unique-file-name :filter-args
              (lambda (args)
                (let ((file (car args)))
                  `(,(if (zerop (length file)) "index.html" file)
                    ,(funcall dir-or-pwd)))))
  (advice-add 'my/eww-save-binary-file :override
              (lambda ()
                (goto-char (point-min))
                (unwind-protect
                    (let ((file (read-file-name
                                 "Filename to save as: "
                                 (funcall dir-or-pwd) filename))
                          (require-final-newline nil))
                      (write-region (point-min) (point-max) file))
                  (kill-buffer (current-buffer)))))
  (setq mailcap-user-mime-data
        '(((viewer . my/eww-save-binary-file) (type . "application/.+")))))

上面的代码在eww-download和直接进入下载链接时产生作用。下载的目录我的习惯是如果eww-download-directory不存在,就下到当前文件夹下。

当然,目前的还有点小问题:如果要下载的xxx.pdf已存在,eww-download应该是想要把新文件保存为xxx(2).pdf。但实际上,新的文件被保存成了xxx(2)200。。。我觉得这不是我的锅。。。

还有,涉及content-disposition的部分,我没测过。

1 个赞