* elmo.el (elmo-file-tag): New class.
[elisp/wanderlust.git] / elmo / elmo-file.el
1 ;;; elmo-file.el --- File interface for ELMO.
2
3 ;; Copyright (C) 2000 Yuuichi Teranishi <teranisi@gohome.org>
4
5 ;; Author: Yuuichi Teranishi <teranisi@gohome.org>
6 ;; Keywords: mail, net news
7
8 ;; This file is part of ELMO (Elisp Library for Message Orchestration).
9
10 ;; This program 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 2, or (at your option)
13 ;; any later version.
14 ;;
15 ;; This program 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; see the file COPYING.  If not, write to the
22 ;; Free Software Foundation, Inc., 59 Temple Place - Suite 330,
23 ;; Boston, MA 02111-1307, USA.
24 ;;
25
26 ;;; Commentary:
27 ;;
28
29 ;;; Code:
30 ;;
31 (eval-when-compile (require 'cl))
32
33 (require 'elmo)
34 (require 'elmo-map)
35 (require 'mime-edit)
36
37 (defun elmo-file-find (files)
38   "Return the first existing filename in the FILES."
39   (let (file)
40     (while files
41       (when (file-exists-p (car files))
42         (setq file (car files)
43               files nil))
44       (setq files (cdr files)))
45     (and file (expand-file-name file))))
46
47 (defcustom elmo-file-command (exec-installed-p "file")
48   "*Program name of the file type detection command `file'."
49   :type '(string :tag "Program name of the file")
50   :group 'elmo)
51
52 (defcustom elmo-file-command-argument
53   (let ((magic-file (elmo-file-find
54                      '("/usr/share/magic.mime"
55                        "/usr/share/file/magic.mime"
56                        "/cygwin/usr/share/file/magic.mime"))))
57     (if magic-file (list "-m" magic-file)))
58   "*Argument list for the `file' command.
59 \(It should return the MIME content type\)"
60   :type '(repeat string)
61   :group 'elmo)
62
63 (defcustom elmo-file-fetch-max-size (* 1024 1024)
64   "*Max size of the message fetching."
65   :type 'integer
66   :group 'elmo)
67
68 (eval-and-compile
69   (luna-define-class elmo-file-folder (elmo-map-folder elmo-file-tag)
70                      (file-path))
71   (luna-define-internal-accessors 'elmo-file-folder))
72
73 (luna-define-method elmo-folder-initialize ((folder
74                                              elmo-file-folder)
75                                             name)
76   (elmo-file-folder-set-file-path-internal folder name)
77   folder)
78
79 (luna-define-method elmo-folder-expand-msgdb-path ((folder
80                                                     elmo-file-folder))
81   (expand-file-name
82    (elmo-replace-string-as-filename (elmo-folder-name-internal folder))
83    (expand-file-name "file" elmo-msgdb-directory)))
84
85 (defun elmo-file-make-date-string (attrs)
86   (let ((s (current-time-string (nth 5 attrs))))
87     (string-match "\\`\\([A-Z][a-z][a-z]\\) +[A-Z][a-z][a-z] +[0-9][0-9]? *[0-9][0-9]?:[0-9][0-9]:[0-9][0-9] *[0-9]?[0-9]?[0-9][0-9]"
88                   s)
89     (concat (elmo-match-string 1 s) ", "
90             (timezone-make-date-arpa-standard s (current-time-zone)))))
91
92 (defun elmo-file-detect-content-type (file)
93   "Return content-type of the FILE."
94   (if (or (not (file-exists-p file))
95           (file-directory-p file))
96       "application/octet-stream"
97     (let (type)
98       (setq type (mime-find-file-type file))
99       (if (and (string= (nth 0 type) "application")
100                (string= (nth 1 type) "octet-stream"))
101           (if (and elmo-file-command
102                    elmo-file-command-argument)
103               (with-temp-buffer
104                 (if (zerop (apply 'call-process elmo-file-command
105                                   nil `(,(current-buffer) nil)
106                                   nil (append elmo-file-command-argument
107                                               (list (expand-file-name file)))))
108                     (progn
109                       (goto-char (point-min))
110                       (when (re-search-forward ": *" nil t)
111                         (setq type (buffer-substring (match-end 0)
112                                                      (point-at-eol))))
113                       (cond
114                        ((string= "empty" type)
115                         "application/octet-stream")
116                        ((string-match "text" type)
117                         "text/plain")
118                        (t
119                         (car (split-string type)))))
120                   "application/octet-stream"))
121             (concat (nth 0 type) "/" (nth 1 type)))
122         (concat (nth 0 type) "/" (nth 1 type))))))
123
124 (defun elmo-file-msgdb-create-entity (msgdb folder number)
125   "Create msgdb entity for the message in the FOLDER with NUMBER."
126   (let* ((file (elmo-message-file-name folder number))
127          (attrs (file-attributes file)))
128     (and (not (file-directory-p file))
129          attrs
130          (elmo-msgdb-make-message-entity
131           (elmo-msgdb-message-entity-handler msgdb)
132           :message-id (concat "<" (elmo-replace-in-string
133                                    file "/" ":")
134                               "@" (system-name))
135           :number number
136           :size (nth 7 attrs)
137           :date (elmo-file-make-date-string attrs)
138           :subject (file-name-nondirectory file)
139           :from (concat (user-full-name (nth 2 attrs))
140                         " <" (user-login-name (nth 2 attrs)) "@"
141                         (system-name) ">")))))
142
143 (luna-define-method elmo-folder-msgdb-create ((folder elmo-file-folder)
144                                               numlist flag-table)
145   (let ((new-msgdb (elmo-make-msgdb))
146         entity mark i percent num)
147     (setq num (length numlist))
148     (setq i 0)
149     (message "Creating msgdb...")
150     (while numlist
151       (setq entity
152             (elmo-file-msgdb-create-entity new-msgdb folder (car numlist)))
153       (when entity
154         (elmo-msgdb-append-entity new-msgdb entity '(new unread)))
155       (when (> num elmo-display-progress-threshold)
156         (setq i (1+ i))
157         (setq percent (/ (* i 100) num))
158         (elmo-display-progress
159          'elmo-folder-msgdb-create "Creating msgdb..."
160          percent))
161       (setq numlist (cdr numlist)))
162     (message "Creating msgdb...done")
163     new-msgdb))
164
165 (luna-define-method elmo-folder-message-file-p ((folder elmo-file-folder))
166   t)
167
168 (luna-define-method elmo-message-file-name ((folder elmo-file-folder)
169                                             number)
170   (expand-file-name (car (split-string
171                           (elmo-map-message-location folder number)
172                           "/"))
173                     (elmo-file-folder-file-path-internal folder)))
174
175 (luna-define-method elmo-folder-message-make-temp-file-p
176   ((folder elmo-file-folder))
177   t)
178
179 (luna-define-method elmo-folder-diff ((folder elmo-file-folder))
180   (cons nil nil))
181
182 (luna-define-method elmo-folder-message-make-temp-files ((folder
183                                                           elmo-file-folder)
184                                                          numbers
185                                                          &optional
186                                                          start-number)
187   (let ((temp-dir (elmo-folder-make-temporary-directory folder))
188         (cur-number (or start-number 0)))
189     (dolist (number numbers)
190       (elmo-copy-file
191        (elmo-message-file-name folder number)
192        (expand-file-name
193         (int-to-string (if start-number cur-number number))
194         temp-dir))
195       (incf cur-number))
196     temp-dir))
197
198 (luna-define-method elmo-map-message-fetch ((folder elmo-file-folder)
199                                             location strategy
200                                             &optional section unseen)
201   (let ((file (expand-file-name (car (split-string location "/"))
202                                 (elmo-file-folder-file-path-internal folder)))
203         charset guess uid is-text)
204     (when (file-exists-p file)
205       (set-buffer-multibyte nil)
206       (prog1
207           (insert-file-contents-as-binary file nil 0 elmo-file-fetch-max-size)
208         (unless (or (std11-field-body "To")
209                     (std11-field-body "Cc")
210                     (std11-field-body "Subject"))
211           (setq guess (elmo-file-detect-content-type file))
212           (setq is-text (string-match "^text/" guess))
213           (when is-text
214             (set-buffer-multibyte t)
215             (decode-coding-region
216              (point-min) (point-max)
217              elmo-mime-display-as-is-coding-system)
218             (setq charset (detect-mime-charset-region (point-min)
219                                                       (point-max))))
220           (goto-char (point-min))
221           (setq uid (nth 2 (file-attributes file)))
222           (insert "From: " (concat (user-full-name uid)
223                                    " <"(user-login-name uid) "@"
224                                    (system-name) ">") "\n")
225           (insert "Subject: " (file-name-nondirectory file) "\n")
226           (insert "Date: "
227                   (elmo-file-make-date-string (file-attributes file))
228                   "\n")
229           (insert "Message-ID: "
230                   (concat "<" (elmo-replace-in-string file "/" ":")
231                           "@" (system-name) ">\n"))
232           (insert "Content-Type: "
233                   guess
234                   (or (and is-text
235                            (concat
236                             "; charset=" (upcase (symbol-name charset))))
237                       "")
238                   "\nMIME-Version: 1.0\n\n")
239           (when is-text
240             (encode-mime-charset-region (point-min) (point-max) charset))
241           (set-buffer-multibyte nil))))))
242
243 (luna-define-method elmo-map-folder-list-message-locations
244   ((folder elmo-file-folder))
245   (delq nil
246         (mapcar
247          (lambda (file)
248            (when (not (file-directory-p file))
249              (concat
250               file "/"
251               (mapconcat
252                'number-to-string
253                (nth 5 (file-attributes (expand-file-name
254                                         file
255                                         (elmo-file-folder-file-path-internal
256                                          folder))))
257                ":"))))
258          (directory-files (elmo-file-folder-file-path-internal folder)))))
259
260 (luna-define-method elmo-folder-exists-p ((folder elmo-file-folder))
261   (file-directory-p (elmo-file-folder-file-path-internal folder)))
262
263 (luna-define-method elmo-folder-list-subfolders ((folder elmo-file-folder)
264                                                  &optional one-level)
265   (when (file-directory-p (elmo-file-folder-file-path-internal folder))
266     (append
267      (list (elmo-folder-name-internal folder))
268      (delq nil
269            (mapcar
270             (lambda (file)
271               (when (and (file-directory-p
272                           (expand-file-name
273                            file
274                            (elmo-file-folder-file-path-internal folder)))
275                          (not (string= file "."))
276                          (not (string= file "..")))
277                 (concat (elmo-folder-name-internal folder) "/" file)))
278             (directory-files (elmo-file-folder-file-path-internal
279                               folder)))))))
280
281 (require 'product)
282 (product-provide (provide 'elmo-file) (require 'elmo-version))
283
284 ;;; elmo-file.el ends here