aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--lisp/net/xesam.el696
1 files changed, 696 insertions, 0 deletions
diff --git a/lisp/net/xesam.el b/lisp/net/xesam.el
new file mode 100644
index 00000000000..7bd7691f3b9
--- /dev/null
+++ b/lisp/net/xesam.el
@@ -0,0 +1,696 @@
1;;; xesam.el --- Xesam interface to search engines.
2
3;; Copyright (C) 2008 Free Software Foundation, Inc.
4
5;; Author: Michael Albinus <michael.albinus@gmx.de>
6;; Keywords: tools, hypermedia
7
8;; This file is part of GNU Emacs.
9
10;; GNU Emacs 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 3, or (at your option)
13;; any later version.
14
15;; GNU Emacs 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, see
22;; <http://www.gnu.org/licenses/>.
23
24;;; Commentary:
25
26;; This package provides an interface to the Xesam, a D-Bus based "eXtEnsible
27;; Search And Metadata specification". It has been tested with
28;;
29;; xesam-glib 0.3.4, xesam-tools 0.6.1
30;; beagle 0.3.7, beagle-xesam 0.2
31;; strigi 0.5.10
32
33;; The precondition for this package is a D-Bus aware Emacs. This is
34;; configured per default, when Emacs is built on a machine running
35;; D-Bus. Furthermore, there must be at least one search engine
36;; running, which support the Xesam interface. Beagle and strigi have
37;; been tested; tracker, pinot and recoll are also said to support
38;; Xesam. You can check the existence of such a search engine by
39;;
40;; (dbus-list-queued-owners :session "org.freedesktop.xesam.searcher")
41
42;; In order to start a search, you must load xesam.el:
43;;
44;; (require 'xesam)
45
46;; xesam.el supports two types of queries, which are explained *very* short:
47;;
48;; * Full text queries. Just search keys shall be given, like
49;;
50;; hello world
51;;
52;; A full text query in xesam.el is restricted to files.
53;;
54;; * Xesam End User Search Language queries. The Xesam query language
55;; is described at <http://xesam.org/main/XesamUserSearchLanguage>,
56;; which must be consulted for the whole features.
57;;
58;; A query string consists of search keys, collectors, selectors,
59;; and phrases. Search keys are words like in a full text query:
60;;
61;; hello word
62;;
63;; A selector is a tuple <keyword><relation>. <keyword> can be any
64;; predefined Xesam keyword, the most common keywords are "ext"
65;; (file name extension), "format " (mime type), "tag" (user
66;; keywords) and "type" (types of items, like "audio", "file",
67;; "picture", "attachment"). <relation> is a comparison to a value,
68;; which must be a string (relation ":" or "=") or number (relation
69;; "<=", ">=", "<", ">"):
70;;
71;; type:attachment ext=el
72;;
73;; A collector is one of the items "AND", "and", "&&", "OR", "or",
74;; "||", or "-". The default collector on multiple terms is "AND";
75;; "-" means "AND NOT".
76;;
77;; albinus -type:file
78;;
79;; A phrase is a string enclosed in quotes, with appended modifiers
80;; (single letters). Examples of modifiers are "c" (case
81;; sensitive), "C" (case insensitive), "e" (exact match), "r"
82;; (regular expression):
83;;
84;; "Hello world"c
85
86;; You can customize, whether you want to apply a Xesam user query, or
87;; a full text query. Note, that not every search engine supports
88;; both query types.
89;;
90;; (setq xesam-query-type 'fulltext-query)
91;;
92;; Another option to be customised is the number of hits to be
93;; presented at once.
94;;
95;; (setq xesam-hits-per-page 50)
96
97;; A search can be started by the command
98;;
99;; M-x xesam-search
100;;
101;; When several search engines are registered, the engine to be used
102;; can be selected via minibuffer completion. Afterwards, the query
103;; shall be entered in the minibuffer.
104
105;;; Code:
106
107;; D-Bus support in the Emacs core can be disabled with configuration
108;; option "--without-dbus". Declare used subroutines and variables.
109(declare-function dbus-call-method "dbusbind.c")
110(declare-function dbus-register-signal "dbusbind.c")
111
112(require 'dbus)
113
114;; Pacify byte compiler.
115(eval-when-compile
116 (require 'cl))
117
118;; Widgets are used to highlight the search results.
119(require 'widget)
120
121(eval-when-compile
122 (require 'wid-edit))
123
124;; `run-at-time' is used in the signal handler.
125(require 'timer)
126
127;; The default search field is "xesam:url". It must be inspected.
128(require 'url)
129
130(defgroup xesam nil
131 "Xesam compatible interface to search engines."
132 :group 'extensions
133 :group 'hypermedia
134 :version "23.1")
135
136(defcustom xesam-query-type 'user-query
137 "Xesam query language type."
138 :group 'xesam
139 :type '(choice
140 (const :tag "Xesam user query" user-query)
141 (const :tag "Xesam fulltext query" fulltext-query)))
142
143(defcustom xesam-hits-per-page 20
144 "Number of search hits to be displayed in the result buffer"
145 :group 'xesam
146 :type 'integer)
147
148(defvar xesam-debug nil
149 "Insert debug information.")
150
151(defconst xesam-service-search "org.freedesktop.xesam.searcher"
152 "The D-Bus name used to talk to Xesam.")
153
154(defconst xesam-path-search "/org/freedesktop/xesam/searcher/main"
155 "The D-Bus object path used to talk to Xesam.")
156
157;; Methods: "NewSession", "SetProperty", "GetProperty",
158;; "CloseSession", "NewSearch", "StartSearch", "GetHitCount",
159;; "GetHits", "GetHitData", "CloseSearch" and "GetState".
160;; Signals: "HitsAdded", "HitsRemoved", "HitsModified", "SearchDone"
161;; and "StateChanged".
162(defconst xesam-interface-search "org.freedesktop.xesam.Search"
163 "The D-Bus Xesam search interface.")
164
165(defconst xesam-all-fields
166 '("xesam:35mmEquivalent" "xesam:Alarm" "xesam:Archive" "xesam:Audio"
167 "xesam:AudioList" "xesam:Content" "xesam:DataObject" "xesam:DeletedFile"
168 "xesam:Document" "xesam:Email" "xesam:EmailAttachment" "xesam:Event"
169 "xesam:File" "xesam:FileSystem" "xesam:FreeBusy" "xesam:IMMessage"
170 "xesam:Image" "xesam:Journal" "xesam:Mailbox" "xesam:Media"
171 "xesam:MediaList" "xesam:Message" "xesam:PIM" "xesam:Partition"
172 "xesam:Photo" "xesam:Presentation" "xesam:Project" "xesam:RemoteResource"
173 "xesam:Software" "xesam:SourceCode" "xesam:Spreadsheet" "xesam:Storage"
174 "xesam:Task" "xesam:TextDocument" "xesam:Video" "xesam:Visual"
175 "xesam:aimContactMedium" "xesam:aperture" "xesam:aspectRatio"
176 "xesam:attachmentEncoding" "xesam:attendee" "xesam:audioBirate"
177 "xesam:audioChannels" "xesam:audioCodec" "xesam:audioCodecType"
178 "xesam:audioSampleFormat" "xesam:audioSampleRate" "xesam:author"
179 "xesam:bcc" "xesam:birthDate" "xesam:blogContactURL"
180 "xesam:cameraManufacturer" "xesam:cameraModel" "xesam:cc" "xesam:ccdWidth"
181 "xesam:cellPhoneNumber" "xesam:characterCount" "xesam:charset"
182 "xesam:colorCount" "xesam:colorSpace" "xesam:columnCount" "xesam:comment"
183 "xesam:commentCharacterCount" "xesam:conflicts" "xesam:contactMedium"
184 "xesam:contactName" "xesam:contactNick" "xesam:contactPhoto"
185 "xesam:contactURL" "xesam:contains" "xesam:contenKeyword"
186 "xesam:contentComment" "xesam:contentCreated" "xesam:contentModified"
187 "xesam:contentType" "xesam:contributor" "xesam:copyright" "xesam:creator"
188 "xesam:definesClass" "xesam:definesFunction" "xesam:definesGlobalVariable"
189 "xesam:deletionTime" "xesam:depends" "xesam:description" "xesam:device"
190 "xesam:disclaimer" "xesam:documentCategory" "xesam:duration"
191 "xesam:emailAddress" "xesam:eventEnd" "xesam:eventLocation"
192 "xesam:eventStart" "xesam:exposureBias" "xesam:exposureProgram"
193 "xesam:exposureTime" "xesam:faxPhoneNumber" "xesam:fileExtension"
194 "xesam:fileSystemType" "xesam:flashUsed" "xesam:focalLength"
195 "xesam:focusDistance" "xesam:formatSubtype" "xesam:frameCount"
196 "xesam:frameRate" "xesam:freeSpace" "xesam:gender" "xesam:generator"
197 "xesam:generatorOptions" "xesam:group" "xesam:hash" "xesam:hash"
198 "xesam:height" "xesam:homeEmailAddress" "xesam:homePhoneNumber"
199 "xesam:homePostalAddress" "xesam:homepageContactURL"
200 "xesam:horizontalResolution" "xesam:icqContactMedium" "xesam:id"
201 "xesam:imContactMedium" "xesam:interests" "xesam:interlaceMode"
202 "xesam:isEncrypted" "xesam:isImportant" "xesam:isInProgress"
203 "xesam:isPasswordProtected" "xesam:isRead" "xesam:isoEquivalent"
204 "xesam:jabberContactMedium" "xesam:keyword" "xesam:language" "xesam:legal"
205 "xesam:license" "xesam:licenseType" "xesam:lineCount" "xesam:links"
206 "xesam:mailingPostalAddress" "xesam:maintainer" "xesam:md5Hash"
207 "xesam:mediaCodec" "xesam:mediaCodecBitrate" "xesam:mediaCodecType"
208 "xesam:meteringMode" "xesam:mimeType" "xesam:mountPoint"
209 "xesam:msnContactMedium" "xesam:name" "xesam:occupiedSpace"
210 "xesam:orientation" "xesam:originalLocation" "xesam:owner"
211 "xesam:pageCount" "xesam:permissions" "xesam:phoneNumber"
212 "xesam:physicalAddress" "xesam:pixelFormat" "xesam:primaryRecipient"
213 "xesam:programmingLanguage" "xesam:rating" "xesam:receptionTime"
214 "xesam:recipient" "xesam:related" "xesam:remoteUser" "xesam:rowCount"
215 "xesam:sampleBitDepth" "xesam:sampleFormat" "xesam:secondaryRecipient"
216 "xesam:sha1Hash" "xesam:size" "xesam:skypeContactMedium"
217 "xesam:sourceCreated" "xesam:sourceModified" "xesam:storageSize"
218 "xesam:subject" "xesam:supercedes" "xesam:title" "xesam:to"
219 "xesam:totalSpace" "xesam:totalUncompressedSize" "xesam:url"
220 "xesam:usageIntensity" "xesam:userComment" "xesam:userKeyword"
221 "xesam:uuid" "xesam:version" "xesam:verticalResolution" "xesam:videoBirate"
222 "xesam:videoCodec" "xesam:videoCodecType" "xesam:whiteBalance"
223 "xesam:width" "xesam:wordCount" "xesam:workEmailAddress"
224 "xesam:workPhoneNumber" "xesam:workPostalAddress"
225 "xesam:yahooContactMedium")
226 "All fields from the Xesam Core Ontology.
227This defconst can be used to check for a new search engine, which
228fields are supported.")
229
230(defconst xesam-user-query
231 "<?xml version=\"1.0\" encoding=\"UTF-8\"?>
232<request xmlns=\"http://freedesktop.org/standards/xesam/1.0/query\">
233 <userQuery>
234 %s
235 </userQuery>
236</request>"
237 "The Xesam user query XML.")
238
239(defconst xesam-fulltext-query
240 "<?xml version=\"1.0\" encoding=\"UTF-8\"?>
241<request xmlns=\"http://freedesktop.org/standards/xesam/1.0/query\">
242 <query content=\"xesam:Document\" source=\"xesam:File\">
243 <fullText>
244 <string>%s</string>
245 </fullText>
246 </query>
247</request>"
248 "The Xesam fulltext query XML.")
249
250(defun xesam-get-property (engine property)
251 "Return the PROPERTY value of ENGINE."
252 ;; "GetProperty" returns a variant, so we must use the car.
253 (car (dbus-call-method
254 :session (car engine) xesam-path-search
255 xesam-interface-search "GetProperty"
256 (cdr engine) property)))
257
258(defun xesam-set-property (engine property value)
259 "Set the PROPERTY of ENGINE to VALUE.
260VALUE can be a string, a non-negative integer, a boolean
261value (nil or t), or a list of them. It returns the new value of
262PROPERTY in the search engine. This new value can be different
263from VALUE, depending on what the search engine accepts."
264 ;; "SetProperty" returns a variant, so we must use the car.
265 (car (dbus-call-method
266 :session (car engine) xesam-path-search
267 xesam-interface-search "SetProperty"
268 (cdr engine) property
269 ;; The value must be a variant. It can be only a string, an
270 ;; unsigned int, a boolean, or an array of them. So we need
271 ;; no type keyword; we let the type check to the search
272 ;; engine.
273 (list :variant value))))
274
275(defvar xesam-minibuffer-vendor-history nil
276 "Interactive vendor history.")
277
278(defvar xesam-minibuffer-query-history nil
279 "Interactive query history.")
280
281;; Pacify byte compiler.
282(defvar xesam-engine nil)
283(defvar xesam-search nil)
284(defvar xesam-current nil)
285(defvar xesam-count nil)
286(defvar xesam-from nil)
287(defvar xesam-to nil)
288(defvar xesam-refreshing nil)
289
290
291;;; Search engines.
292
293(defvar xesam-search-engines nil
294 "List of available Xesam search engines.
295Every entry is a triple of the unique D-Bus service name of the
296engine, the session identifier, and the display name. Example:
297
298 \(\(\":1.59\" \"0t1214948020ut358230u0p2698r3912347765k3213849828\" \"Tracker Xesam Service\")
299 \(\":1.27\" \"strigisession1369133069\" \"Strigi Desktop Search\"))
300
301A Xesam-compatible search engine is identified as a queued D-Bus
302service of `xesam-service-search'.")
303
304(defun xesam-delete-search-engine (&rest args)
305 "Removes service from `xesam-search-engines'."
306 (when (and (= (length args) 3) (stringp (car args)))
307 (setq xesam-search-engines
308 (delete (assoc (car args) xesam-search-engines)
309 xesam-search-engines))))
310
311(defun xesam-search-engines ()
312 "Return Xesam search engines, stored in `xesam-search-engines'.
313The first search engine is the name owner of `xesam-service-search'.
314If there is no registered search engine at all, the function returns `nil'."
315 (let ((services (dbus-ignore-errors
316 (dbus-list-queued-owners
317 :session xesam-service-search)))
318 engine vendor-id hit-fields)
319 (dolist (service services)
320 (unless (assoc-string service xesam-search-engines)
321
322 ;; Open a new session, and add it to the search engines list.
323 (add-to-list
324 'xesam-search-engines
325 (setq engine
326 (cons service
327 (dbus-call-method
328 :session service xesam-path-search
329 xesam-interface-search "NewSession")))
330 'append)
331
332 ;; Set the "search.live" property; otherwise the search engine
333 ;; might refuse to answer.
334; (xesam-set-property engine "search.live" t)
335
336 ;; Check the vendor properties.
337 (setq vendor-id (xesam-get-property engine "vendor.id")
338 hit-fields (xesam-get-property engine "hit.fields"))
339
340 ;; Ususally, `hit.fields' shall describe supported fields.
341 ;; That is not the case now, so we set it ourselves.
342 ;; Hopefully, this will change later.
343 (setq hit-fields
344 (cond
345 ((string-equal vendor-id "Beagle")
346 '("xesam:mimeType" "xesam:url"))
347 ((string-equal vendor-id "Strigi")
348 '("xesam:author" "xesam:cc" "xesam:cc" "xesam:charset"
349 "xesam:contentType" "xesam:fileExtension" "xesam:id"
350 "xesam:lineCount" "xesam:links" "xesam:mimeType" "xesam:name"
351 "xesam:size" "xesam:sourceModified" "xesam:subject"
352 "xesam:to" "xesam:url"))
353 ((string-equal vendor-id "TrackerXesamSession")
354 '("xesam:relevancyRating" "xesam:url"))
355 ;; xesam-tools yahoo service.
356 (t '("xesam:contentModified" "xesam:mimeType" "xesam:summary"
357 "xesam:title" "xesam:url" "yahoo:displayUrl"))))
358
359 (xesam-set-property engine "hit.fields" hit-fields)
360 (xesam-set-property engine "hit.fields.extended" '("xesam:snippet"))
361
362 ;; Let us notify, when the search engine disappears.
363 (dbus-register-signal
364 :session dbus-service-dbus dbus-path-dbus
365 dbus-interface-dbus "NameOwnerChanged"
366 'xesam-delete-search-engine service))))
367 xesam-search-engines)
368
369
370;;; Search buffers.
371
372(define-derived-mode xesam-mode nil "Xesam"
373 "Major mode for presenting search results of a Xesam search.
374In this mode, widgets represent the search results.
375
376\\{xesam-mode-map}
377Turning on Xesam mode runs the normal hook `xesam-mode-hook'."
378 ;; Initialize buffer.
379 (setq buffer-read-only t)
380 (let ((inhibit-read-only t))
381 (erase-buffer))
382
383 ;; Keymap.
384 (set-keymap-parent xesam-mode-map widget-keymap)
385 (define-key xesam-mode-map "q" 'quit-window)
386
387 ;; Local variables.
388 (set (make-local-variable 'mode-line-position) (list "%p (0/0)"))
389 ;; `xesam-engine' and `xesam-search' will be set in `xesam-new-search'.
390 (set (make-local-variable 'xesam-engine) nil)
391 (set (make-local-variable 'xesam-search) nil)
392 (set (make-local-variable 'xesam-current) 1)
393 (set (make-local-variable 'xesam-count) 0)
394 (set (make-local-variable 'xesam-from) 1)
395 (set (make-local-variable 'xesam-to) xesam-hits-per-page)
396 ;; `xesam-refreshing' is an indicator, whether the buffer is just
397 ;; being updated. Needed, because `xesam-refresh-search-buffer'
398 ;; can be triggered by an event.
399 (set (make-local-variable 'xesam-refreshing) nil))
400
401(defun xesam-buffer-name (service search)
402 "Return the buffer name where to present search results.
403SERVICE is the D-Bus unique service name of the Xesam search engine.
404SEARCH is the search identification in that engine. Both must be strings."
405 (format "*%s/%s*" service search))
406
407(defun xesam-refresh-entry (engine search hit-number)
408 "Refreshes one entry in the search buffer."
409 (let* ((result
410 (car
411 (dbus-call-method
412 :session (car engine) xesam-path-search
413 xesam-interface-search "GetHits" search 1)))
414 (snippet)
415 ;; We must disable this for the time being; the search
416 ;; engines don't return usable values so far.
417; (caaar
418; (dbus-ignore-errors
419; (dbus-call-method
420; :session (car engine) xesam-path-search
421; xesam-interface-search "GetHitData"
422; search (list hit-number) '("snippet")))))
423 widget)
424
425 ;; Create widget.
426 (setq widget (widget-convert 'link))
427
428 ;; Take all results.
429 (dolist (field (xesam-get-property engine "hit.fields"))
430 (when (not (zerop (length (caar result))))
431 (when xesam-debug
432 (widget-insert (format "%s: %s\n" field (caar result))))
433 (widget-put widget (intern (concat ":" field)) (caar result)))
434 (setq result (cdr result)))
435
436 ;; Strigi doesn't return URLs in xesam:url. We must fix this.
437 (when
438 (not (url-type (url-generic-parse-url (widget-get widget :xesam:url))))
439 (widget-put
440 widget :xesam:url (concat "file://" (widget-get widget :xesam:url))))
441
442 ;; First line: :tag.
443 (cond
444 ((widget-member widget :xesam:title)
445 (widget-put widget :tag (widget-get widget :xesam:title)))
446 ((widget-member widget :xesam:subject)
447 (widget-put widget :tag (widget-get widget :xesam:subject)))
448 ((widget-member widget :xesam:mimeType)
449 (widget-put widget :tag (widget-get widget :xesam:mimeType)))
450 ((widget-member widget :xesam:name)
451 (widget-put widget :tag (widget-get widget :xesam:name))))
452
453 ;; Last Modified.
454 (when (widget-member widget :xesam:sourceModified)
455 (widget-put
456 widget :tag
457 (format
458 "%s\nLast Modified: %s"
459 (or (widget-get widget :tag) "")
460 (format-time-string
461 "%d %B %Y, %T"
462 (seconds-to-time
463 (string-to-number (widget-get widget :xesam:sourceModified)))))))
464
465 ;; Second line: :value.
466 (widget-put widget :value (widget-get widget :xesam:url))
467
468 (cond
469 ;; In case of HTML, we use a URL link.
470 ((and (widget-member widget :xesam:mimeType)
471 (string-equal "text/html" (widget-get widget :xesam:mimeType)))
472 (setcar widget 'url-link))
473
474 ;; For local files, we will open the file as default action.
475 ((string-match "file"
476 (url-type (url-generic-parse-url
477 (widget-get widget :xesam:url))))
478 (widget-put
479 widget :notify
480 '(lambda (widget &rest ignore)
481 (find-file
482 (url-filename (url-generic-parse-url (widget-value widget))))))
483 (widget-put
484 widget :value
485 (url-filename (url-generic-parse-url (widget-get widget :xesam:url))))))
486
487 ;; Third line: :doc.
488 (cond
489 ((widget-member widget :xesam:summary)
490 (widget-put widget :doc (widget-get widget :xesam:summary)))
491 ((widget-member widget :xesam:snippet)
492 (widget-put widget :doc (widget-get widget :xesam:snippet))))
493
494 (when (widget-member widget :doc)
495 (widget-put widget :help-echo (widget-get widget :doc))
496 (with-temp-buffer
497 (insert (widget-get widget :doc))
498 (fill-region-as-paragraph (point-min) (point-max))
499 (widget-put widget :doc (buffer-string))))
500
501 ;; Format the widget.
502 (widget-put
503 widget :format
504 (format "%d. %s%%[%%v%%]\n%s\n" hit-number
505 (if (widget-member widget :tag) "%{%t%}\n" "")
506 (if (widget-member widget :doc) "%h" "")))
507
508 ;; Write widget.
509 (goto-char (point-max))
510 (widget-default-create widget)
511 (set-buffer-modified-p nil)
512 (setq mode-line-position
513 (list (format "%%p (%d/%d)" xesam-current xesam-count)))
514 (redisplay)))
515
516(defun xesam-refresh-search-buffer (engine search)
517 "Refreshes the buffer, presenting results of SEARCH."
518 (with-current-buffer (xesam-buffer-name (car engine) search)
519 ;; Work only if nobody else is here.
520 (unless xesam-refreshing
521 (setq xesam-refreshing t)
522 (unwind-protect
523 ;; `xesam-from' is the first result id to be presented.
524 ;; `xesam-current' is the last result which has been presented.
525 ;; `xesam-to' is the upper result to be presented.
526 ;; All of them are buffer-local variables.
527 (let ((from (max xesam-from xesam-current))
528 widget next)
529 ;; Add all result widgets. The upper boundary is always
530 ;; computed, because new hits might have arrived while
531 ;; running.
532 (while (<= from (min xesam-to xesam-count))
533 (xesam-refresh-entry engine search from)
534 (setq next t
535 from (1+ from)
536 xesam-current from))
537
538 ;; Add "NEXT" widget.
539 (when (and next (> xesam-count xesam-to))
540 (goto-char (point-max))
541 (widget-create
542 'link
543 :notify
544 '(lambda (widget &rest ignore)
545 (setq xesam-from (+ xesam-from xesam-hits-per-page)
546 xesam-to (+ xesam-to xesam-hits-per-page))
547 (widget-delete widget)
548 (xesam-refresh-search-buffer xesam-engine xesam-search))
549 "NEXT")
550 (widget-beginning-of-line)))
551
552 ;; Return with save settings.
553 (setq xesam-refreshing nil)))))
554
555
556;;; Search functions.
557
558(defun xesam-signal-handler (&rest args)
559 "Handles the different D-Bus signals of a Xesam search."
560 (let* ((service (dbus-event-service-name last-input-event))
561 (member (dbus-event-member-name last-input-event))
562 (search (nth 0 args))
563 (buffer (xesam-buffer-name service search)))
564
565 (when (get-buffer buffer)
566 (with-current-buffer buffer
567 (cond
568
569 ((string-equal member "HitsAdded")
570 (setq xesam-count (+ xesam-count (nth 1 args))
571 mode-line-position
572 (list (format "%%p (%d/%d)" (1- xesam-current) xesam-count)))
573 ;; We use `run-at-time' in order to not block the event queue.
574 (run-at-time
575 0 nil
576 'xesam-refresh-search-buffer
577 (assoc service xesam-search-engines) search))
578
579 ((string-equal member "SearchDone")
580 (setq mode-line-process
581 (propertize " Done" 'face 'font-lock-type-face))
582 (force-mode-line-update)))))))
583
584(defun xesam-new-search (engine query)
585 "Create a new search session.
586ENGINE identifies the search engine. QUERY is a string in the
587Xesam user query language. A string, identifying the search, is
588returned."
589 (let* ((service (car engine))
590 (session (cdr engine))
591 (search (dbus-call-method
592 :session service xesam-path-search
593 xesam-interface-search "NewSearch" session query)))
594 ;; Let us notify for relevant signals. We ignore "HitsRemoved",
595 ;; "HitsModified" and "StateChanged"; there is nothing to do for
596 ;; us.
597 (dbus-register-signal
598 :session service xesam-path-search
599 xesam-interface-search "HitsAdded"
600 'xesam-signal-handler search)
601 (dbus-register-signal
602 :session service xesam-path-search
603 xesam-interface-search "SearchDone"
604 'xesam-signal-handler search)
605 (dbus-call-method
606 :session (car engine) xesam-path-search
607 xesam-interface-search "StartSearch" search)
608 ;; Create the search buffer.
609 (with-current-buffer
610 (generate-new-buffer (xesam-buffer-name service search))
611 (switch-to-buffer-other-window (current-buffer))
612 (xesam-mode)
613 (setq xesam-engine engine
614 xesam-search search
615 mode-line-buffer-identification
616 (xesam-get-property engine "vendor.id"))
617 (when xesam-debug
618 (widget-insert
619 (format "vendor.id: %s\n"
620 (xesam-get-property engine "vendor.id"))
621 (format "vendor.version: %s\n"
622 (xesam-get-property engine "vendor.version"))
623 (format "vendor.display: %s\n"
624 (xesam-get-property engine "vendor.display"))
625 (format "vendor.xesam: %s\n"
626 (xesam-get-property engine "vendor.xesam"))
627 (format "vendor.ontology.fields: %s\n"
628 (xesam-get-property engine "vendor.ontology.fields"))
629 (format "vendor.ontology.contents: %s\n"
630 (xesam-get-property engine "vendor.ontology.contents"))
631 (format "vendor.ontology.sources: %s\n"
632 (xesam-get-property engine "vendor.ontology.sources"))
633 (format "vendor.extensions: %s\n"
634 (xesam-get-property engine "vendor.extensions"))
635 (format "vendor.ontologies: %s\n"
636 (xesam-get-property engine "vendor.ontologies"))
637 (format "vendor.maxhits: %s\n\n"
638 (xesam-get-property engine "vendor.maxhits")))))
639
640 ;; Return search id.
641 search))
642
643(defun xesam-search (engine query)
644 "Perform an interactive search.
645ENGINE is the Xesam search engine to be applied, it must be one of the
646entries of `xesam-search-engines'. QUERY is the search string in the
647Xesam user query language. If the search engine does not support
648the Xesam user query language, a Xesam fulltext search is applied.
649
650The default search engine is the first entry in `xesam-search-engines'.
651Example:
652
653 (xesam-search (car (xesam-search-engines)) \"emacs\")"
654 (interactive
655 (let* ((vendors (mapcar
656 '(lambda (x) (xesam-get-property x "vendor.display"))
657 (xesam-search-engines)))
658 (vendor
659 (if (> (length vendors) 1)
660 (completing-read
661 "Enter search engine: " vendors nil t
662 (try-completion "" vendors) 'xesam-minibuffer-vendor-history)
663 (car vendors))))
664 (list
665 ;; ENGINE.
666 (when vendor
667 (dolist (elt (xesam-search-engines) engine)
668 (when (string-equal (xesam-get-property elt "vendor.display") vendor)
669 (setq engine elt))))
670 ;; QUERY.
671 (when vendor
672 (read-from-minibuffer
673 "Enter search string: " nil nil nil
674 'xesam-minibuffer-query-history)))))
675
676 (if (and engine (stringp query))
677 (if (eq xesam-query-type 'user-query)
678 (xesam-new-search engine (format xesam-user-query query))
679 (xesam-new-search engine (format xesam-fulltext-query query)))
680 ;; There might be no search engine available ATM.
681 (message "No query applied")))
682
683(provide 'xesam)
684
685;;; TODO:
686
687;; * Retrieve several results at once.
688;; * Improve mode-line handling. Show search string etc.
689;; * Minibuffer completion for user queries.
690
691;; * Mid term
692;; - If available, use ontologies for field selection.
693;; - Search engines for Emacs bugs database, wikipedia, google,
694;; yahoo, ebay, ...
695
696;;; xesam.el ends here