mh-search.el 75 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687168816891690169116921693169416951696169716981699170017011702170317041705170617071708170917101711171217131714171517161717171817191720172117221723172417251726172717281729173017311732173317341735173617371738173917401741174217431744174517461747174817491750175117521753175417551756175717581759176017611762176317641765176617671768176917701771177217731774177517761777177817791780178117821783178417851786178717881789179017911792179317941795179617971798179918001801180218031804180518061807180818091810181118121813181418151816181718181819182018211822182318241825182618271828182918301831183218331834183518361837183818391840184118421843184418451846184718481849185018511852185318541855185618571858185918601861186218631864186518661867186818691870187118721873187418751876187718781879188018811882188318841885188618871888188918901891189218931894189518961897189818991900190119021903190419051906190719081909191019111912191319141915191619171918191919201921192219231924192519261927192819291930193119321933193419351936
  1. ;;; mh-search --- MH-Search mode
  2. ;; Copyright (C) 1993, 1995, 2001-2012 Free Software Foundation, Inc.
  3. ;; Author: Indexed search by Satyaki Das <satyaki@theforce.stanford.edu>
  4. ;; Maintainer: Bill Wohler <wohler@newt.com>
  5. ;; Keywords: mail
  6. ;; See: mh-e.el
  7. ;; This file is part of GNU Emacs.
  8. ;; GNU Emacs is free software: you can redistribute it and/or modify
  9. ;; it under the terms of the GNU General Public License as published by
  10. ;; the Free Software Foundation, either version 3 of the License, or
  11. ;; (at your option) any later version.
  12. ;; GNU Emacs is distributed in the hope that it will be useful,
  13. ;; but WITHOUT ANY WARRANTY; without even the implied warranty of
  14. ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  15. ;; GNU General Public License for more details.
  16. ;; You should have received a copy of the GNU General Public License
  17. ;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>.
  18. ;;; Commentary:
  19. ;; Mode used to compose search criteria.
  20. ;; (1) The following search engines are supported:
  21. ;; swish++
  22. ;; swish-e
  23. ;; mairix
  24. ;; namazu
  25. ;; pick
  26. ;; grep
  27. ;; (2) To use this package, you first have to build an index. Please
  28. ;; read the documentation for `mh-search' to get started. That
  29. ;; documentation will direct you to the specific instructions for
  30. ;; your particular searcher.
  31. ;;; Change Log:
  32. ;;; Code:
  33. (require 'mh-e)
  34. (mh-require-cl)
  35. (require 'gnus-util)
  36. (require 'imenu)
  37. (defvar mh-searcher nil
  38. "Cached value of chosen search program.")
  39. (defvar mh-search-function nil
  40. "Function which executes the search program.")
  41. (defvar mh-search-next-result-function nil
  42. "Function to parse the next line of output.
  43. Expected to return a list of three strings: name of the folder,
  44. message number, and optionally the match.")
  45. (defvar mh-search-regexp-builder nil
  46. "Function used to construct search regexp.")
  47. (defvar mh-index-folder "+mhe-index"
  48. "Folder that contains the folders resulting from the index searches.")
  49. (defvar mh-flists-results-folder "sequence"
  50. "Subfolder for `mh-index-folder' where flists output is placed.")
  51. (defvar mh-flists-sequence)
  52. (defvar mh-flists-called-flag nil)
  53. ;;; MH-Folder Commands
  54. ;;;###mh-autoload
  55. (defun mh-search (folder search-regexp
  56. &optional redo-search-flag window-config)
  57. "Search your MH mail.
  58. This command helps you find messages in your entire corpus of
  59. mail. You can search for messages to or from a particular person
  60. or about a particular subject. In fact, you can also search for
  61. messages containing selected strings in any arbitrary header
  62. field or any string found within the messages.
  63. Out of the box, MH-E uses \"pick\" to find messages. With a
  64. little extra effort, you can set an indexing program which
  65. rewards you with extremely quick results. The drawback is that
  66. sometimes the index does not contain the words you're looking
  67. for. You can still use \"pick\" in these situations.
  68. You are prompted for the FOLDER to search. This can be \"all\" to
  69. search all folders. Note that the search works recursively on the
  70. listed folder.
  71. Next, an MH-Search buffer appears where you can enter search
  72. criteria SEARCH-REGEXP.
  73. From:
  74. To:
  75. Cc:
  76. Date:
  77. Subject:
  78. --------
  79. Edit this template by entering your search criteria in an
  80. appropriate header field that is already there, or create a new
  81. field yourself. If the string you're looking for could be
  82. anywhere in a message, then place the string underneath the row
  83. of dashes.
  84. As an example, let's say that we want to find messages from
  85. Ginnean about horseback riding in the Kosciusko National
  86. Park (Australia) during January, 1994. Normally we would start
  87. with a broad search and narrow it down if necessary to produce a
  88. manageable amount of data, but we'll cut to the chase and create
  89. a fairly restrictive set of criteria as follows:\\<mh-search-mode-map>
  90. From: ginnean
  91. To:
  92. Cc:
  93. Date: Jan 1994
  94. Subject:
  95. --------
  96. horse
  97. kosciusko
  98. As with MH-Letter mode, MH-Search provides commands like
  99. \\[mh-to-field] to help you fill in the blanks.\\<mh-folder-mode-map>
  100. If you find that you do the same thing over and over when editing
  101. the search template, you may wish to bind some shortcuts to keys.
  102. This can be done with the variable `mh-search-mode-hook', which is
  103. called when \\[mh-search] is run on a new pattern.\\<mh-search-mode-map>
  104. To perform the search, type \\[mh-index-do-search].
  105. Sometimes you're searching for text that is either not indexed,
  106. or hasn't been indexed yet. In this case you can override the
  107. default method with the pick method by running the command
  108. \\[mh-pick-do-search].
  109. The messages that are found are put in a temporary sub-folder of
  110. \"+mhe-index\" and are displayed in an MH-Folder buffer. This
  111. buffer is special because it displays messages from multiple
  112. folders; each set of messages from a given folder has a heading
  113. with the folder name.\\<mh-folder-mode-map>
  114. The appearance of the heading can be modified by customizing the
  115. face `mh-search-folder'. You can jump back and forth between the
  116. headings using the commands \\[mh-index-next-folder] and
  117. \\[mh-index-previous-folder].
  118. In addition, the command \\[mh-index-visit-folder] can be used to
  119. visit the folder of the message at point. Initially, only the
  120. messages that matched the search criteria are displayed in the
  121. folder. While the temporary buffer has its own set of message
  122. numbers, the actual messages numbers are shown in the visited
  123. folder. Thus, the command \\[mh-index-visit-folder] is useful to
  124. find the actual message number of an interesting message, or to
  125. view surrounding messages with the command \\[mh-rescan-folder].
  126. Because this folder is temporary, you'll probably get in the
  127. habit of killing it when you're done with \\[mh-kill-folder].
  128. You can regenerate the results by running this command with a
  129. prefix argument REDO-SEARCH-FLAG.
  130. Note: This command uses an \"X-MHE-Checksum:\" header field to
  131. cache the MD5 checksum of a message. This means that if an
  132. incoming message already contains an \"X-MHE-Checksum:\" field,
  133. that message might not be found by this command. The following
  134. \"procmail\" recipe avoids this problem by renaming the existing
  135. header field:
  136. :0 wf
  137. | formail -R \"X-MHE-Checksum\" \"X-Old-MHE-Checksum\"
  138. Configuring Indexed Searches
  139. The command \\[mh-search] runs the command defined by the option
  140. `mh-search-program'. The default value is \"Auto-detect\" which
  141. means that MH-E will automatically choose one of \"swish++\",
  142. \"swish-e\", \"mairix\", \"namazu\", \"pick\" and \"grep\" in
  143. that order. If, for example, you have both \"swish++\" and
  144. \"mairix\" installed and you want to use \"mairix\", then you can
  145. set this option to \"mairix\".
  146. The documentation for the following commands describe how to set
  147. up the various indexing programs to use with MH-E.
  148. - `mh-swish++-execute-search'
  149. - `mh-swish-execute-search'
  150. - `mh-mairix-execute-search'
  151. - `mh-namazu-execute-search'
  152. - `mh-pick-execute-search'
  153. - `mh-grep-execute-search'
  154. In a program, if FOLDER is \"+\" or nil, then mail in all folders
  155. are searched. Optional argument WINDOW-CONFIG stores the window
  156. configuration that will be restored after the user quits the
  157. folder containing the index search results."
  158. (interactive
  159. (list (progn
  160. (mh-find-path)
  161. ;; Yes, we do want to call mh-search-choose every time in case the
  162. ;; user has switched the searcher manually.
  163. (unless (mh-search-choose (and current-prefix-arg
  164. mh-index-previous-search
  165. (cadr mh-index-previous-search)))
  166. (error "No search program found"))
  167. (or (and current-prefix-arg mh-index-sequence-search-flag)
  168. (and current-prefix-arg (car mh-index-previous-search))
  169. (mh-prompt-for-folder "Search" "+" nil "all" t)))
  170. (or (and current-prefix-arg (caddr mh-index-previous-search))
  171. mh-search-regexp-builder
  172. (read-string (format "%s regexp: "
  173. (upcase-initials (symbol-name mh-searcher)))))
  174. current-prefix-arg
  175. (if (and (not (and current-prefix-arg
  176. (caddr mh-index-previous-search)))
  177. mh-search-regexp-builder)
  178. (current-window-configuration)
  179. nil)))
  180. (block mh-search
  181. ;; Redoing a sequence search?
  182. (when (and redo-search-flag mh-index-data mh-index-sequence-search-flag
  183. (not mh-flists-called-flag))
  184. (let ((mh-flists-called-flag t))
  185. (apply #'mh-index-sequenced-messages mh-index-previous-search))
  186. (return-from mh-search))
  187. ;; We have fancy query parsing.
  188. (when (symbolp search-regexp)
  189. (mh-search-folder folder window-config)
  190. (return-from mh-search))
  191. ;; Begin search proper.
  192. (mh-checksum-choose)
  193. (let ((result-count 0)
  194. (old-window-config (or window-config mh-previous-window-config))
  195. (previous-search mh-index-previous-search)
  196. (index-folder (format "%s/%s" mh-index-folder
  197. (mh-index-generate-pretty-name search-regexp))))
  198. ;; Create a new folder for the search results or recreate the old one...
  199. (if (and redo-search-flag mh-index-previous-search)
  200. (let ((buffer-name (buffer-name (current-buffer))))
  201. (mh-process-or-undo-commands buffer-name)
  202. (save-excursion (mh-exec-cmd-quiet nil "rmf" buffer-name))
  203. (mh-exec-cmd-quiet nil "folder" "-create" "-fast" buffer-name)
  204. (setq index-folder buffer-name))
  205. (setq index-folder (mh-index-new-folder index-folder search-regexp)))
  206. (let ((folder-path (format "%s%s" mh-user-path (substring folder 1)))
  207. (folder-results-map (make-hash-table :test #'equal))
  208. (origin-map (make-hash-table :test #'equal)))
  209. ;; Run search program...
  210. (message "Executing %s... " mh-searcher)
  211. (funcall mh-search-function folder-path search-regexp)
  212. ;; Parse searcher output.
  213. (message "Processing %s output... " mh-searcher)
  214. (goto-char (point-min))
  215. (loop for next-result = (funcall mh-search-next-result-function)
  216. while next-result
  217. do (unless (eq next-result 'error)
  218. (unless (gethash (car next-result) folder-results-map)
  219. (setf (gethash (car next-result) folder-results-map)
  220. (make-hash-table :test #'equal)))
  221. (setf (gethash (cadr next-result)
  222. (gethash (car next-result) folder-results-map))
  223. t)))
  224. ;; Copy the search results over.
  225. (maphash #'(lambda (folder msgs)
  226. (let ((cur (car (mh-translate-range folder "cur")))
  227. (msgs (sort (loop for msg being the hash-keys of msgs
  228. collect msg)
  229. #'<)))
  230. (mh-exec-cmd "refile" msgs "-src" folder
  231. "-link" index-folder)
  232. ;; Restore cur to old value, that refile changed
  233. (when cur
  234. (mh-exec-cmd-quiet nil "mark" folder "-add" "-zero"
  235. "-sequence"
  236. "cur" (format "%s" cur)))
  237. (loop for msg in msgs
  238. do (incf result-count)
  239. (setf (gethash result-count origin-map)
  240. (cons folder msg)))))
  241. folder-results-map)
  242. ;; Vist the results folder.
  243. (mh-visit-folder index-folder () (list folder-results-map origin-map))
  244. (goto-char (point-min))
  245. (forward-line)
  246. (mh-update-sequences)
  247. (mh-recenter nil)
  248. ;; Update the speedbar, if needed.
  249. (when (mh-speed-flists-active-p)
  250. (mh-speed-flists t mh-current-folder))
  251. ;; Maintain history.
  252. (when (or (and redo-search-flag previous-search) window-config)
  253. (setq mh-previous-window-config old-window-config))
  254. (setq mh-index-previous-search (list folder mh-searcher search-regexp))
  255. ;; Write out data to disk.
  256. (unless mh-flists-called-flag (mh-index-write-data))
  257. (message "%s found %s matches in %s folders"
  258. (upcase-initials (symbol-name mh-searcher))
  259. (loop for msg-hash being the hash-values of mh-index-data
  260. sum (hash-table-count msg-hash))
  261. (loop for msg-hash being the hash-values of mh-index-data
  262. count (> (hash-table-count msg-hash) 0)))))))
  263. ;; Shush compiler.
  264. (mh-do-in-xemacs
  265. (defvar pick-folder))
  266. (defun mh-search-folder (folder window-config)
  267. "Search FOLDER for messages matching a pattern.
  268. In a program, argument WINDOW-CONFIG is the current window
  269. configuration and is used when the search folder is dismissed."
  270. (interactive (list (mh-prompt-for-folder "Search" mh-current-folder nil nil t)
  271. (current-window-configuration)))
  272. (let ((pick-folder (if (equal folder "+") mh-current-folder folder)))
  273. (switch-to-buffer-other-window "search-pattern")
  274. (if (or (zerop (buffer-size))
  275. (not (y-or-n-p "Reuse pattern? ")))
  276. (mh-make-pick-template)
  277. (message ""))
  278. (mh-make-local-vars 'mh-current-folder folder
  279. 'mh-previous-window-config window-config)
  280. (message "%s" (substitute-command-keys
  281. (concat "Type \\[mh-index-do-search] to search messages, "
  282. "\\[mh-pick-do-search] to use pick, "
  283. "\\[mh-help] for help")))))
  284. (defun mh-make-pick-template ()
  285. "Initialize the current buffer with a template for a pick pattern."
  286. (let ((inhibit-read-only t)) (erase-buffer))
  287. (insert "From: \n"
  288. "To: \n"
  289. "Cc: \n"
  290. "Date: \n"
  291. "Subject: \n"
  292. "---------\n")
  293. (mh-search-mode)
  294. (goto-char (point-min))
  295. (dotimes (i 5)
  296. (add-text-properties (point) (1+ (point)) '(front-sticky t))
  297. (add-text-properties (- (mh-line-end-position) 2)
  298. (1- (mh-line-end-position))
  299. '(rear-nonsticky t))
  300. (add-text-properties (point) (1- (mh-line-end-position)) '(read-only t))
  301. (forward-line))
  302. (add-text-properties (point) (1+ (point)) '(front-sticky t))
  303. (add-text-properties (point) (1- (mh-line-end-position)) '(read-only t))
  304. (goto-char (point-max)))
  305. ;; Sequence Searches
  306. ;;;###mh-autoload
  307. (defun mh-index-new-messages (folders)
  308. "Display unseen messages.
  309. If you use a program such as \"procmail\" to use \"rcvstore\" to file
  310. your incoming mail automatically, you can display new, unseen,
  311. messages using this command. All messages in the \"unseen\"
  312. sequence from the folders in `mh-new-messages-folders' are
  313. listed.
  314. With a prefix argument, enter a space-separated list of FOLDERS,
  315. or nothing to search all folders."
  316. (interactive
  317. (list (if current-prefix-arg
  318. (split-string (read-string "Search folder(s) (default all): "))
  319. mh-new-messages-folders)))
  320. (mh-index-sequenced-messages folders mh-unseen-seq))
  321. ;;;###mh-autoload
  322. (defun mh-index-ticked-messages (folders)
  323. "Display ticked messages.
  324. All messages in `mh-tick-seq' from the folders in
  325. `mh-ticked-messages-folders' are listed.
  326. With a prefix argument, enter a space-separated list of FOLDERS,
  327. or nothing to search all folders."
  328. (interactive
  329. (list (if current-prefix-arg
  330. (split-string (read-string "Search folder(s) (default all): "))
  331. mh-ticked-messages-folders)))
  332. (mh-index-sequenced-messages folders mh-tick-seq))
  333. ;; Shush compiler.
  334. (mh-do-in-xemacs
  335. (defvar mh-mairix-folder)
  336. (defvar mh-flists-search-folders))
  337. ;;;###mh-autoload
  338. (defun mh-index-sequenced-messages (folders sequence)
  339. "Display messages in any sequence.
  340. All messages from the FOLDERS in `mh-new-messages-folders' in the
  341. SEQUENCE you provide are listed. With a prefix argument, enter a
  342. space-separated list of folders at the prompt, or nothing to
  343. search all folders."
  344. (interactive
  345. (list (if current-prefix-arg
  346. (split-string (read-string "Search folder(s) (default all): "))
  347. mh-new-messages-folders)
  348. (mh-read-seq-default "Search" nil)))
  349. (unless sequence (setq sequence mh-unseen-seq))
  350. (let* ((mh-flists-search-folders folders)
  351. (mh-flists-sequence sequence)
  352. (mh-flists-called-flag t)
  353. (mh-searcher 'flists)
  354. (mh-search-function 'mh-flists-execute)
  355. (mh-search-next-result-function 'mh-mairix-next-result)
  356. (mh-mairix-folder mh-user-path)
  357. (mh-search-regexp-builder nil)
  358. (new-folder (format "%s/%s/%s" mh-index-folder
  359. mh-flists-results-folder sequence))
  360. (window-config (if (equal new-folder mh-current-folder)
  361. mh-previous-window-config
  362. (current-window-configuration)))
  363. (redo-flag nil)
  364. message)
  365. (cond ((buffer-live-p (get-buffer new-folder))
  366. ;; The destination folder is being visited. Trick `mh-search'
  367. ;; into thinking that the folder resulted from a previous search.
  368. (set-buffer new-folder)
  369. (setq mh-index-previous-search (list folders mh-searcher sequence))
  370. (setq redo-flag t))
  371. ((mh-folder-exists-p new-folder)
  372. ;; Folder exists but we don't have it open. That means they are
  373. ;; stale results from a old flists search. Clear it out.
  374. (mh-exec-cmd-quiet nil "rmf" new-folder)))
  375. (setq message (mh-search "+" mh-flists-results-folder
  376. redo-flag window-config)
  377. mh-index-sequence-search-flag t
  378. mh-index-previous-search (list folders mh-searcher sequence))
  379. (mh-index-write-data)
  380. (when (stringp message) (message "%s" message))))
  381. (defvar mh-flists-search-folders)
  382. (defun mh-flists-execute (&rest ignored)
  383. "Execute flists.
  384. Search for messages belonging to `mh-flists-sequence' in the
  385. folders specified by `mh-flists-search-folders'. If
  386. `mh-recursive-folders-flag' is t, then the folders are searched
  387. recursively. All arguments are IGNORED."
  388. (set-buffer (get-buffer-create mh-temp-index-buffer))
  389. (erase-buffer)
  390. (unless (executable-find "sh")
  391. (error "Didn't find sh"))
  392. (with-temp-buffer
  393. (let ((seq (symbol-name mh-flists-sequence)))
  394. (insert "for folder in `" (expand-file-name "flists" mh-progs) " "
  395. (cond ((eq mh-flists-search-folders t)
  396. (mh-quote-for-shell mh-inbox))
  397. ((eq mh-flists-search-folders nil) "")
  398. ((listp mh-flists-search-folders)
  399. (loop for folder in mh-flists-search-folders
  400. concat
  401. (concat " " (mh-quote-for-shell folder)))))
  402. (if mh-recursive-folders-flag " -recurse" "")
  403. " -sequence " seq " -noshowzero -fast` ; do\n"
  404. (expand-file-name "mhpath" mh-progs) " \"+$folder\" " seq "\n"
  405. "done\n"))
  406. (call-process-region
  407. (point-min) (point-max) "sh" nil (get-buffer mh-temp-index-buffer))))
  408. ;; Navigation
  409. ;;;###mh-autoload
  410. (defun mh-index-next-folder (&optional backward-flag)
  411. "Jump to the next folder marker.
  412. With non-nil optional argument BACKWARD-FLAG, jump to the previous
  413. group of results."
  414. (interactive "P")
  415. (if (null mh-index-data)
  416. (message "Only applicable in an MH-E index search buffer")
  417. (let ((point (point)))
  418. (forward-line (if backward-flag 0 1))
  419. (cond ((if backward-flag
  420. (re-search-backward "^+" (point-min) t)
  421. (re-search-forward "^+" (point-max) t))
  422. (beginning-of-line))
  423. ((and (if backward-flag
  424. (goto-char (point-max))
  425. (goto-char (point-min)))
  426. nil))
  427. ((if backward-flag
  428. (re-search-backward "^+" (point-min) t)
  429. (re-search-forward "^+" (point-max) t))
  430. (beginning-of-line))
  431. (t (goto-char point))))))
  432. ;;;###mh-autoload
  433. (defun mh-index-previous-folder ()
  434. "Jump to the previous folder marker."
  435. (interactive)
  436. (mh-index-next-folder t))
  437. ;;;###mh-autoload
  438. (defun mh-index-visit-folder ()
  439. "Visit original folder from where the message at point was found."
  440. (interactive)
  441. (unless mh-index-data
  442. (error "Not in an index folder"))
  443. (let (folder msg)
  444. (save-excursion
  445. (cond ((and (bolp) (eolp))
  446. (ignore-errors (forward-line -1))
  447. (setq msg (mh-get-msg-num t)))
  448. ((equal (char-after (mh-line-beginning-position)) ?+)
  449. (setq folder (buffer-substring-no-properties
  450. (mh-line-beginning-position)
  451. (mh-line-end-position))))
  452. (t (setq msg (mh-get-msg-num t)))))
  453. (when (not folder)
  454. (setq folder (car (gethash (gethash msg mh-index-msg-checksum-map)
  455. mh-index-checksum-origin-map))))
  456. (when (or (not (get-buffer folder))
  457. (y-or-n-p (format "Reuse buffer displaying %s? " folder)))
  458. (mh-visit-folder
  459. folder (loop for x being the hash-keys of (gethash folder mh-index-data)
  460. when (mh-msg-exists-p x folder) collect x)))))
  461. ;;; Search Menu
  462. (easy-menu-define
  463. mh-pick-menu mh-search-mode-map "Menu for MH-E Search"
  464. '("Search"
  465. ["Perform Search" mh-index-do-search t]
  466. ["Search with pick" mh-pick-do-search t]))
  467. ;;; MH-Search Keys
  468. ;; If this changes, modify mh-search-mode-help-messages accordingly, below.
  469. (gnus-define-keys mh-search-mode-map
  470. "\C-c?" mh-help
  471. "\C-c\C-c" mh-index-do-search
  472. "\C-c\C-p" mh-pick-do-search
  473. "\C-c\C-f\C-b" mh-to-field
  474. "\C-c\C-f\C-c" mh-to-field
  475. "\C-c\C-f\C-m" mh-to-field
  476. "\C-c\C-f\C-s" mh-to-field
  477. "\C-c\C-f\C-t" mh-to-field
  478. "\C-c\C-fb" mh-to-field
  479. "\C-c\C-fc" mh-to-field
  480. "\C-c\C-fm" mh-to-field
  481. "\C-c\C-fs" mh-to-field
  482. "\C-c\C-ft" mh-to-field)
  483. ;;; MH-Search Help Messages
  484. ;; Group messages logically, more or less.
  485. (defvar mh-search-mode-help-messages
  486. '((nil
  487. "Perform search: \\[mh-index-do-search]\n"
  488. "Search with pick: \\[mh-pick-do-search]\n\n"
  489. "Move to a field by typing C-c C-f C-<field>\n"
  490. "where <field> is the first letter of the desired field\n"
  491. "(except for From: which uses \"m\")."))
  492. "Key binding cheat sheet.
  493. This is an associative array which is used to show the most common
  494. commands. The key is a prefix char. The value is one or more strings
  495. which are concatenated together and displayed in the minibuffer if ?
  496. is pressed after the prefix character. The special key nil is used to
  497. display the non-prefixed commands.
  498. The substitutions described in `substitute-command-keys' are performed
  499. as well.")
  500. ;;; MH-Search Mode
  501. (put 'mh-search-mode 'mode-class 'special)
  502. (define-derived-mode mh-search-mode fundamental-mode "MH-Search"
  503. "Mode for creating search templates in MH-E.\\<mh-search-mode-map>
  504. Edit this template by entering your search criteria in an
  505. appropriate header field that is already there, or create a new
  506. field yourself. If the string you're looking for could be
  507. anywhere in a message, then place the string underneath the row
  508. of dashes.
  509. To perform the search, type \\[mh-index-do-search].
  510. Sometimes you're searching for text that is either not indexed,
  511. or hasn't been indexed yet. In this case you can override the
  512. default method with the pick method by running the command
  513. \\[mh-pick-do-search].
  514. The hook `mh-search-mode-hook' is called upon entry to this mode.
  515. \\{mh-search-mode-map}"
  516. (easy-menu-add mh-pick-menu)
  517. (mh-set-help mh-search-mode-help-messages))
  518. ;;; MH-Search Commands
  519. (defun mh-index-do-search (&optional searcher)
  520. "Find messages using `mh-search-program'.
  521. If optional argument SEARCHER is present, use it instead of
  522. `mh-search-program'."
  523. (interactive)
  524. (unless (mh-search-choose searcher) (error "No search program found"))
  525. (let* ((regexp-list (mh-pick-parse-search-buffer))
  526. (pattern (funcall mh-search-regexp-builder regexp-list)))
  527. (if pattern
  528. (mh-search mh-current-folder pattern nil mh-previous-window-config)
  529. (error "No search terms"))))
  530. (defun mh-pick-do-search ()
  531. "Find messages using \"pick\".
  532. Uses the pick method described in `mh-pick-execute-search'."
  533. (interactive)
  534. (mh-index-do-search 'pick))
  535. (defun mh-pick-parse-search-buffer ()
  536. "Parse the search buffer contents.
  537. The function returns an alist. The car of each element is either
  538. the header name to search in or nil to search the whole message.
  539. The cdr of the element is the pattern to search."
  540. (save-excursion
  541. (let ((pattern-list ())
  542. (in-body-flag nil)
  543. start begin)
  544. (goto-char (point-min))
  545. (while (not (eobp))
  546. (if (search-forward "--------" (mh-line-end-position) t)
  547. (setq in-body-flag t)
  548. (beginning-of-line)
  549. (setq begin (point))
  550. (setq start (if in-body-flag
  551. (point)
  552. (search-forward ":" (mh-line-end-position) t)
  553. (point)))
  554. (push (cons (and (not in-body-flag)
  555. (intern (downcase
  556. (buffer-substring-no-properties
  557. begin (1- start)))))
  558. (mh-index-parse-search-regexp
  559. (buffer-substring-no-properties
  560. start (mh-line-end-position))))
  561. pattern-list))
  562. (forward-line))
  563. pattern-list)))
  564. (defun mh-index-parse-search-regexp (input-string)
  565. "Construct parse tree for INPUT-STRING.
  566. All occurrences of &, |, ! and ~ in INPUT-STRING are replaced by
  567. AND, OR and NOT as appropriate. Then the resulting string is
  568. parsed."
  569. (let (input)
  570. (with-temp-buffer
  571. (insert input-string)
  572. ;; replace tabs
  573. (mh-replace-string "\t" " ")
  574. ;; synonyms of AND
  575. (mh-replace-string " AND " " and ")
  576. (mh-replace-string "&" " and ")
  577. (mh-replace-string " -and " " and ")
  578. ;; synonyms of OR
  579. (mh-replace-string " OR " " or ")
  580. (mh-replace-string "|" " or ")
  581. (mh-replace-string " -or " " or ")
  582. ;; synonyms of NOT
  583. (mh-replace-string " NOT " " not ")
  584. (mh-replace-string "!" " not ")
  585. (mh-replace-string "~" " not ")
  586. (mh-replace-string " -not " " not ")
  587. ;; synonyms of left brace
  588. (mh-replace-string "(" " ( ")
  589. (mh-replace-string " -lbrace " " ( ")
  590. ;; synonyms of right brace
  591. (mh-replace-string ")" " ) ")
  592. (mh-replace-string " -rbrace " " ) ")
  593. ;; get the normalized input
  594. (setq input (format "( %s )" (buffer-substring (point-min) (point-max)))))
  595. (let ((tokens (mh-index-add-implicit-ops (split-string input)))
  596. (op-stack ())
  597. (operand-stack ())
  598. oper1)
  599. (dolist (token tokens)
  600. (cond ((equal token "(") (push 'paren op-stack))
  601. ((equal token "not") (push 'not op-stack))
  602. ((equal token "or") (push 'or op-stack))
  603. ((equal token "and") (push 'and op-stack))
  604. ((equal token ")")
  605. (multiple-value-setq (op-stack operand-stack)
  606. (values-list (mh-index-evaluate op-stack operand-stack)))
  607. (when (eq (car op-stack) 'not)
  608. (setq op-stack (cdr op-stack))
  609. (push `(not ,(pop operand-stack)) operand-stack))
  610. (when (eq (car op-stack) 'and)
  611. (setq op-stack (cdr op-stack))
  612. (setq oper1 (pop operand-stack))
  613. (push `(and ,(pop operand-stack) ,oper1) operand-stack)))
  614. ((eq (car op-stack) 'not)
  615. (setq op-stack (cdr op-stack))
  616. (push `(not ,token) operand-stack)
  617. (when (eq (car op-stack) 'and)
  618. (setq op-stack (cdr op-stack))
  619. (setq oper1 (pop operand-stack))
  620. (push `(and ,(pop operand-stack) ,oper1) operand-stack)))
  621. ((eq (car op-stack) 'and)
  622. (setq op-stack (cdr op-stack))
  623. (push `(and ,(pop operand-stack) ,token) operand-stack))
  624. (t (push token operand-stack))))
  625. (prog1 (pop operand-stack)
  626. (when (or op-stack operand-stack)
  627. (error "Invalid regexp: %s" input))))))
  628. (defun mh-index-add-implicit-ops (tokens)
  629. "Add implicit operators in the list TOKENS."
  630. (let ((result ())
  631. (literal-seen nil)
  632. current)
  633. (while tokens
  634. (setq current (pop tokens))
  635. (cond ((or (equal current ")") (equal current "and") (equal current "or"))
  636. (setq literal-seen nil)
  637. (push current result))
  638. ((and literal-seen
  639. (push "and" result)
  640. (setq literal-seen nil)
  641. nil))
  642. (t
  643. (push current result)
  644. (unless (or (equal current "(") (equal current "not"))
  645. (setq literal-seen t)))))
  646. (nreverse result)))
  647. (defun mh-index-evaluate (op-stack operand-stack)
  648. "Read expression till starting paren based on OP-STACK and OPERAND-STACK."
  649. (block mh-index-evaluate
  650. (let (op oper1)
  651. (while op-stack
  652. (setq op (pop op-stack))
  653. (cond ((eq op 'paren)
  654. (return-from mh-index-evaluate (list op-stack operand-stack)))
  655. ((eq op 'not)
  656. (push `(not ,(pop operand-stack)) operand-stack))
  657. ((or (eq op 'and) (eq op 'or))
  658. (setq oper1 (pop operand-stack))
  659. (push `(,op ,(pop operand-stack) ,oper1) operand-stack))))
  660. (error "Ran out of tokens"))))
  661. ;;; Indexing Functions
  662. ;; Support different search programs
  663. (defvar mh-search-choices
  664. '((swish++
  665. mh-swish++-binary mh-swish++-execute-search mh-swish++-next-result
  666. mh-swish++-regexp-builder)
  667. (swish
  668. mh-swish-binary mh-swish-execute-search mh-swish-next-result nil)
  669. (mairix
  670. mh-mairix-binary mh-mairix-execute-search mh-mairix-next-result
  671. mh-mairix-regexp-builder)
  672. (namazu
  673. mh-namazu-binary mh-namazu-execute-search mh-namazu-next-result nil)
  674. (pick
  675. mh-pick-binary mh-pick-execute-search mh-pick-next-result
  676. mh-pick-regexp-builder)
  677. (grep
  678. mh-grep-binary mh-grep-execute-search mh-grep-next-result nil))
  679. "List of possible searcher choices.")
  680. (defun mh-search-choose (&optional searcher)
  681. "Choose a searching function.
  682. The side-effects of this function are that the variables
  683. `mh-searcher', `mh-search-function', and
  684. `mh-search-next-result-function' are set according to the first
  685. searcher in `mh-search-choices' present on the system. If
  686. optional argument SEARCHER is present, use it instead of
  687. `mh-search-program'."
  688. (block nil
  689. (let ((program-alist (cond (searcher
  690. (list (assoc searcher mh-search-choices)))
  691. (mh-search-program
  692. (list
  693. (assoc mh-search-program mh-search-choices)))
  694. (t mh-search-choices))))
  695. (while program-alist
  696. (let* ((current (pop program-alist))
  697. (executable (symbol-value (cadr current))))
  698. (when executable
  699. (setq mh-searcher (car current))
  700. (setq mh-search-function (nth 2 current))
  701. (setq mh-search-next-result-function (nth 3 current))
  702. (setq mh-search-regexp-builder (nth 4 current))
  703. (return mh-searcher))))
  704. nil)))
  705. ;;; Swish++
  706. (defvar mh-swish++-binary (or (executable-find "search++")
  707. (executable-find "search")))
  708. (defvar mh-swish++-directory ".swish++")
  709. (defvar mh-swish-folder nil)
  710. (defun mh-swish++-execute-search (folder-path search-regexp)
  711. "Execute swish++.
  712. In the examples below, replace \"/home/user/Mail\" with the path to
  713. your MH directory.
  714. First create the directory \"/home/user/Mail/.swish++\". Then create
  715. the file \"/home/user/Mail/.swish++/swish++.conf\" with the following
  716. contents:
  717. IncludeMeta Bcc Cc Comments Content-Description From Keywords
  718. IncludeMeta Newsgroups Resent-To Subject To
  719. IncludeMeta Message-Id References In-Reply-To
  720. IncludeFile Mail *
  721. IndexFile /home/user/Mail/.swish++/swish++.index
  722. Use the following command line to generate the swish index. Run
  723. this daily from cron:
  724. find /home/user/Mail -path /home/user/Mail/mhe-index -prune \\
  725. -o -path /home/user/Mail/.swish++ -prune \\
  726. -o -name \"[0-9]*\" -print \\
  727. | index -c /home/user/Mail/.swish++/swish++.conf -
  728. This command does not index the folders that hold the results of your
  729. searches in \"+mhe-index\" since they tend to be ephemeral and the
  730. original messages are indexed anyway.
  731. On some systems (Debian GNU/Linux, for example), use \"index++\"
  732. instead of \"index\".
  733. In a program, FOLDER-PATH is the directory in which SEARCH-REGEXP is
  734. used to search."
  735. (set-buffer (get-buffer-create mh-temp-index-buffer))
  736. (erase-buffer)
  737. (unless mh-swish++-binary
  738. (error "Set `mh-swish++-binary' appropriately"))
  739. (call-process mh-swish++-binary nil '(t nil) nil
  740. "-m" "10000"
  741. (format "-i%s%s/swish++.index"
  742. mh-user-path mh-swish++-directory)
  743. search-regexp)
  744. (goto-char (point-min))
  745. (setq mh-swish-folder
  746. (let ((last-char (substring folder-path (1- (length folder-path)))))
  747. (if (equal last-char "/")
  748. folder-path
  749. (format "%s/" folder-path)))))
  750. (defalias 'mh-swish++-next-result 'mh-swish-next-result)
  751. (defun mh-swish++-regexp-builder (regexp-list)
  752. "Generate query for swish++.
  753. REGEXP-LIST is an alist of fields and values."
  754. (let ((regexp ""))
  755. (dolist (elem regexp-list)
  756. (when (cdr elem)
  757. (setq regexp (concat regexp " and "
  758. (if (car elem) "(" "")
  759. (if (car elem) (symbol-name (car elem)) "")
  760. (if (car elem) " = " "")
  761. (mh-swish++-print-regexp (cdr elem))
  762. (if (car elem) ")" "")))))
  763. (substring regexp 4)))
  764. (defun mh-swish++-print-regexp (expr)
  765. "Return infix expression corresponding to EXPR."
  766. (cond ((atom expr) (format "%s" expr))
  767. ((eq (car expr) 'not)
  768. (format "(not %s)" (mh-swish++-print-regexp (cadr expr))))
  769. (t (format "(%s %s %s)" (mh-swish++-print-regexp (cadr expr))
  770. (symbol-name (car expr))
  771. (mh-swish++-print-regexp (caddr expr))))))
  772. ;;; Swish
  773. (defvar mh-swish-binary (executable-find "swish-e"))
  774. (defvar mh-swish-directory ".swish")
  775. (defun mh-swish-execute-search (folder-path search-regexp)
  776. "Execute swish-e.
  777. In the examples below, replace \"/home/user/Mail\" with the path
  778. to your MH directory.
  779. First create the directory \"/home/user/Mail/.swish\". Then
  780. create the file \"/home/user/Mail/.swish/config\" with the
  781. following contents:
  782. DefaultContents TXT*
  783. IndexDir /home/user/Mail
  784. IndexFile /home/user/Mail/.swish/index
  785. IndexName \"Mail Index\"
  786. IndexDescription \"Mail Index\"
  787. IndexPointer \"http://nowhere\"
  788. IndexAdmin \"nobody\"
  789. #MetaNames automatic
  790. IndexReport 3
  791. FollowSymLinks no
  792. UseStemming no
  793. IgnoreTotalWordCountWhenRanking yes
  794. WordCharacters abcdefghijklmnopqrstuvwxyz0123456789-
  795. BeginCharacters abcdefghijklmnopqrstuvwxyz
  796. EndCharacters abcdefghijklmnopqrstuvwxyz0123456789
  797. IgnoreLimit 50 1000
  798. IndexComments 0
  799. FileRules filename contains \\D
  800. FileRules pathname contains /home/user/Mail/.swish
  801. FileRules pathname contains /home/user/Mail/mhe-index
  802. This configuration does not index the folders that hold the
  803. results of your searches in \"+mhe-index\" since they tend to be
  804. ephemeral and the original messages are indexed anyway.
  805. If there are any directories you would like to ignore, append
  806. lines like the following to \"config\":
  807. FileRules pathname contains /home/user/Mail/scripts
  808. Use the following command line to generate the swish index. Run
  809. this daily from cron:
  810. swish-e -c /home/user/Mail/.swish/config
  811. In a program, FOLDER-PATH is the directory in which SEARCH-REGEXP
  812. is used to search."
  813. (set-buffer (get-buffer-create mh-temp-index-buffer))
  814. (erase-buffer)
  815. (unless mh-swish-binary
  816. (error "Set `mh-swish-binary' appropriately"))
  817. (call-process mh-swish-binary nil '(t nil) nil
  818. "-w" search-regexp
  819. "-f" (format "%s%s/index" mh-user-path mh-swish-directory))
  820. (goto-char (point-min))
  821. (setq mh-swish-folder
  822. (let ((last-char (substring folder-path (1- (length folder-path)))))
  823. (if (equal last-char "/")
  824. folder-path
  825. (format "%s/" folder-path)))))
  826. (defun mh-swish-next-result ()
  827. "Get the next result from swish output."
  828. (prog1
  829. (block nil
  830. (when (or (eobp) (equal (char-after (point)) ?.))
  831. (return nil))
  832. (when (equal (char-after (point)) ?#)
  833. (return 'error))
  834. (let* ((start (search-forward " " (mh-line-end-position) t))
  835. (end (search-forward " " (mh-line-end-position) t)))
  836. (unless (and start end)
  837. (return 'error))
  838. (setq end (1- end))
  839. (unless (file-exists-p (buffer-substring-no-properties start end))
  840. (return 'error))
  841. (unless (search-backward "/" start t)
  842. (return 'error))
  843. (list (let* ((s (buffer-substring-no-properties start (1+ (point)))))
  844. (unless (string-match mh-swish-folder s)
  845. (return 'error))
  846. (if (and (string-match mh-user-path s)
  847. (< (match-end 0) (1- (length s))))
  848. (format "+%s"
  849. (substring s (match-end 0) (1- (length s))))
  850. (return 'error)))
  851. (let* ((s (buffer-substring-no-properties (1+ (point)) end))
  852. (n (ignore-errors (string-to-number s))))
  853. (if n n (return 'error)))
  854. nil)))
  855. (forward-line)))
  856. ;;; Mairix
  857. (defvar mh-mairix-binary (executable-find "mairix"))
  858. (defvar mh-mairix-directory ".mairix")
  859. (defvar mh-mairix-folder nil)
  860. (defun mh-mairix-execute-search (folder-path search-regexp-list)
  861. "Execute mairix.
  862. In the examples below, replace \"/home/user/Mail\" with the path
  863. to your MH directory.
  864. First create the directory \"/home/user/Mail/.mairix\". Then
  865. create the file \"/home/user/Mail/.mairix/config\" with the
  866. following contents:
  867. base=/home/user/Mail
  868. # List of folders that should be indexed. 3 dots at the end means there
  869. # are subfolders within the folder
  870. mh=archive...:inbox:drafts:news:sent:trash
  871. vfolder_format=mh
  872. database=/home/user/Mail/.mairix/database
  873. Use the following command line to generate the mairix index. Run
  874. this daily from cron:
  875. mairix -f /home/user/Mail/.mairix/config
  876. In a program, FOLDER-PATH is the directory in which
  877. SEARCH-REGEXP-LIST is used to search."
  878. (set-buffer (get-buffer-create mh-temp-index-buffer))
  879. (erase-buffer)
  880. (unless mh-mairix-binary
  881. (error "Set `mh-mairix-binary' appropriately"))
  882. (apply #'call-process mh-mairix-binary nil '(t nil) nil
  883. "-r" "-f" (format "%s%s/config" mh-user-path mh-mairix-directory)
  884. search-regexp-list)
  885. (goto-char (point-min))
  886. (setq mh-mairix-folder
  887. (let ((last-char (substring folder-path (1- (length folder-path)))))
  888. (if (equal last-char "/")
  889. folder-path
  890. (format "%s/" folder-path)))))
  891. (defun mh-mairix-next-result ()
  892. "Return next result from mairix output."
  893. (prog1
  894. (block nil
  895. (when (or (eobp) (and (bolp) (eolp)))
  896. (return nil))
  897. (unless (eq (char-after) ?/)
  898. (return 'error))
  899. (let ((start (point))
  900. end msg-start)
  901. (setq end (mh-line-end-position))
  902. (unless (search-forward mh-mairix-folder end t)
  903. (return 'error))
  904. (goto-char (match-beginning 0))
  905. (unless (equal (point) start)
  906. (return 'error))
  907. (goto-char end)
  908. (unless (search-backward "/" start t)
  909. (return 'error))
  910. (setq msg-start (1+ (point)))
  911. (goto-char start)
  912. (unless (search-forward mh-user-path end t)
  913. (return 'error))
  914. (list (format "+%s" (buffer-substring-no-properties
  915. (point) (1- msg-start)))
  916. (string-to-number
  917. (buffer-substring-no-properties msg-start end))
  918. nil)))
  919. (forward-line)))
  920. (defun mh-mairix-regexp-builder (regexp-list)
  921. "Generate query for mairix.
  922. REGEXP-LIST is an alist of fields and values."
  923. (let ((result ()))
  924. (dolist (pair regexp-list)
  925. (when (cdr pair)
  926. (push
  927. (concat
  928. (cond ((eq (car pair) 'to) "t:")
  929. ((eq (car pair) 'from) "f:")
  930. ((eq (car pair) 'cc) "c:")
  931. ((eq (car pair) 'to-or-cc) "tc:")
  932. ((eq (car pair) 'address) "a:")
  933. ((eq (car pair) 'subject) "s:")
  934. ((eq (car pair) 'subject-or-body) "bs:")
  935. ((eq (car pair) 'date) "d:")
  936. ((eq (car pair) 'message-id) "m:")
  937. ((eq (car pair) 'message-body) "b:")
  938. ((eq (car pair) 'message-size) "z:")
  939. ((eq (car pair) 'message-attachment-name) "n:")
  940. ((eq (car pair) 'message-flags) "F:")
  941. (t ""))
  942. (let ((sop (cdr (mh-mairix-convert-to-sop* (cdr pair))))
  943. (final ""))
  944. (dolist (conjunct sop)
  945. (let ((expr-list (cdr conjunct))
  946. (expr-string ""))
  947. (dolist (e expr-list)
  948. (setq expr-string (concat expr-string ","
  949. (if (atom e) "" "~")
  950. (if (atom e) e (cadr e)))))
  951. (setq final (concat final "/" (substring expr-string 1)))))
  952. (substring final 1)))
  953. result)))
  954. result))
  955. (defun mh-mairix-convert-to-sop* (expr)
  956. "Convert EXPR to sum of product form."
  957. (cond ((atom expr) `(or (and ,expr)))
  958. ((eq (car expr) 'or)
  959. (cons 'or
  960. (loop for e in (mapcar #'mh-mairix-convert-to-sop* (cdr expr))
  961. append (cdr e))))
  962. ((eq (car expr) 'and)
  963. (let ((conjuncts (mapcar #'mh-mairix-convert-to-sop* (cdr expr)))
  964. result next-factor)
  965. (setq result (pop conjuncts))
  966. (while conjuncts
  967. (setq next-factor (pop conjuncts))
  968. (setq result (let ((res ()))
  969. (dolist (t1 (cdr result))
  970. (dolist (t2 (cdr next-factor))
  971. (push `(and ,@(cdr t1) ,@(cdr t2)) res)))
  972. (cons 'or res))))
  973. result))
  974. ((atom (cadr expr)) `(or (and ,expr)))
  975. ((eq (caadr expr) 'not) (mh-mairix-convert-to-sop* (cadadr expr)))
  976. ((eq (caadr expr) 'and) (mh-mairix-convert-to-sop*
  977. `(or ,@(mapcar #'(lambda (x) `(not ,x))
  978. (cdadr expr)))))
  979. ((eq (caadr expr) 'or) (mh-mairix-convert-to-sop*
  980. `(and ,@(mapcar #'(lambda (x) `(not ,x))
  981. (cdadr expr)))))
  982. (t (error "Unreachable: %s" expr))))
  983. ;;; Namazu
  984. (defvar mh-namazu-binary (executable-find "namazu"))
  985. (defvar mh-namazu-directory ".namazu")
  986. (defvar mh-namazu-folder nil)
  987. (defun mh-namazu-execute-search (folder-path search-regexp)
  988. "Execute namazu.
  989. In the examples below, replace \"/home/user/Mail\" with the path to
  990. your MH directory.
  991. First create the directory \"/home/user/Mail/.namazu\". Then create
  992. the file \"/home/user/Mail/.namazu/mknmzrc\" with the following
  993. contents:
  994. package conf; # Don't remove this line!
  995. $ADDRESS = 'user@localhost';
  996. $ALLOW_FILE = \"[0-9]*\";
  997. $EXCLUDE_PATH = \"^/home/user/Mail/(mhe-index|spam)\";
  998. This configuration does not index the folders that hold the results of
  999. your searches in \"+mhe-index\" since they tend to be ephemeral and
  1000. the original messages are indexed anyway.
  1001. Use the following command line to generate the namazu index. Run this
  1002. daily from cron:
  1003. mknmz -f /home/user/Mail/.namazu/mknmzrc -O /home/user/Mail/.namazu \\
  1004. -q /home/user/Mail
  1005. In a program, FOLDER-PATH is the directory in which SEARCH-REGEXP
  1006. is used to search."
  1007. (let ((namazu-index-directory
  1008. (format "%s%s" mh-user-path mh-namazu-directory)))
  1009. (unless (file-exists-p namazu-index-directory)
  1010. (error "Namazu directory %s not present" namazu-index-directory))
  1011. (unless (executable-find mh-namazu-binary)
  1012. (error "Set `mh-namazu-binary' appropriately"))
  1013. (set-buffer (get-buffer-create mh-temp-index-buffer))
  1014. (erase-buffer)
  1015. (call-process mh-namazu-binary nil '(t nil) nil
  1016. "-alR" search-regexp namazu-index-directory)
  1017. (goto-char (point-min))
  1018. (setq mh-namazu-folder
  1019. (let ((last (substring folder-path (1- (length folder-path)))))
  1020. (if (equal last "/")
  1021. folder-path
  1022. (format "%s/" folder-path))))))
  1023. (defun mh-namazu-next-result ()
  1024. "Get the next result from namazu output."
  1025. (prog1
  1026. (block nil
  1027. (when (eobp) (return nil))
  1028. (let ((file-name (buffer-substring-no-properties
  1029. (point) (mh-line-end-position))))
  1030. (unless (equal (string-match mh-namazu-folder file-name) 0)
  1031. (return 'error))
  1032. (unless (file-exists-p file-name)
  1033. (return 'error))
  1034. (string-match mh-user-path file-name)
  1035. (let* ((folder/msg (substring file-name (match-end 0)))
  1036. (mark (mh-search-from-end ?/ folder/msg)))
  1037. (unless mark (return 'error))
  1038. (list (format "+%s" (substring folder/msg 0 mark))
  1039. (let ((n (ignore-errors (string-to-number
  1040. (substring folder/msg (1+ mark))))))
  1041. (if n n (return 'error)))
  1042. nil))))
  1043. (forward-line)))
  1044. ;;; Pick
  1045. (defvar mh-index-pick-folder)
  1046. (defvar mh-pick-binary "pick")
  1047. (defconst mh-pick-single-dash '(cc date from subject to)
  1048. "Search components that are supported by single-dash option in pick.")
  1049. (defun mh-pick-execute-search (folder-path search-regexp)
  1050. "Execute pick.
  1051. Read \"pick(1)\" or the section Finding Messages with pick in the
  1052. MH book to find out more about how to enter the criteria (see URL
  1053. `http://www.ics.uci.edu/~mh/book/mh/finpic.htm').
  1054. In a program, FOLDER-PATH is the directory in which SEARCH-REGEXP
  1055. is used to search."
  1056. (set-buffer (get-buffer-create mh-temp-index-buffer))
  1057. (erase-buffer)
  1058. (let ((folders
  1059. (mh-folder-list (substring folder-path (length mh-user-path)))))
  1060. (loop for folder in folders do
  1061. (setq folder (concat "+" folder))
  1062. (insert folder "\n")
  1063. (apply #'call-process (expand-file-name "pick" mh-progs)
  1064. nil '(t nil) nil folder "-list" search-regexp)))
  1065. (goto-char (point-min)))
  1066. (defun mh-pick-next-result ()
  1067. "Return the next pick search result."
  1068. (prog1
  1069. (block nil
  1070. (when (eobp) (return nil))
  1071. (when (search-forward-regexp "^\+" (mh-line-end-position) t)
  1072. (setq mh-index-pick-folder
  1073. (buffer-substring-no-properties (mh-line-beginning-position)
  1074. (mh-line-end-position)))
  1075. (return 'error))
  1076. (unless (search-forward-regexp "^[1-9][0-9]*$" (mh-line-end-position) t)
  1077. (return 'error))
  1078. (list mh-index-pick-folder
  1079. (string-to-number
  1080. (buffer-substring-no-properties (mh-line-beginning-position)
  1081. (mh-line-end-position)))
  1082. nil))
  1083. (forward-line)))
  1084. ;; All implementations of pick have special options -cc, -date, -from and
  1085. ;; -subject that allow to search for corresponding components. Any other
  1086. ;; component is searched using option --COMPNAME, for example: `pick
  1087. ;; --x-mailer mh-e'. Mailutils "pick" supports this option using a certain
  1088. ;; kludge, but it prefers the following syntax for this purpose:
  1089. ;; "--component=COMPNAME --pattern=PATTERN".
  1090. ;; -- Sergey Poznyakoff, Aug 2003
  1091. (defun mh-pick-regexp-builder (pattern-list)
  1092. "Generate pick search expression from PATTERN-LIST."
  1093. (let ((result ()))
  1094. (dolist (pattern pattern-list)
  1095. (when (cdr pattern)
  1096. (setq result `(,@result "-and" "-lbrace"
  1097. ,@(mh-pick-construct-regexp
  1098. (if (and (mh-variant-p 'gnu-mh) (car pattern))
  1099. (format "--pattern=%s" (cdr pattern))
  1100. (cdr pattern))
  1101. (if (car pattern)
  1102. (cond
  1103. ((mh-variant-p 'gnu-mh)
  1104. (format "--component=%s" (car pattern)))
  1105. ((member (car pattern) mh-pick-single-dash)
  1106. (format "-%s" (car pattern)))
  1107. (t
  1108. (format "--%s" (car pattern))))
  1109. "-search"))
  1110. "-rbrace"))))
  1111. (cdr result)))
  1112. (defun mh-pick-construct-regexp (expr component)
  1113. "Construct pick compatible expression corresponding to EXPR.
  1114. COMPONENT is the component to search."
  1115. (cond ((atom expr) (list component expr))
  1116. ((eq (car expr) 'and)
  1117. `("-lbrace" ,@(mh-pick-construct-regexp (cadr expr) component) "-and"
  1118. ,@(mh-pick-construct-regexp (caddr expr) component) "-rbrace"))
  1119. ((eq (car expr) 'or)
  1120. `("-lbrace" ,@(mh-pick-construct-regexp (cadr expr) component) "-or"
  1121. ,@(mh-pick-construct-regexp (caddr expr) component) "-rbrace"))
  1122. ((eq (car expr) 'not)
  1123. `("-lbrace" "-not" ,@(mh-pick-construct-regexp (cadr expr) component)
  1124. "-rbrace"))
  1125. (t (error "Unknown operator %s seen" (car expr)))))
  1126. ;;; Grep
  1127. (defvar mh-grep-binary (executable-find "grep"))
  1128. (defun mh-grep-execute-search (folder-path search-regexp)
  1129. "Execute grep.
  1130. Unlike the other search methods, this method does not use the
  1131. MH-Search buffer. Instead, you simply enter a regular expression
  1132. in the minibuffer. For help in constructing regular expressions,
  1133. see your man page for \"grep\".
  1134. In a program, FOLDER-PATH is the directory in which SEARCH-REGEXP
  1135. is used to search."
  1136. (set-buffer (get-buffer-create mh-temp-index-buffer))
  1137. (erase-buffer)
  1138. (call-process mh-grep-binary nil '(t nil) nil
  1139. "-i" "-r" search-regexp folder-path)
  1140. (goto-char (point-min)))
  1141. (defun mh-grep-next-result ()
  1142. "Read the next result.
  1143. Parse it and return the message folder, message index and the
  1144. match. If no other matches left then return nil. If the current
  1145. record is invalid return 'error."
  1146. (prog1
  1147. (block nil
  1148. (when (eobp)
  1149. (return nil))
  1150. (let ((eol-pos (mh-line-end-position))
  1151. (bol-pos (mh-line-beginning-position))
  1152. folder-start msg-end)
  1153. (goto-char bol-pos)
  1154. (unless (search-forward mh-user-path eol-pos t)
  1155. (return 'error))
  1156. (setq folder-start (point))
  1157. (unless (search-forward ":" eol-pos t)
  1158. (return 'error))
  1159. (let ((match (buffer-substring-no-properties (point) eol-pos)))
  1160. (forward-char -1)
  1161. (setq msg-end (point))
  1162. (unless (search-backward "/" folder-start t)
  1163. (return 'error))
  1164. (list (format "+%s" (buffer-substring-no-properties
  1165. folder-start (point)))
  1166. (let ((n (ignore-errors (string-to-number
  1167. (buffer-substring-no-properties
  1168. (1+ (point)) msg-end)))))
  1169. (if n n (return 'error)))
  1170. match))))
  1171. (forward-line)))
  1172. ;;; Folder Utilities
  1173. ;;;###mh-autoload
  1174. (defun mh-index-group-by-folder ()
  1175. "Partition the messages based on source folder.
  1176. Returns an alist with the folder names in the car and the cdr
  1177. being the list of messages originally from that folder."
  1178. (save-excursion
  1179. (goto-char (point-min))
  1180. (let ((result-table (make-hash-table :test #'equal)))
  1181. (loop for msg being the hash-keys of mh-index-msg-checksum-map
  1182. do (push msg (gethash (car (gethash
  1183. (gethash msg mh-index-msg-checksum-map)
  1184. mh-index-checksum-origin-map))
  1185. result-table)))
  1186. (loop for x being the hash-keys of result-table
  1187. collect (cons x (nreverse (gethash x result-table)))))))
  1188. ;;;###mh-autoload
  1189. (defun mh-index-insert-folder-headers ()
  1190. "Annotate the search results with original folder names."
  1191. (let ((cur-msg (mh-get-msg-num nil))
  1192. (old-buffer-modified-flag (buffer-modified-p))
  1193. (buffer-read-only nil)
  1194. current-folder last-folder)
  1195. (goto-char (point-min))
  1196. (while (not (eobp))
  1197. (setq current-folder (car (gethash (gethash (mh-get-msg-num nil)
  1198. mh-index-msg-checksum-map)
  1199. mh-index-checksum-origin-map)))
  1200. (when (and current-folder (not (equal current-folder last-folder)))
  1201. (insert (if last-folder "\n" "") current-folder "\n")
  1202. (setq last-folder current-folder))
  1203. (forward-line))
  1204. (when cur-msg
  1205. (mh-notate-cur)
  1206. (mh-goto-msg cur-msg t))
  1207. (set-buffer-modified-p old-buffer-modified-flag))
  1208. (mh-index-create-imenu-index))
  1209. ;;;###mh-autoload
  1210. (defun mh-index-delete-folder-headers ()
  1211. "Delete the folder headers."
  1212. (let ((cur-msg (mh-get-msg-num nil))
  1213. (old-buffer-modified-flag (buffer-modified-p))
  1214. (buffer-read-only nil))
  1215. (while (and (not cur-msg) (not (eobp)))
  1216. (forward-line)
  1217. (setq cur-msg (mh-get-msg-num nil)))
  1218. (goto-char (point-min))
  1219. (while (not (eobp))
  1220. (if (or (char-equal (char-after) ?+) (char-equal (char-after) 10))
  1221. (delete-region (point) (progn (forward-line) (point)))
  1222. (forward-line)))
  1223. (when cur-msg (mh-goto-msg cur-msg t t))
  1224. (set-buffer-modified-p old-buffer-modified-flag)))
  1225. (mh-require 'which-func nil t)
  1226. ;; Shush compiler.
  1227. (defvar which-func-mode) ; < Emacs 22, XEmacs
  1228. ;;;###mh-autoload
  1229. (defun mh-index-create-imenu-index ()
  1230. "Create alist of folder names and positions in index folder buffers."
  1231. (save-excursion
  1232. (if (boundp 'which-func-mode)
  1233. (setq which-func-mode t))
  1234. (let ((alist ()))
  1235. (goto-char (point-min))
  1236. (while (re-search-forward "^+" nil t)
  1237. (save-excursion
  1238. (beginning-of-line)
  1239. (push (cons (buffer-substring-no-properties
  1240. (point) (mh-line-end-position))
  1241. (set-marker (make-marker) (point)))
  1242. alist)))
  1243. (setq imenu--index-alist (nreverse alist)))))
  1244. ;;;###mh-autoload
  1245. (defun mh-search-p ()
  1246. "Non-nil means that this folder was generated by searching."
  1247. mh-index-data)
  1248. ;; Shush compiler
  1249. (mh-do-in-xemacs
  1250. (defvar mh-speed-flists-inhibit-flag))
  1251. ;;;###mh-autoload
  1252. (defun mh-index-execute-commands ()
  1253. "Delete/refile the actual messages.
  1254. The copies in the searched folder are then deleted/refiled to get
  1255. the desired result. Before deleting the messages we make sure
  1256. that the message being deleted is identical to the one that the
  1257. user has marked in the index buffer."
  1258. (save-excursion
  1259. (let ((folders ())
  1260. (mh-speed-flists-inhibit-flag t))
  1261. (maphash
  1262. (lambda (folder msgs)
  1263. (push folder folders)
  1264. (if (not (get-buffer folder))
  1265. ;; If source folder not open, just delete the messages...
  1266. (apply #'mh-exec-cmd "rmm" folder (mh-coalesce-msg-list msgs))
  1267. ;; Otherwise delete the messages in the source buffer...
  1268. (with-current-buffer folder
  1269. (let ((old-refile-list mh-refile-list)
  1270. (old-delete-list mh-delete-list))
  1271. (setq mh-refile-list nil
  1272. mh-delete-list msgs)
  1273. (unwind-protect (mh-execute-commands)
  1274. (setq mh-refile-list
  1275. (mapcar (lambda (x)
  1276. (cons (car x)
  1277. (loop for y in (cdr x)
  1278. unless (memq y msgs) collect y)))
  1279. old-refile-list)
  1280. mh-delete-list
  1281. (loop for x in old-delete-list
  1282. unless (memq x msgs) collect x))
  1283. (mh-set-folder-modified-p (mh-outstanding-commands-p))
  1284. (when (mh-outstanding-commands-p)
  1285. (mh-notate-deleted-and-refiled)))))))
  1286. (mh-index-matching-source-msgs (append (loop for x in mh-refile-list
  1287. append (cdr x))
  1288. mh-delete-list)
  1289. t))
  1290. folders)))
  1291. (defun mh-index-generate-pretty-name (string)
  1292. "Given STRING generate a name which is suitable for use as a folder name.
  1293. White space from the beginning and end are removed. All spaces in
  1294. the name are replaced with underscores and all / are replaced
  1295. with $. If STRING is longer than 20 it is truncated too. STRING
  1296. could be a list of strings in which case they are concatenated to
  1297. construct the base name."
  1298. (with-temp-buffer
  1299. (if (stringp string)
  1300. (insert string)
  1301. (when (car string) (insert (car string)))
  1302. (dolist (s (cdr string))
  1303. (insert "_" s)))
  1304. (setq string (mh-replace-string "-lbrace" " "))
  1305. (setq string (mh-replace-string "-rbrace" " "))
  1306. (setq string (mh-replace-string "-search" " "))
  1307. (subst-char-in-region (point-min) (point-max) ?( ? t)
  1308. (subst-char-in-region (point-min) (point-max) ?) ? t)
  1309. (subst-char-in-region (point-min) (point-max) ?- ? t)
  1310. (goto-char (point-min))
  1311. (while (and (not (eobp)) (memq (char-after) '(? ?\t ?\n ?\r ?_)))
  1312. (delete-char 1))
  1313. (goto-char (point-max))
  1314. (while (and (not (bobp)) (memq (char-before) '(? ?\t ?\n ?\r ?_)))
  1315. (delete-char -1))
  1316. (subst-char-in-region (point-min) (point-max) ? ?_ t)
  1317. (subst-char-in-region (point-min) (point-max) ?\t ?_ t)
  1318. (subst-char-in-region (point-min) (point-max) ?\n ?_ t)
  1319. (subst-char-in-region (point-min) (point-max) ?\r ?_ t)
  1320. (subst-char-in-region (point-min) (point-max) ?/ ?$ t)
  1321. (let ((out (truncate-string-to-width (buffer-string) 20)))
  1322. (cond ((eq mh-searcher 'flists)
  1323. (format "%s/%s" mh-flists-results-folder mh-flists-sequence))
  1324. ((equal out mh-flists-results-folder) (concat out "1"))
  1325. (t out)))))
  1326. (defun mh-folder-exists-p (folder)
  1327. "Check if FOLDER exists."
  1328. (and (mh-folder-name-p folder)
  1329. (save-excursion
  1330. (with-temp-buffer
  1331. (mh-exec-cmd-output "folder" nil "-fast" "-nocreate" folder)
  1332. (goto-char (point-min))
  1333. ;; Strip + from folder; use optional + in regexp.
  1334. (looking-at (format "+?%s" (substring folder 1)))))))
  1335. (defun mh-msg-exists-p (msg folder)
  1336. "Check if MSG exists in FOLDER."
  1337. (file-exists-p (format "%s%s/%s" mh-user-path (substring folder 1) msg)))
  1338. (defun mh-index-new-folder (name search-regexp)
  1339. "Return a folder name based on NAME for search results of SEARCH-REGEXP.
  1340. If folder NAME already exists and was generated for the same
  1341. SEARCH-REGEXP then it is reused.
  1342. Otherwise if the folder NAME was generated from a different
  1343. search then check if NAME-2 can be used. Otherwise try NAME-3.
  1344. This is repeated till we find a new folder name.
  1345. If the folder returned doesn't exist then it is created."
  1346. (unless (mh-folder-name-p name)
  1347. (error "The argument should be a valid MH folder name"))
  1348. (let ((chosen-name
  1349. (loop for i from 1
  1350. for candidate = (if (equal i 1) name (format "%s-%s" name i))
  1351. when (or (not (mh-folder-exists-p candidate))
  1352. (equal (mh-index-folder-search-regexp candidate)
  1353. search-regexp))
  1354. return candidate)))
  1355. ;; Do pending refiles/deletes...
  1356. (when (get-buffer chosen-name)
  1357. (mh-process-or-undo-commands chosen-name))
  1358. ;; Recreate folder...
  1359. (save-excursion (mh-exec-cmd-quiet nil "rmf" chosen-name))
  1360. (mh-exec-cmd-quiet nil "folder" "-create" "-fast" chosen-name)
  1361. (mh-remove-from-sub-folders-cache chosen-name)
  1362. (when (boundp 'mh-speed-folder-map)
  1363. (mh-speed-add-folder chosen-name))
  1364. chosen-name))
  1365. (defun mh-index-folder-search-regexp (folder)
  1366. "If FOLDER was created by a index search, return the search regexp.
  1367. Return nil if FOLDER doesn't exist or the .mhe_index file is
  1368. garbled."
  1369. (ignore-errors
  1370. (with-temp-buffer
  1371. (insert-file-contents
  1372. (format "%s%s/%s" mh-user-path (substring folder 1) mh-index-data-file))
  1373. (goto-char (point-min))
  1374. (forward-list 3)
  1375. (cadr (read (current-buffer))))))
  1376. ;;; Sequence Support
  1377. ;;;###mh-autoload
  1378. (defun mh-index-create-sequences ()
  1379. "Mirror sequences present in source folders in index folder."
  1380. (let ((seq-hash (make-hash-table :test #'equal))
  1381. (seq-list ()))
  1382. (loop for folder being the hash-keys of mh-index-data
  1383. do (setf (gethash folder seq-hash)
  1384. (mh-create-sequence-map
  1385. (mh-read-folder-sequences folder nil))))
  1386. (dolist (msg (mh-translate-range mh-current-folder "all"))
  1387. (let* ((checksum (gethash msg mh-index-msg-checksum-map))
  1388. (pair (gethash checksum mh-index-checksum-origin-map))
  1389. (ofolder (car pair))
  1390. (omsg (cdr pair)))
  1391. (loop for seq in (ignore-errors
  1392. (gethash omsg (gethash ofolder seq-hash)))
  1393. do (if (assoc seq seq-list)
  1394. (push msg (cdr (assoc seq seq-list)))
  1395. (push (list seq msg) seq-list)))))
  1396. (loop for seq in seq-list
  1397. do (apply #'mh-exec-cmd "mark" mh-current-folder
  1398. "-sequence" (symbol-name (car seq)) "-add"
  1399. (mapcar #'(lambda (x) (format "%s" x)) (cdr seq))))))
  1400. ;;;###mh-autoload
  1401. (defun mh-create-sequence-map (seq-list)
  1402. "Return a map from msg number to list of sequences in which it is present.
  1403. SEQ-LIST is an assoc list whose keys are sequence names and whose
  1404. cdr is the list of messages in that sequence."
  1405. (loop with map = (make-hash-table)
  1406. for seq in seq-list
  1407. when (and (not (memq (car seq) (mh-unpropagated-sequences)))
  1408. (mh-valid-seq-p (car seq)))
  1409. do (loop for msg in (cdr seq)
  1410. do (push (car seq) (gethash msg map)))
  1411. finally return map))
  1412. ;;;###mh-autoload
  1413. (defun mh-index-add-to-sequence (seq msgs)
  1414. "Add to SEQ the messages in the list MSGS.
  1415. This function updates the source folder sequences. Also makes an
  1416. attempt to update the source folder buffer if we have it open."
  1417. ;; Don't need to do anything for cur
  1418. (save-excursion
  1419. (when (and (not (memq seq (mh-unpropagated-sequences)))
  1420. (mh-valid-seq-p seq))
  1421. (let ((folders ())
  1422. (mh-speed-flists-inhibit-flag t))
  1423. (maphash (lambda (folder msgs)
  1424. (push folder folders)
  1425. ;; Add messages to sequence in source folder...
  1426. (apply #'mh-exec-cmd-quiet nil "mark" folder
  1427. "-add" "-nozero" "-sequence" (symbol-name seq)
  1428. (mapcar (lambda (x) (format "%s" x))
  1429. (mh-coalesce-msg-list msgs)))
  1430. ;; Update source folder buffer if we have it open...
  1431. (when (get-buffer folder)
  1432. (with-current-buffer folder
  1433. (mh-put-msg-in-seq msgs seq))))
  1434. (mh-index-matching-source-msgs msgs))
  1435. folders))))
  1436. ;;;###mh-autoload
  1437. (defun mh-index-delete-from-sequence (seq msgs)
  1438. "Delete from SEQ the messages in MSGS.
  1439. This function updates the source folder sequences. Also makes an
  1440. attempt to update the source folder buffer if present."
  1441. (save-excursion
  1442. (when (and (not (memq seq (mh-unpropagated-sequences)))
  1443. (mh-valid-seq-p seq))
  1444. (let ((folders ())
  1445. (mh-speed-flists-inhibit-flag t))
  1446. (maphash (lambda (folder msgs)
  1447. (push folder folders)
  1448. ;; Remove messages from sequence in source folder...
  1449. (apply #'mh-exec-cmd-quiet nil "mark" folder
  1450. "-del" "-nozero" "-sequence" (symbol-name seq)
  1451. (mapcar (lambda (x) (format "%s" x))
  1452. (mh-coalesce-msg-list msgs)))
  1453. ;; Update source folder buffer if we have it open...
  1454. (when (get-buffer folder)
  1455. (with-current-buffer folder
  1456. (mh-delete-msg-from-seq msgs seq t))))
  1457. (mh-index-matching-source-msgs msgs))
  1458. folders))))
  1459. (defvar mh-unpropagated-sequences '(cur range subject search)
  1460. "List of sequences that aren't preserved.")
  1461. (defun mh-unpropagated-sequences ()
  1462. "Return a list of sequences that aren't propagated to the source folders.
  1463. It is just the sequences in the variable
  1464. `mh-unpropagated-sequences' in addition to the
  1465. Previous-Sequence (see mh-profile 5)."
  1466. (if mh-previous-seq
  1467. (cons mh-previous-seq mh-unpropagated-sequences)
  1468. mh-unpropagated-sequences))
  1469. (defun mh-index-matching-source-msgs (msgs &optional delete-from-index-data)
  1470. "Return a table of original messages and folders for messages in MSGS.
  1471. If optional argument DELETE-FROM-INDEX-DATA is non-nil, then each
  1472. of the messages, whose counter-part is found in some source
  1473. folder, is removed from `mh-index-data'."
  1474. (let ((table (make-hash-table :test #'equal)))
  1475. (dolist (msg msgs)
  1476. (let* ((checksum (gethash msg mh-index-msg-checksum-map))
  1477. (pair (gethash checksum mh-index-checksum-origin-map)))
  1478. (when (and checksum (car pair) (cdr pair)
  1479. (mh-index-match-checksum (cdr pair) (car pair) checksum))
  1480. (push (cdr pair) (gethash (car pair) table))
  1481. (when delete-from-index-data
  1482. (remhash (cdr pair) (gethash (car pair) mh-index-data))))))
  1483. table))
  1484. (defun mh-index-match-checksum (msg folder checksum)
  1485. "Check if MSG in FOLDER has X-MHE-Checksum header value of CHECKSUM."
  1486. (with-temp-buffer
  1487. (mh-exec-cmd-output mh-scan-prog nil "-width" "80"
  1488. "-format" "%{x-mhe-checksum}\n" folder msg)
  1489. (goto-char (point-min))
  1490. (string-equal (buffer-substring-no-properties
  1491. (point) (mh-line-end-position))
  1492. checksum)))
  1493. ;;; Serialization of Index Data
  1494. (defun mh-index-write-data ()
  1495. "Write index data to file."
  1496. (ignore-errors
  1497. (unless (eq major-mode 'mh-folder-mode)
  1498. (error "Can't be called from folder in \"%s\"" major-mode))
  1499. (let ((data mh-index-data)
  1500. (msg-checksum-map mh-index-msg-checksum-map)
  1501. (checksum-origin-map mh-index-checksum-origin-map)
  1502. (previous-search mh-index-previous-search)
  1503. (sequence-search-flag mh-index-sequence-search-flag)
  1504. (outfile (concat buffer-file-name mh-index-data-file))
  1505. (print-length nil)
  1506. (print-level nil))
  1507. (with-temp-file outfile
  1508. (mh-index-write-hashtable
  1509. data (lambda (x) (loop for y being the hash-keys of x collect y)))
  1510. (mh-index-write-hashtable msg-checksum-map #'identity)
  1511. (mh-index-write-hashtable checksum-origin-map #'identity)
  1512. (pp previous-search (current-buffer)) (insert "\n")
  1513. (pp sequence-search-flag (current-buffer)) (insert "\n")))))
  1514. (defun mh-index-write-hashtable (table proc)
  1515. "Write TABLE to `current-buffer'.
  1516. PROC is used to serialize the values corresponding to the hash
  1517. table keys."
  1518. (pp (loop for x being the hash-keys of table
  1519. collect (cons x (funcall proc (gethash x table))))
  1520. (current-buffer))
  1521. (insert "\n"))
  1522. ;;;###mh-autoload
  1523. (defun mh-index-read-data ()
  1524. "Read index data from file."
  1525. (ignore-errors
  1526. (unless (eq major-mode 'mh-folder-mode)
  1527. (error "Can't be called from folder in \"%s\"" major-mode))
  1528. (let ((infile (concat buffer-file-name mh-index-data-file))
  1529. t1 t2 t3 t4 t5)
  1530. (with-temp-buffer
  1531. (insert-file-contents-literally infile)
  1532. (goto-char (point-min))
  1533. (setq t1 (mh-index-read-hashtable
  1534. (lambda (data)
  1535. (loop with table = (make-hash-table :test #'equal)
  1536. for x in data do (setf (gethash x table) t)
  1537. finally return table)))
  1538. t2 (mh-index-read-hashtable #'identity)
  1539. t3 (mh-index-read-hashtable #'identity)
  1540. t4 (read (current-buffer))
  1541. t5 (read (current-buffer))))
  1542. (setq mh-index-data t1
  1543. mh-index-msg-checksum-map t2
  1544. mh-index-checksum-origin-map t3
  1545. mh-index-previous-search t4
  1546. mh-index-sequence-search-flag t5))))
  1547. (defun mh-index-read-hashtable (proc)
  1548. "From BUFFER read a hash table serialized as a list.
  1549. PROC is used to convert the value to actual data."
  1550. (loop with table = (make-hash-table :test #'equal)
  1551. for pair in (read (current-buffer))
  1552. do (setf (gethash (car pair) table) (funcall proc (cdr pair)))
  1553. finally return table))
  1554. ;;; Checksum Routines
  1555. ;; A few different checksum programs are supported. The supported
  1556. ;; programs are:
  1557. ;; 1. md5sum
  1558. ;; 2. md5
  1559. ;; 3. openssl
  1560. ;; To add support for your favorite checksum program add a clause to
  1561. ;; the cond statement in mh-checksum-choose. This should set the
  1562. ;; variable mh-checksum-cmd to the command line needed to run the
  1563. ;; checksum program and should set mh-checksum-parser to a function
  1564. ;; which returns a cons cell containing the message number and
  1565. ;; checksum string.
  1566. (defvar mh-checksum-cmd)
  1567. (defvar mh-checksum-parser)
  1568. (defun mh-checksum-choose ()
  1569. "Check if a program to create a checksum is present."
  1570. (unless (boundp 'mh-checksum-cmd)
  1571. (let ((exec-path (append '("/sbin" "/usr/sbin") exec-path)))
  1572. (cond ((executable-find "md5sum")
  1573. (setq mh-checksum-cmd (list (executable-find "md5sum")))
  1574. (setq mh-checksum-parser #'mh-md5sum-parser))
  1575. ((executable-find "openssl")
  1576. (setq mh-checksum-cmd (list (executable-find "openssl") "md5"))
  1577. (setq mh-checksum-parser #'mh-openssl-parser))
  1578. ((executable-find "md5")
  1579. (setq mh-checksum-cmd (list (executable-find "md5")))
  1580. (setq mh-checksum-parser #'mh-md5-parser))
  1581. (t (error "No suitable checksum program"))))))
  1582. (defun mh-md5sum-parser ()
  1583. "Parse md5sum output."
  1584. (let ((begin (mh-line-beginning-position))
  1585. (end (mh-line-end-position))
  1586. first-space last-slash)
  1587. (setq first-space (search-forward " " end t))
  1588. (goto-char end)
  1589. (setq last-slash (search-backward "/" begin t))
  1590. (cond ((and first-space last-slash)
  1591. (cons (string-to-number (buffer-substring-no-properties
  1592. (1+ last-slash) end))
  1593. (buffer-substring-no-properties begin (1- first-space))))
  1594. (t (cons nil nil)))))
  1595. (defun mh-openssl-parser ()
  1596. "Parse openssl output."
  1597. (let ((begin (mh-line-beginning-position))
  1598. (end (mh-line-end-position))
  1599. last-space last-slash)
  1600. (goto-char end)
  1601. (setq last-space (search-backward " " begin t))
  1602. (setq last-slash (search-backward "/" begin t))
  1603. (cond ((and last-slash last-space)
  1604. (cons (string-to-number (buffer-substring-no-properties
  1605. (1+ last-slash) (1- last-space)))
  1606. (buffer-substring-no-properties (1+ last-space) end))))))
  1607. (defalias 'mh-md5-parser 'mh-openssl-parser)
  1608. ;;;###mh-autoload
  1609. (defun mh-index-update-maps (folder &optional origin-map)
  1610. "Annotate all as yet unannotated messages in FOLDER with their MD5 hash.
  1611. As a side effect msg -> checksum map is updated. Optional
  1612. argument ORIGIN-MAP is a hashtable which maps each message in the
  1613. index folder to the original folder and message from whence it
  1614. was copied. If present the checksum -> (origin-folder,
  1615. origin-index) map is updated too."
  1616. (clrhash mh-index-msg-checksum-map)
  1617. ;; Clear temp buffer
  1618. (with-current-buffer (get-buffer-create mh-temp-checksum-buffer)
  1619. (erase-buffer)
  1620. ;; Run scan to check if any messages needs MD5 annotations at all
  1621. (with-temp-buffer
  1622. (mh-exec-cmd-output mh-scan-prog nil "-width" "80"
  1623. "-format" "%(msg)\n%{x-mhe-checksum}\n"
  1624. folder "all")
  1625. (goto-char (point-min))
  1626. (let (msg checksum)
  1627. (while (not (eobp))
  1628. (setq msg (buffer-substring-no-properties
  1629. (point) (mh-line-end-position)))
  1630. (forward-line)
  1631. (save-excursion
  1632. (cond ((not (string-match "^[0-9]*$" msg)))
  1633. ((eolp)
  1634. ;; need to compute checksum
  1635. (set-buffer mh-temp-checksum-buffer)
  1636. (insert mh-user-path (substring folder 1) "/" msg "\n"))
  1637. (t
  1638. ;; update maps
  1639. (setq checksum (buffer-substring-no-properties
  1640. (point) (mh-line-end-position)))
  1641. (let ((msg (string-to-number msg)))
  1642. (set-buffer folder)
  1643. (mh-index-update-single-msg msg checksum origin-map)))))
  1644. (forward-line))))
  1645. ;; Run checksum program if needed
  1646. (unless (and (eobp) (bobp))
  1647. (apply #'mh-xargs mh-checksum-cmd)
  1648. (goto-char (point-min))
  1649. (while (not (eobp))
  1650. (let* ((intermediate (funcall mh-checksum-parser))
  1651. (msg (car intermediate))
  1652. (checksum (cdr intermediate)))
  1653. (when msg
  1654. ;; annotate
  1655. (mh-exec-cmd "anno" folder msg "-component" "X-MHE-Checksum"
  1656. "-nodate" "-text" checksum "-inplace")
  1657. ;; update maps
  1658. (with-current-buffer folder
  1659. (mh-index-update-single-msg msg checksum origin-map)))
  1660. (forward-line)))))
  1661. (mh-index-write-data))
  1662. (defun mh-index-update-single-msg (msg checksum origin-map)
  1663. "Update various maps for one message.
  1664. MSG is a index folder message, CHECKSUM its MD5 hash and
  1665. ORIGIN-MAP, if non-nil, a hashtable containing which maps each
  1666. message in the index folder to the folder and message that it was
  1667. copied from. The function updates the hash tables
  1668. `mh-index-msg-checksum-map' and `mh-index-checksum-origin-map'.
  1669. This function should only be called in the appropriate index
  1670. folder buffer."
  1671. (cond ((gethash checksum mh-index-checksum-origin-map)
  1672. (when origin-map
  1673. (let* ((intermediate (gethash msg origin-map))
  1674. (ofolder (car intermediate))
  1675. (omsg (cdr intermediate)))
  1676. ;; This is most probably a duplicate. So eliminate it.
  1677. (call-process "rm" nil nil nil
  1678. (format "%s%s/%s" mh-user-path
  1679. (substring mh-current-folder 1) msg))
  1680. (when (gethash ofolder mh-index-data)
  1681. (remhash omsg (gethash ofolder mh-index-data))))))
  1682. (t
  1683. (setf (gethash msg mh-index-msg-checksum-map) checksum)
  1684. (when (and origin-map (gethash msg origin-map))
  1685. (setf (gethash checksum mh-index-checksum-origin-map)
  1686. (gethash msg origin-map))))))
  1687. (provide 'mh-search)
  1688. ;; Local Variables:
  1689. ;; indent-tabs-mode: nil
  1690. ;; sentence-end-double-space: nil
  1691. ;; End:
  1692. ;;; mh-search ends here