diff options
| author | Michael Albinus | 2008-07-24 21:45:01 +0000 |
|---|---|---|
| committer | Michael Albinus | 2008-07-24 21:45:01 +0000 |
| commit | 24008bc4f3ed8e6068e90e184b2dd7dccf83ac5b (patch) | |
| tree | 7e1b87cb81e79dddd3865c3358afc91e2986321f | |
| parent | 6449674edfbc2e10da852da5d4cc253ce7cf91f4 (diff) | |
| download | emacs-24008bc4f3ed8e6068e90e184b2dd7dccf83ac5b.tar.gz emacs-24008bc4f3ed8e6068e90e184b2dd7dccf83ac5b.zip | |
* net/xesam.el: New file.
| -rw-r--r-- | lisp/net/xesam.el | 696 |
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. | ||
| 227 | This defconst can be used to check for a new search engine, which | ||
| 228 | fields 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. | ||
| 260 | VALUE can be a string, a non-negative integer, a boolean | ||
| 261 | value (nil or t), or a list of them. It returns the new value of | ||
| 262 | PROPERTY in the search engine. This new value can be different | ||
| 263 | from 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. | ||
| 295 | Every entry is a triple of the unique D-Bus service name of the | ||
| 296 | engine, the session identifier, and the display name. Example: | ||
| 297 | |||
| 298 | \(\(\":1.59\" \"0t1214948020ut358230u0p2698r3912347765k3213849828\" \"Tracker Xesam Service\") | ||
| 299 | \(\":1.27\" \"strigisession1369133069\" \"Strigi Desktop Search\")) | ||
| 300 | |||
| 301 | A Xesam-compatible search engine is identified as a queued D-Bus | ||
| 302 | service 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'. | ||
| 313 | The first search engine is the name owner of `xesam-service-search'. | ||
| 314 | If 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. | ||
| 374 | In this mode, widgets represent the search results. | ||
| 375 | |||
| 376 | \\{xesam-mode-map} | ||
| 377 | Turning 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. | ||
| 403 | SERVICE is the D-Bus unique service name of the Xesam search engine. | ||
| 404 | SEARCH 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. | ||
| 586 | ENGINE identifies the search engine. QUERY is a string in the | ||
| 587 | Xesam user query language. A string, identifying the search, is | ||
| 588 | returned." | ||
| 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. | ||
| 645 | ENGINE is the Xesam search engine to be applied, it must be one of the | ||
| 646 | entries of `xesam-search-engines'. QUERY is the search string in the | ||
| 647 | Xesam user query language. If the search engine does not support | ||
| 648 | the Xesam user query language, a Xesam fulltext search is applied. | ||
| 649 | |||
| 650 | The default search engine is the first entry in `xesam-search-engines'. | ||
| 651 | Example: | ||
| 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 | ||