1 ;;; elmo-search.el --- Search by external program interface for ELMO.
3 ;; Copyright (C) 2005 Yuuichi Teranishi <teranisi@gohome.org>
5 ;; Author: Yuuichi Teranishi <teranisi@gohome.org>
6 ;; Hiroya Murata <lapis-lazuli@pop06.odn.ne.jp>
7 ;; Keywords: mail, net news
9 ;; This file is part of ELMO (Elisp Library for Message Orchestration).
11 ;; This program is free software; you can redistribute it and/or modify
12 ;; it under the terms of the GNU General Public License as published by
13 ;; the Free Software Foundation; either version 2, or (at your option)
16 ;; This program is distributed in the hope that it will be useful,
17 ;; but WITHOUT ANY WARRANTY; without even the implied warranty of
18 ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
19 ;; GNU General Public License for more details.
21 ;; You should have received a copy of the GNU General Public License
22 ;; along with GNU Emacs; see the file COPYING. If not, write to the
23 ;; Free Software Foundation, Inc., 59 Temple Place - Suite 330,
24 ;; Boston, MA 02111-1307, USA.
32 (eval-when-compile (require 'cl))
38 (defcustom elmo-search-use-drive-letter
39 (memq system-type '(OS/2 emx windows-nt))
40 "*If non-nil, do a drive letter conversion (e.g. /a|/ => a:/)."
41 :type '(choice (const :tag "Not use" nil)
45 (defvar elmo-search-engine-alist nil
46 "*An alist of search engines.
47 Each element looks like (ENGINE CLASS PROPERTIES...)
48 ENGINE is a symbol, the name of the search engine.
49 CLASS is a symbol, the class name that performs a search.
50 PROPERTIES is a plist, it configure an engine with the CLASS.")
52 (defcustom elmo-search-default-engine 'namazu
53 "*Default search engine for elmo-search folder."
58 (defconst elmo-search-folder-name-syntax `(pattern (?\] param (?! engine))))
63 (luna-define-class elmo-search-engine () (param))
64 (luna-define-internal-accessors 'elmo-search-engine))
66 (luna-define-generic elmo-search-engine-do-search (engine pattern)
67 "Search messages which is match PATTERN by ENGINE.")
69 (luna-define-generic elmo-search-engine-create-message-entity (engine
72 "Create msgdb entity for the message in the FOLDER with NUMBER.")
74 (luna-define-generic elmo-search-engine-fetch-message (engine location)
75 "Fetch a message into current buffer.
76 ENGINE is the ELMO search engine structure.
77 LOCATION is the location of the message.
78 Returns non-nil if fetching was succeed.")
80 (defun elmo-make-search-engine (type &optional param)
81 (let ((spec (or (cdr (assq type elmo-search-engine-alist))
82 (error "Undefined search engine `%s'" type))))
83 (require (intern (format "elmo-search-%s" (car spec))))
84 (apply 'luna-make-entity
85 (intern (format "elmo-search-engine-%s" (car spec)))
89 (defun elmo-search-register-engine (name class &rest properties)
90 (let ((cell (assq name elmo-search-engine-alist))
91 (spec (cons class properties)))
94 (setq elmo-search-engine-alist
95 (cons (cons name spec) elmo-search-engine-alist)))))
99 (luna-define-class elmo-search-folder (elmo-map-folder)
101 (luna-define-internal-accessors 'elmo-search-folder))
103 (luna-define-method elmo-folder-initialize ((folder elmo-search-folder)
105 (when (> (length name) 0)
106 (let* ((tokens (car (elmo-parse-separated-tokens
108 elmo-search-folder-name-syntax)))
109 (engine (cdr (assq 'engine tokens))))
110 (elmo-search-folder-set-engine-internal
112 (elmo-make-search-engine (if (> (length engine) 0)
114 elmo-search-default-engine)
115 (cdr (assq 'param tokens))))
116 (elmo-search-folder-set-pattern-internal
118 (cdr (assq 'pattern tokens)))))
121 (luna-define-method elmo-folder-expand-msgdb-path ((folder elmo-search-folder))
123 (elmo-replace-string-as-filename
124 (elmo-folder-name-internal folder))
125 (expand-file-name "search" elmo-msgdb-directory)))
127 (luna-define-method elmo-folder-msgdb-create ((folder elmo-search-folder)
129 (let ((new-msgdb (elmo-make-msgdb))
130 (num (length numbers))
132 (message "Creating msgdb...")
133 (elmo-with-progress-display (> num elmo-display-progress-threshold)
134 (elmo-folder-msgdb-create num "Creating msgdb...")
135 (dolist (number numbers)
136 (setq entity (elmo-search-engine-create-message-entity
137 (elmo-search-folder-engine-internal folder)
138 (elmo-msgdb-message-entity-handler new-msgdb)
141 (elmo-msgdb-append-entity new-msgdb entity '(new unread)))
142 (elmo-progress-notify 'elmo-folder-msgdb-create)))
143 (message "Creating msgdb...done")
146 (luna-define-method elmo-folder-message-file-p ((folder elmo-search-folder))
149 (defun elmo-search-location-to-filename (location)
150 (when (string-match "^file://" location)
151 (let ((filename (substring location (match-end 0))))
153 (if (and elmo-search-use-drive-letter
154 (string-match "^/\\([A-Za-z]\\)[:|]/\\(.*\\)$" filename))
155 (replace-match "\\1:/\\2" t nil filename)
158 (luna-define-method elmo-message-file-name ((folder elmo-search-folder)
160 (elmo-search-location-to-filename
161 (elmo-map-message-location folder number)))
163 (luna-define-method elmo-folder-message-make-temp-file-p
164 ((folder elmo-search-folder))
167 (luna-define-method elmo-folder-diff ((folder elmo-search-folder))
170 (luna-define-method elmo-folder-message-make-temp-files ((folder
175 (let ((temp-dir (elmo-folder-make-temporary-directory folder))
176 (cur-number (or start-number 0)))
177 (dolist (number numbers)
179 (elmo-message-file-name folder number)
181 (number-to-string (if start-number cur-number number))
186 (luna-define-method elmo-map-message-fetch ((folder elmo-search-folder)
188 &optional section unseen)
189 (elmo-search-engine-fetch-message
190 (elmo-search-folder-engine-internal folder)
193 (luna-define-method elmo-map-folder-list-message-locations
194 ((folder elmo-search-folder))
195 (elmo-search-engine-do-search
196 (elmo-search-folder-engine-internal folder)
197 (elmo-search-folder-pattern-internal folder)))
199 (luna-define-method elmo-folder-exists-p ((folder elmo-search-folder))
200 (elmo-search-folder-pattern-internal folder))
202 (luna-define-method elmo-folder-have-subfolder-p ((folder elmo-search-folder))
203 (null (elmo-search-folder-pattern-internal folder)))
205 (luna-define-method elmo-folder-list-subfolders ((folder elmo-search-folder)
208 (lambda (name) (elmo-recover-string-from-filename name))
209 (directory-files (expand-file-name "search" elmo-msgdb-directory)
211 (concat "^" (regexp-quote
212 (elmo-folder-prefix-internal folder))))))
214 (luna-define-method elmo-folder-delete-messages ((folder elmo-search-folder)
216 (elmo-folder-kill-messages folder numbers)
222 ;; external program search engine
224 (luna-define-class elmo-search-engine-extprog (elmo-search-engine)
225 (prog args charset parser))
226 (luna-define-internal-accessors 'elmo-search-engine-extprog))
228 (luna-define-method elmo-search-engine-do-search
229 ((engine elmo-search-engine-extprog) pattern)
231 (let ((charset (elmo-search-engine-extprog-charset-internal engine))
232 (parser (or (elmo-search-engine-extprog-parser-internal engine)
233 #'elmo-search-parse-filename-list)))
235 (elmo-search-engine-extprog-prog-internal engine)
242 (cond ((stringp arg) arg)
245 (encode-mime-charset-string pattern charset)
249 (funcall arg engine pattern)
250 (wrong-number-of-arguments
251 (funcall arg engine))))
254 (symbol-value arg))))
255 (elmo-search-engine-extprog-args-internal engine)))))
258 ;; search engine for local files
260 (luna-define-class elmo-search-engine-local-file
261 (elmo-search-engine-extprog))
262 (luna-define-internal-accessors 'elmo-search-engine-local-file))
264 (defun elmo-search-parse-filename-list ()
266 (goto-char (point-min))
269 (when (and elmo-search-use-drive-letter
270 (looking-at "^\\([A-Za-z]\\)[:|]/"))
271 (replace-match "/\\1:/")
273 (unless (looking-at "^file://")
278 (setq locations (cons (buffer-substring bol (point)) locations))
280 (nreverse locations)))
282 (luna-define-method elmo-search-engine-create-message-entity
283 ((engine elmo-search-engine-local-file) handler folder number)
284 (let ((filename (elmo-message-file-name folder number))
287 (setq entity (elmo-msgdb-create-message-entity-from-file
288 handler number filename)))
289 (unless (or (elmo-message-entity-field entity 'to)
290 (elmo-message-entity-field entity 'cc)
291 (not (string= (elmo-message-entity-field entity 'subject)
293 (elmo-message-entity-set-field entity 'subject
294 (file-name-nondirectory filename))
295 (setq uid (nth 2 (file-attributes filename)))
296 (elmo-message-entity-set-field entity 'from
299 " <"(user-login-name uid) "@"
303 (luna-define-method elmo-search-engine-fetch-message
304 ((engine elmo-search-engine-local-file) location)
305 (let ((filename (elmo-search-location-to-filename location)))
306 (when (and filename (file-exists-p filename))
308 (insert-file-contents-as-binary filename)
309 (unless (or (std11-field-body "To")
310 (std11-field-body "Cc")
311 (std11-field-body "Subject"))
312 (let (charset guess uid)
314 (set-buffer-multibyte t)
315 (insert-file-contents filename)
316 (setq charset (detect-mime-charset-region (point-min)
318 (goto-char (point-min))
319 (setq guess (mime-find-file-type filename))
320 (setq uid (nth 2 (file-attributes filename)))
321 (insert "From: " (concat (user-full-name uid)
322 " <"(user-login-name uid) "@"
323 (system-name) ">") "\n")
324 (insert "Subject: " filename "\n")
325 (insert "Content-Type: "
326 (concat (nth 0 guess) "/" (nth 1 guess))
327 "; charset=" (upcase (symbol-name charset))
328 "\nMIME-Version: 1.0\n\n")
329 (encode-mime-charset-region (point-min) (point-max) charset)
330 (set-buffer-multibyte nil)))))))
332 (provide 'elmo-search-local-file)
335 (defcustom elmo-search-namazu-default-index-path "~/Mail"
336 "*Default index path for namazu.
337 If the value is a list, all elements are used as index paths for namazu."
338 :type '(choice (directory :tag "Index Path")
339 (repeat (directory :tag "Index Path")))
342 (defcustom elmo-search-namazu-index-alias-alist nil
343 "*Alist of ALIAS and INDEX-PATH."
344 :type '(repeat (cons (string :tag "Alias Name")
345 (choice (directory :tag "Index Path")
346 (repeat (directory :tag "Index Path")))))
349 (defun elmo-search-namazu-index (engine pattern)
350 (let* ((param (elmo-search-engine-param-internal engine))
352 ((cdr (assoc param elmo-search-namazu-index-alias-alist)))
353 ((and param (> (length param) 0))
356 elmo-search-namazu-default-index-path))))
358 (mapcar 'expand-file-name index)
359 (expand-file-name index))))
363 (defun elmo-search-grep-target (engine pattern)
364 (let ((dirname (expand-file-name (elmo-search-engine-param-internal engine)))
365 (files (list null-device)))
366 (dolist (filename (directory-files dirname))
367 (unless (string-match "^\\.\\.?" filename)
368 (setq files (cons (expand-file-name filename dirname) files))))
372 ;;; Setup `elmo-search-engine-alist'
373 (unless noninteractive
374 (or (assq 'namazu elmo-search-engine-alist)
375 (elmo-search-register-engine
378 :args '("--all" "--list" "--early" pattern elmo-search-namazu-index)
379 :charset 'iso-2022-jp))
380 (or (assq 'grep elmo-search-engine-alist)
381 (elmo-search-register-engine
384 :args '("-l" "-e" pattern elmo-search-grep-target))))
387 (product-provide (provide 'elmo-search) (require 'elmo-version))
389 ;;; elmo-search.el ends here