gnus-registry.el 48 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214
  1. ;;; gnus-registry.el --- article registry for Gnus
  2. ;; Copyright (C) 2002-2012 Free Software Foundation, Inc.
  3. ;; Author: Ted Zlatanov <tzz@lifelogs.com>
  4. ;; Keywords: news registry
  5. ;; This file is part of GNU Emacs.
  6. ;; GNU Emacs is free software: you can redistribute it and/or modify
  7. ;; it under the terms of the GNU General Public License as published by
  8. ;; the Free Software Foundation, either version 3 of the License, or
  9. ;; (at your option) any later version.
  10. ;; GNU Emacs is distributed in the hope that it will be useful,
  11. ;; but WITHOUT ANY WARRANTY; without even the implied warranty of
  12. ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  13. ;; GNU General Public License for more details.
  14. ;; You should have received a copy of the GNU General Public License
  15. ;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>.
  16. ;;; Commentary:
  17. ;; This is the gnus-registry.el package, which works with all
  18. ;; Gnus backends, not just nnmail. The major issue is that it
  19. ;; doesn't go across backends, so for instance if an article is in
  20. ;; nnml:sys and you see a reference to it in nnimap splitting, the
  21. ;; article will end up in nnimap:sys
  22. ;; gnus-registry.el intercepts article respooling, moving, deleting,
  23. ;; and copying for all backends. If it doesn't work correctly for
  24. ;; you, submit a bug report and I'll be glad to fix it. It needs
  25. ;; better documentation in the manual (also on my to-do list).
  26. ;; If you want to track recipients (and you should to make the
  27. ;; gnus-registry splitting work better), you need the To and Cc
  28. ;; headers collected by Gnus. Note that in more recent Gnus versions
  29. ;; this is already the case: look at `gnus-extra-headers' to be sure.
  30. ;; ;;; you may also want Gcc Newsgroups Keywords X-Face
  31. ;; (add-to-list 'gnus-extra-headers 'To)
  32. ;; (add-to-list 'gnus-extra-headers 'Cc)
  33. ;; (setq nnmail-extra-headers gnus-extra-headers)
  34. ;; Put this in your startup file (~/.gnus.el for instance) or use Customize:
  35. ;; (setq gnus-registry-max-entries 2500
  36. ;; gnus-registry-track-extra '(sender subject recipient))
  37. ;; (gnus-registry-initialize)
  38. ;; Then use this in your fancy-split:
  39. ;; (: gnus-registry-split-fancy-with-parent)
  40. ;; You should also consider using the nnregistry backend to look up
  41. ;; articles. See the Gnus manual for more information.
  42. ;; Finally, you can put %uM in your summary line format to show the
  43. ;; registry marks if you do this:
  44. ;; show the marks as single characters (see the :char property in
  45. ;; `gnus-registry-marks'):
  46. ;; (defalias 'gnus-user-format-function-M 'gnus-registry-article-marks-to-chars)
  47. ;; show the marks by name (see `gnus-registry-marks'):
  48. ;; (defalias 'gnus-user-format-function-M 'gnus-registry-article-marks-to-names)
  49. ;; TODO:
  50. ;; - get the correct group on spool actions
  51. ;; - articles that are spooled to a different backend should be moved
  52. ;; after splitting
  53. ;;; Code:
  54. (eval-when-compile (require 'cl))
  55. (eval-when-compile
  56. (when (null (ignore-errors (require 'ert)))
  57. (defmacro* ert-deftest (name () &body docstring-keys-and-body))))
  58. (ignore-errors
  59. (require 'ert))
  60. (require 'gnus)
  61. (require 'gnus-int)
  62. (require 'gnus-sum)
  63. (require 'gnus-art)
  64. (require 'gnus-util)
  65. (require 'nnmail)
  66. (require 'easymenu)
  67. (require 'registry)
  68. (defvar gnus-adaptive-word-syntax-table)
  69. (defvar gnus-registry-dirty t
  70. "Boolean set to t when the registry is modified")
  71. (defgroup gnus-registry nil
  72. "The Gnus registry."
  73. :version "22.1"
  74. :group 'gnus)
  75. (defvar gnus-registry-marks
  76. '((Important
  77. :char ?i
  78. :image "summary_important")
  79. (Work
  80. :char ?w
  81. :image "summary_work")
  82. (Personal
  83. :char ?p
  84. :image "summary_personal")
  85. (To-Do
  86. :char ?t
  87. :image "summary_todo")
  88. (Later
  89. :char ?l
  90. :image "summary_later"))
  91. "List of registry marks and their options.
  92. `gnus-registry-mark-article' will offer symbols from this list
  93. for completion.
  94. Each entry must have a character to be useful for summary mode
  95. line display and for keyboard shortcuts.
  96. Each entry must have an image string to be useful for visual
  97. display.")
  98. (defcustom gnus-registry-default-mark 'To-Do
  99. "The default mark. Should be a valid key for `gnus-registry-marks'."
  100. :group 'gnus-registry
  101. :type 'symbol)
  102. (defcustom gnus-registry-unfollowed-addresses
  103. (list (regexp-quote user-mail-address))
  104. "List of addresses that gnus-registry-split-fancy-with-parent won't trace.
  105. The addresses are matched, they don't have to be fully qualified.
  106. In the messages, these addresses can be the sender or the
  107. recipients."
  108. :version "24.1"
  109. :group 'gnus-registry
  110. :type '(repeat regexp))
  111. (defcustom gnus-registry-unfollowed-groups
  112. '("delayed$" "drafts$" "queue$" "INBOX$" "^nnmairix:" "archive")
  113. "List of groups that gnus-registry-split-fancy-with-parent won't return.
  114. The group names are matched, they don't have to be fully
  115. qualified. This parameter tells the Gnus registry 'never split a
  116. message into a group that matches one of these, regardless of
  117. references.'
  118. nnmairix groups are specifically excluded because they are ephemeral."
  119. :group 'gnus-registry
  120. :type '(repeat regexp))
  121. (defcustom gnus-registry-install 'ask
  122. "Whether the registry should be installed."
  123. :group 'gnus-registry
  124. :type '(choice (const :tag "Never Install" nil)
  125. (const :tag "Always Install" t)
  126. (const :tag "Ask Me" ask)))
  127. (defvar gnus-registry-enabled nil)
  128. (defvar gnus-summary-misc-menu) ;; Avoid byte compiler warning.
  129. (defvar gnus-registry-misc-menus nil) ; ugly way to keep the menus
  130. (make-obsolete-variable 'gnus-registry-clean-empty nil "23.4")
  131. (make-obsolete-variable 'gnus-registry-use-long-group-names nil "23.4")
  132. (make-obsolete-variable 'gnus-registry-max-track-groups nil "23.4")
  133. (make-obsolete-variable 'gnus-registry-entry-caching nil "23.4")
  134. (make-obsolete-variable 'gnus-registry-trim-articles-without-groups nil "23.4")
  135. (defcustom gnus-registry-track-extra '(subject sender recipient)
  136. "Whether the registry should track extra data about a message.
  137. The subject, recipients (To: and Cc:), and Sender (From:) headers
  138. are tracked this way by default."
  139. :group 'gnus-registry
  140. :type
  141. '(set :tag "Tracking choices"
  142. (const :tag "Track by subject (Subject: header)" subject)
  143. (const :tag "Track by recipient (To: and Cc: headers)" recipient)
  144. (const :tag "Track by sender (From: header)" sender)))
  145. (defcustom gnus-registry-split-strategy nil
  146. "The splitting strategy applied to the keys in `gnus-registry-track-extra'.
  147. Given a set of unique found groups G and counts for each element
  148. of G, and a key K (typically 'sender or 'subject):
  149. When nil, if G has only one element, use it. Otherwise give up.
  150. This is the fastest but also least useful strategy.
  151. When 'majority, use the majority by count. So if there is a
  152. group with the most articles counted by K, use that. Ties are
  153. resolved in no particular order, simply the first one found wins.
  154. This is the slowest strategy but also the most accurate one.
  155. When 'first, the first element of G wins. This is fast and
  156. should be OK if your senders and subjects don't \"bleed\" across
  157. groups."
  158. :group 'gnus-registry
  159. :type
  160. '(choice :tag "Splitting strategy"
  161. (const :tag "Only use single choices, discard multiple matches" nil)
  162. (const :tag "Majority of matches wins" majority)
  163. (const :tag "First found wins" first)))
  164. (defcustom gnus-registry-minimum-subject-length 5
  165. "The minimum length of a subject before it's considered trackable."
  166. :group 'gnus-registry
  167. :type 'integer)
  168. (defcustom gnus-registry-extra-entries-precious '(mark)
  169. "What extra keys are precious, meaning entries with them won't get pruned.
  170. By default, 'mark is included, so articles with marks are
  171. considered precious.
  172. Before you save the Gnus registry, it's pruned. Any entries with
  173. keys in this list will not be pruned. All other entries go to
  174. the Bit Bucket."
  175. :group 'gnus-registry
  176. :type '(repeat symbol))
  177. (defcustom gnus-registry-cache-file
  178. (nnheader-concat
  179. (or gnus-dribble-directory gnus-home-directory "~/")
  180. ".gnus.registry.eioio")
  181. "File where the Gnus registry will be stored."
  182. :group 'gnus-registry
  183. :type 'file)
  184. (defcustom gnus-registry-max-entries nil
  185. "Maximum number of entries in the registry, nil for unlimited."
  186. :group 'gnus-registry
  187. :type '(radio (const :format "Unlimited " nil)
  188. (integer :format "Maximum number: %v")))
  189. (defcustom gnus-registry-max-pruned-entries nil
  190. "Maximum number of pruned entries in the registry, nil for unlimited."
  191. :version "24.1"
  192. :group 'gnus-registry
  193. :type '(radio (const :format "Unlimited " nil)
  194. (integer :format "Maximum number: %v")))
  195. (defun gnus-registry-fixup-registry (db)
  196. (when db
  197. (let ((old (oref db :tracked)))
  198. (oset db :precious
  199. (append gnus-registry-extra-entries-precious
  200. '()))
  201. (oset db :max-hard
  202. (or gnus-registry-max-entries
  203. most-positive-fixnum))
  204. (oset db :prune-factor
  205. 0.1)
  206. (oset db :max-soft
  207. (or gnus-registry-max-pruned-entries
  208. most-positive-fixnum))
  209. (oset db :tracked
  210. (append gnus-registry-track-extra
  211. '(mark group keyword)))
  212. (when (not (equal old (oref db :tracked)))
  213. (gnus-message 4 "Reindexing the Gnus registry (tracked change)")
  214. (registry-reindex db))))
  215. db)
  216. (defun gnus-registry-make-db (&optional file)
  217. (interactive "fGnus registry persistence file: \n")
  218. (gnus-registry-fixup-registry
  219. (registry-db
  220. "Gnus Registry"
  221. :file (or file gnus-registry-cache-file)
  222. ;; these parameters are set in `gnus-registry-fixup-registry'
  223. :max-hard most-positive-fixnum
  224. :max-soft most-positive-fixnum
  225. :precious nil
  226. :tracked nil)))
  227. (defvar gnus-registry-db (gnus-registry-make-db)
  228. "*The article registry by Message ID. See `registry-db'")
  229. ;; top-level registry data management
  230. (defun gnus-registry-remake-db (&optional forsure)
  231. "Remake the registry database after customization.
  232. This is not required after changing `gnus-registry-cache-file'."
  233. (interactive (list (y-or-n-p "Remake and CLEAR the Gnus registry? ")))
  234. (when forsure
  235. (gnus-message 4 "Remaking the Gnus registry")
  236. (setq gnus-registry-db (gnus-registry-make-db))))
  237. (defun gnus-registry-read ()
  238. "Read the registry cache file."
  239. (interactive)
  240. (let ((file gnus-registry-cache-file))
  241. (condition-case nil
  242. (progn
  243. (gnus-message 5 "Reading Gnus registry from %s..." file)
  244. (setq gnus-registry-db (gnus-registry-fixup-registry
  245. (eieio-persistent-read file)))
  246. (gnus-message 5 "Reading Gnus registry from %s...done" file))
  247. (error
  248. (gnus-message
  249. 1
  250. "The Gnus registry could not be loaded from %s, creating a new one"
  251. file)
  252. (gnus-registry-remake-db t)))))
  253. (defun gnus-registry-save (&optional file db)
  254. "Save the registry cache file."
  255. (interactive)
  256. (let ((file (or file gnus-registry-cache-file))
  257. (db (or db gnus-registry-db)))
  258. (gnus-message 5 "Saving Gnus registry (%d entries) to %s..."
  259. (registry-size db) file)
  260. (registry-prune db)
  261. ;; TODO: call (gnus-string-remove-all-properties v) on all elements?
  262. (eieio-persistent-save db file)
  263. (gnus-message 5 "Saving Gnus registry (size %d) to %s...done"
  264. (registry-size db) file)))
  265. (defun gnus-registry-remove-ignored ()
  266. (interactive)
  267. (let* ((db gnus-registry-db)
  268. (grouphashtb (registry-lookup-secondary db 'group))
  269. (old-size (registry-size db)))
  270. (registry-reindex db)
  271. (loop for k being the hash-keys of grouphashtb
  272. using (hash-values v)
  273. when (gnus-registry-ignore-group-p k)
  274. do (registry-delete db v nil))
  275. (registry-reindex db)
  276. (gnus-message 4 "Removed %d ignored entries from the Gnus registry"
  277. (- old-size (registry-size db)))))
  278. ;; article move/copy/spool/delete actions
  279. (defun gnus-registry-action (action data-header from &optional to method)
  280. (let* ((id (mail-header-id data-header))
  281. (subject (mail-header-subject data-header))
  282. (extra (mail-header-extra data-header))
  283. (recipients (gnus-registry-sort-addresses
  284. (or (cdr-safe (assq 'Cc extra)) "")
  285. (or (cdr-safe (assq 'To extra)) "")))
  286. (sender (nth 0 (gnus-registry-extract-addresses
  287. (mail-header-from data-header))))
  288. (from (gnus-group-guess-full-name-from-command-method from))
  289. (to (if to (gnus-group-guess-full-name-from-command-method to) nil))
  290. (to-name (if to to "the Bit Bucket")))
  291. (gnus-message 7 "Gnus registry: article %s %s from %s to %s"
  292. id (if method "respooling" "going") from to)
  293. (gnus-registry-handle-action
  294. id
  295. ;; unless copying, remove the old "from" group
  296. (if (not (equal 'copy action)) from nil)
  297. to subject sender recipients)))
  298. (defun gnus-registry-spool-action (id group &optional subject sender recipients)
  299. (let ((to (gnus-group-guess-full-name-from-command-method group))
  300. (recipients (or recipients
  301. (gnus-registry-sort-addresses
  302. (or (message-fetch-field "cc") "")
  303. (or (message-fetch-field "to") ""))))
  304. (subject (or subject (message-fetch-field "subject")))
  305. (sender (or sender (message-fetch-field "from"))))
  306. (when (and (stringp id) (string-match "\r$" id))
  307. (setq id (substring id 0 -1)))
  308. (gnus-message 7 "Gnus registry: article %s spooled to %s"
  309. id
  310. to)
  311. (gnus-registry-handle-action id nil to subject sender recipients)))
  312. (defun gnus-registry-handle-action (id from to subject sender
  313. &optional recipients)
  314. (gnus-message
  315. 10
  316. "gnus-registry-handle-action %S" (list id from to subject sender recipients))
  317. (let ((db gnus-registry-db)
  318. ;; if the group is ignored, set the destination to nil (same as delete)
  319. (to (if (gnus-registry-ignore-group-p to) nil to))
  320. ;; safe if not found
  321. (entry (gnus-registry-get-or-make-entry id))
  322. (subject (gnus-string-remove-all-properties
  323. (gnus-registry-simplify-subject subject)))
  324. (sender (gnus-string-remove-all-properties sender)))
  325. ;; this could be done by calling `gnus-registry-set-id-key'
  326. ;; several times but it's better to bunch the transactions
  327. ;; together
  328. (registry-delete db (list id) nil)
  329. (when from
  330. (setq entry (cons (delete from (assoc 'group entry))
  331. (assq-delete-all 'group entry))))
  332. (dolist (kv `((group ,to)
  333. (sender ,sender)
  334. (recipient ,@recipients)
  335. (subject ,subject)))
  336. (when (second kv)
  337. (let ((new (or (assq (first kv) entry)
  338. (list (first kv)))))
  339. (dolist (toadd (cdr kv))
  340. (add-to-list 'new toadd t))
  341. (setq entry (cons new
  342. (assq-delete-all (first kv) entry))))))
  343. (gnus-message 10 "Gnus registry: new entry for %s is %S"
  344. id
  345. entry)
  346. (gnus-registry-insert db id entry)))
  347. ;; Function for nn{mail|imap}-split-fancy: look up all references in
  348. ;; the cache and if a match is found, return that group.
  349. (defun gnus-registry-split-fancy-with-parent ()
  350. "Split this message into the same group as its parent. The parent
  351. is obtained from the registry. This function can be used as an entry
  352. in `nnmail-split-fancy' or `nnimap-split-fancy', for example like
  353. this: (: gnus-registry-split-fancy-with-parent)
  354. This function tracks ALL backends, unlike
  355. `nnmail-split-fancy-with-parent' which tracks only nnmail
  356. messages.
  357. For a message to be split, it looks for the parent message in the
  358. References or In-Reply-To header and then looks in the registry
  359. to see which group that message was put in. This group is
  360. returned, unless `gnus-registry-follow-group-p' return nil for
  361. that group.
  362. See the Info node `(gnus)Fancy Mail Splitting' for more details."
  363. (let* ((refstr (or (message-fetch-field "references") "")) ; guaranteed
  364. (reply-to (message-fetch-field "in-reply-to")) ; may be nil
  365. ;; now, if reply-to is valid, append it to the References
  366. (refstr (if reply-to
  367. (concat refstr " " reply-to)
  368. refstr))
  369. (references (and refstr (gnus-extract-references refstr)))
  370. ;; these may not be used, but the code is cleaner having them up here
  371. (sender (gnus-string-remove-all-properties
  372. (message-fetch-field "from")))
  373. (recipients (gnus-registry-sort-addresses
  374. (or (message-fetch-field "cc") "")
  375. (or (message-fetch-field "to") "")))
  376. (subject (gnus-string-remove-all-properties
  377. (gnus-registry-simplify-subject
  378. (message-fetch-field "subject"))))
  379. (nnmail-split-fancy-with-parent-ignore-groups
  380. (if (listp nnmail-split-fancy-with-parent-ignore-groups)
  381. nnmail-split-fancy-with-parent-ignore-groups
  382. (list nnmail-split-fancy-with-parent-ignore-groups))))
  383. (gnus-registry--split-fancy-with-parent-internal
  384. :references references
  385. :refstr refstr
  386. :sender sender
  387. :recipients recipients
  388. :subject subject
  389. :log-agent "Gnus registry fancy splitting with parent")))
  390. (defun* gnus-registry--split-fancy-with-parent-internal
  391. (&rest spec
  392. &key references refstr sender subject recipients log-agent
  393. &allow-other-keys)
  394. (gnus-message
  395. 10
  396. "gnus-registry--split-fancy-with-parent-internal %S" spec)
  397. (let ((db gnus-registry-db)
  398. found)
  399. ;; this is a big chain of statements. it uses
  400. ;; gnus-registry-post-process-groups to filter the results after
  401. ;; every step.
  402. ;; the references string must be valid and parse to valid references
  403. (when references
  404. (gnus-message
  405. 9
  406. "%s is tracing references %s"
  407. log-agent refstr)
  408. (dolist (reference (nreverse references))
  409. (gnus-message 9 "%s is looking up %s" log-agent reference)
  410. (loop for group in (gnus-registry-get-id-key reference 'group)
  411. when (gnus-registry-follow-group-p group)
  412. do
  413. (progn
  414. (gnus-message 7 "%s traced %s to %s" log-agent reference group)
  415. (push group found))))
  416. ;; filter the found groups and return them
  417. ;; the found groups are the full groups
  418. (setq found (gnus-registry-post-process-groups
  419. "references" refstr found)))
  420. ;; else: there were no matches, now try the extra tracking by subject
  421. (when (and (null found)
  422. (memq 'subject gnus-registry-track-extra)
  423. subject
  424. (< gnus-registry-minimum-subject-length (length subject)))
  425. (let ((groups (apply
  426. 'append
  427. (mapcar
  428. (lambda (reference)
  429. (gnus-registry-get-id-key reference 'group))
  430. (registry-lookup-secondary-value db 'subject subject)))))
  431. (setq found
  432. (loop for group in groups
  433. when (gnus-registry-follow-group-p group)
  434. do (gnus-message
  435. ;; warn more if gnus-registry-track-extra
  436. (if gnus-registry-track-extra 7 9)
  437. "%s (extra tracking) traced subject '%s' to %s"
  438. log-agent subject group)
  439. and collect group))
  440. ;; filter the found groups and return them
  441. ;; the found groups are NOT the full groups
  442. (setq found (gnus-registry-post-process-groups
  443. "subject" subject found))))
  444. ;; else: there were no matches, try the extra tracking by sender
  445. (when (and (null found)
  446. (memq 'sender gnus-registry-track-extra)
  447. sender
  448. (not (gnus-grep-in-list
  449. sender
  450. gnus-registry-unfollowed-addresses)))
  451. (let ((groups (apply
  452. 'append
  453. (mapcar
  454. (lambda (reference)
  455. (gnus-registry-get-id-key reference 'group))
  456. (registry-lookup-secondary-value db 'sender sender)))))
  457. (setq found
  458. (loop for group in groups
  459. when (gnus-registry-follow-group-p group)
  460. do (gnus-message
  461. ;; warn more if gnus-registry-track-extra
  462. (if gnus-registry-track-extra 7 9)
  463. "%s (extra tracking) traced sender '%s' to %s"
  464. log-agent sender group)
  465. and collect group)))
  466. ;; filter the found groups and return them
  467. ;; the found groups are NOT the full groups
  468. (setq found (gnus-registry-post-process-groups
  469. "sender" sender found)))
  470. ;; else: there were no matches, try the extra tracking by recipient
  471. (when (and (null found)
  472. (memq 'recipient gnus-registry-track-extra)
  473. recipients)
  474. (dolist (recp recipients)
  475. (when (and (null found)
  476. (not (gnus-grep-in-list
  477. recp
  478. gnus-registry-unfollowed-addresses)))
  479. (let ((groups (apply 'append
  480. (mapcar
  481. (lambda (reference)
  482. (gnus-registry-get-id-key reference 'group))
  483. (registry-lookup-secondary-value
  484. db 'recipient recp)))))
  485. (setq found
  486. (loop for group in groups
  487. when (gnus-registry-follow-group-p group)
  488. do (gnus-message
  489. ;; warn more if gnus-registry-track-extra
  490. (if gnus-registry-track-extra 7 9)
  491. "%s (extra tracking) traced recipient '%s' to %s"
  492. log-agent recp group)
  493. and collect group)))))
  494. ;; filter the found groups and return them
  495. ;; the found groups are NOT the full groups
  496. (setq found (gnus-registry-post-process-groups
  497. "recipients" (mapconcat 'identity recipients ", ") found)))
  498. ;; after the (cond) we extract the actual value safely
  499. (car-safe found)))
  500. (defun gnus-registry-post-process-groups (mode key groups)
  501. "Inspects GROUPS found by MODE for KEY to determine which ones to follow.
  502. MODE can be 'subject' or 'sender' for example. The KEY is the
  503. value by which MODE was searched.
  504. Transforms each group name to the equivalent short name.
  505. Checks if the current Gnus method (from `gnus-command-method' or
  506. from `gnus-newsgroup-name') is the same as the group's method.
  507. Foreign methods are not supported so they are rejected.
  508. Reduces the list to a single group, or complains if that's not
  509. possible. Uses `gnus-registry-split-strategy'."
  510. (let ((log-agent "gnus-registry-post-process-group")
  511. (desc (format "%d groups" (length groups)))
  512. out chosen)
  513. ;; the strategy can be nil, in which case chosen is nil
  514. (setq chosen
  515. (case gnus-registry-split-strategy
  516. ;; default, take only one-element lists into chosen
  517. ((nil)
  518. (and (= (length groups) 1)
  519. (car-safe groups)))
  520. ((first)
  521. (car-safe groups))
  522. ((majority)
  523. (let ((freq (make-hash-table
  524. :size 256
  525. :test 'equal)))
  526. (mapc (lambda (x) (let ((x (gnus-group-short-name x)))
  527. (puthash x (1+ (gethash x freq 0)) freq)))
  528. groups)
  529. (setq desc (format "%d groups, %d unique"
  530. (length groups)
  531. (hash-table-count freq)))
  532. (car-safe
  533. (sort groups
  534. (lambda (a b)
  535. (> (gethash (gnus-group-short-name a) freq 0)
  536. (gethash (gnus-group-short-name b) freq 0)))))))))
  537. (if chosen
  538. (gnus-message
  539. 9
  540. "%s: strategy %s on %s produced %s"
  541. log-agent gnus-registry-split-strategy desc chosen)
  542. (gnus-message
  543. 9
  544. "%s: strategy %s on %s did not produce an answer"
  545. log-agent
  546. (or gnus-registry-split-strategy "default")
  547. desc))
  548. (setq groups (and chosen (list chosen)))
  549. (dolist (group groups)
  550. (let ((m1 (gnus-find-method-for-group group))
  551. (m2 (or gnus-command-method
  552. (gnus-find-method-for-group gnus-newsgroup-name)))
  553. (short-name (gnus-group-short-name group)))
  554. (if (gnus-methods-equal-p m1 m2)
  555. (progn
  556. ;; this is REALLY just for debugging
  557. (when (not (equal group short-name))
  558. (gnus-message
  559. 10
  560. "%s: stripped group %s to %s"
  561. log-agent group short-name))
  562. (add-to-list 'out short-name))
  563. ;; else...
  564. (gnus-message
  565. 7
  566. "%s: ignored foreign group %s"
  567. log-agent group))))
  568. (setq out (delq nil out))
  569. (cond
  570. ((= (length out) 1) out)
  571. ((null out)
  572. (gnus-message
  573. 5
  574. "%s: no matches for %s '%s'."
  575. log-agent mode key)
  576. nil)
  577. (t (gnus-message
  578. 5
  579. "%s: too many extra matches (%s) for %s '%s'. Returning none."
  580. log-agent out mode key)
  581. nil))))
  582. (defun gnus-registry-follow-group-p (group)
  583. "Determines if a group name should be followed.
  584. Consults `gnus-registry-unfollowed-groups' and
  585. `nnmail-split-fancy-with-parent-ignore-groups'."
  586. (and group
  587. (not (or (gnus-grep-in-list
  588. group
  589. gnus-registry-unfollowed-groups)
  590. (gnus-grep-in-list
  591. group
  592. nnmail-split-fancy-with-parent-ignore-groups)))))
  593. ;; note that gnus-registry-ignored-groups is defined in gnus.el as a
  594. ;; group/topic parameter and an associated variable!
  595. ;; we do special logic for ignoring to accept regular expressions and
  596. ;; nnmail-split-fancy-with-parent-ignore-groups as well
  597. (defun gnus-registry-ignore-group-p (group)
  598. "Determines if a group name should be ignored.
  599. Consults `gnus-registry-ignored-groups' and
  600. `nnmail-split-fancy-with-parent-ignore-groups'."
  601. (and group
  602. (or (gnus-grep-in-list
  603. group
  604. (delq nil (mapcar (lambda (g)
  605. (cond
  606. ((stringp g) g)
  607. ((and (listp g) (nth 1 g))
  608. (nth 0 g))
  609. (t nil))) gnus-registry-ignored-groups)))
  610. ;; only use `gnus-parameter-registry-ignore' if
  611. ;; `gnus-registry-ignored-groups' is a list of lists
  612. ;; (it can be a list of regexes)
  613. (and (listp (nth 0 gnus-registry-ignored-groups))
  614. (get-buffer "*Group*") ; in automatic tests this is false
  615. (gnus-parameter-registry-ignore group))
  616. (gnus-grep-in-list
  617. group
  618. nnmail-split-fancy-with-parent-ignore-groups))))
  619. (defun gnus-registry-wash-for-keywords (&optional force)
  620. "Get the keywords of the current article.
  621. Overrides existing keywords with FORCE set non-nil."
  622. (interactive)
  623. (let ((id (gnus-registry-fetch-message-id-fast gnus-current-article))
  624. word words)
  625. (if (or (not (gnus-registry-get-id-key id 'keyword))
  626. force)
  627. (with-current-buffer gnus-article-buffer
  628. (article-goto-body)
  629. (save-window-excursion
  630. (save-restriction
  631. (narrow-to-region (point) (point-max))
  632. (with-syntax-table gnus-adaptive-word-syntax-table
  633. (while (re-search-forward "\\b\\w+\\b" nil t)
  634. (setq word (gnus-string-remove-all-properties
  635. (downcase (buffer-substring
  636. (match-beginning 0) (match-end 0)))))
  637. (if (> (length word) 2)
  638. (push word words))))))
  639. (gnus-registry-set-id-key id 'keyword words)))))
  640. (defun gnus-registry-keywords ()
  641. (let ((table (registry-lookup-secondary gnus-registry-db 'keyword)))
  642. (when table (maphash (lambda (k v) k) table))))
  643. (defun gnus-registry-find-keywords (keyword)
  644. (interactive (list
  645. (completing-read "Keyword: " (gnus-registry-keywords) nil t)))
  646. (registry-lookup-secondary-value gnus-registry-db 'keyword keyword))
  647. (defun gnus-registry-register-message-ids ()
  648. "Register the Message-ID of every article in the group"
  649. (unless (gnus-parameter-registry-ignore gnus-newsgroup-name)
  650. (dolist (article gnus-newsgroup-articles)
  651. (let* ((id (gnus-registry-fetch-message-id-fast article))
  652. (groups (gnus-registry-get-id-key id 'group)))
  653. (unless (member gnus-newsgroup-name groups)
  654. (gnus-message 9 "Registry: Registering article %d with group %s"
  655. article gnus-newsgroup-name)
  656. (gnus-registry-handle-action id nil gnus-newsgroup-name
  657. (gnus-registry-fetch-simplified-message-subject-fast article)
  658. (gnus-registry-fetch-sender-fast article)
  659. (gnus-registry-fetch-recipients-fast article)))))))
  660. ;; message field fetchers
  661. (defun gnus-registry-fetch-message-id-fast (article)
  662. "Fetch the Message-ID quickly, using the internal gnus-data-list function"
  663. (if (and (numberp article)
  664. (assoc article (gnus-data-list nil)))
  665. (mail-header-id (gnus-data-header (assoc article (gnus-data-list nil))))
  666. nil))
  667. (defun gnus-registry-extract-addresses (text)
  668. "Extract all the addresses in a normalized way from TEXT.
  669. Returns an unsorted list of strings in the name <address> format.
  670. Addresses without a name will say \"noname\"."
  671. (mapcar (lambda (add)
  672. (gnus-string-remove-all-properties
  673. (let* ((name (or (nth 0 add) "noname"))
  674. (addr (nth 1 add))
  675. (addr (if (bufferp addr)
  676. (with-current-buffer addr
  677. (buffer-string))
  678. addr)))
  679. (format "%s <%s>" name addr))))
  680. (mail-extract-address-components text t)))
  681. (defun gnus-registry-sort-addresses (&rest addresses)
  682. "Return a normalized and sorted list of ADDRESSES."
  683. (sort (apply 'nconc (mapcar 'gnus-registry-extract-addresses addresses))
  684. 'string-lessp))
  685. (defun gnus-registry-simplify-subject (subject)
  686. (if (stringp subject)
  687. (gnus-simplify-subject subject)
  688. nil))
  689. (defun gnus-registry-fetch-simplified-message-subject-fast (article)
  690. "Fetch the Subject quickly, using the internal gnus-data-list function"
  691. (if (and (numberp article)
  692. (assoc article (gnus-data-list nil)))
  693. (gnus-string-remove-all-properties
  694. (gnus-registry-simplify-subject
  695. (mail-header-subject (gnus-data-header
  696. (assoc article (gnus-data-list nil))))))
  697. nil))
  698. (defun gnus-registry-fetch-sender-fast (article)
  699. (gnus-registry-fetch-header-fast "from" article))
  700. (defun gnus-registry-fetch-recipients-fast (article)
  701. (gnus-registry-sort-addresses
  702. (or (ignore-errors (gnus-registry-fetch-header-fast "Cc" article)) "")
  703. (or (ignore-errors (gnus-registry-fetch-header-fast "To" article)) "")))
  704. (defun gnus-registry-fetch-header-fast (article header)
  705. "Fetch the HEADER quickly, using the internal gnus-data-list function"
  706. (if (and (numberp article)
  707. (assoc article (gnus-data-list nil)))
  708. (gnus-string-remove-all-properties
  709. (cdr (assq header (gnus-data-header
  710. (assoc article (gnus-data-list nil))))))
  711. nil))
  712. ;; registry marks glue
  713. (defun gnus-registry-do-marks (type function)
  714. "For each known mark, call FUNCTION for each cell of type TYPE.
  715. FUNCTION should take two parameters, a mark symbol and the cell value."
  716. (dolist (mark-info gnus-registry-marks)
  717. (let* ((mark (car-safe mark-info))
  718. (data (cdr-safe mark-info))
  719. (cell-data (plist-get data type)))
  720. (when cell-data
  721. (funcall function mark cell-data)))))
  722. ;;; this is ugly code, but I don't know how to do it better
  723. (defun gnus-registry-install-shortcuts ()
  724. "Install the keyboard shortcuts and menus for the registry.
  725. Uses `gnus-registry-marks' to find what shortcuts to install."
  726. (let (keys-plist)
  727. (setq gnus-registry-misc-menus nil)
  728. (gnus-registry-do-marks
  729. :char
  730. (lambda (mark data)
  731. (let ((function-format
  732. (format "gnus-registry-%%s-article-%s-mark" mark)))
  733. ;;; The following generates these functions:
  734. ;;; (defun gnus-registry-set-article-Important-mark (&rest articles)
  735. ;;; "Apply the Important mark to process-marked ARTICLES."
  736. ;;; (interactive (gnus-summary-work-articles current-prefix-arg))
  737. ;;; (gnus-registry-set-article-mark-internal 'Important articles nil t))
  738. ;;; (defun gnus-registry-remove-article-Important-mark (&rest articles)
  739. ;;; "Apply the Important mark to process-marked ARTICLES."
  740. ;;; (interactive (gnus-summary-work-articles current-prefix-arg))
  741. ;;; (gnus-registry-set-article-mark-internal 'Important articles t t))
  742. (dolist (remove '(t nil))
  743. (let* ((variant-name (if remove "remove" "set"))
  744. (function-name (format function-format variant-name))
  745. (shortcut (format "%c" data))
  746. (shortcut (if remove (upcase shortcut) shortcut)))
  747. (unintern function-name obarray)
  748. (eval
  749. `(defun
  750. ;; function name
  751. ,(intern function-name)
  752. ;; parameter definition
  753. (&rest articles)
  754. ;; documentation
  755. ,(format
  756. "%s the %s mark over process-marked ARTICLES."
  757. (upcase-initials variant-name)
  758. mark)
  759. ;; interactive definition
  760. (interactive
  761. (gnus-summary-work-articles current-prefix-arg))
  762. ;; actual code
  763. ;; if this is called and the user doesn't want the
  764. ;; registry enabled, we'll ask anyhow
  765. (unless gnus-registry-install
  766. (let ((gnus-registry-install 'ask))
  767. (gnus-registry-install-p)))
  768. ;; now the user is asked if gnus-registry-install is 'ask
  769. (when (gnus-registry-install-p)
  770. (gnus-registry-set-article-mark-internal
  771. ;; all this just to get the mark, I must be doing it wrong
  772. (intern ,(symbol-name mark))
  773. articles ,remove t)
  774. (gnus-message
  775. 9
  776. "Applying mark %s to %d articles"
  777. ,(symbol-name mark) (length articles))
  778. (dolist (article articles)
  779. (gnus-summary-update-article
  780. article
  781. (assoc article (gnus-data-list nil)))))))
  782. (push (intern function-name) keys-plist)
  783. (push shortcut keys-plist)
  784. (push (vector (format "%s %s"
  785. (upcase-initials variant-name)
  786. (symbol-name mark))
  787. (intern function-name) t)
  788. gnus-registry-misc-menus)
  789. (gnus-message
  790. 9
  791. "Defined mark handling function %s"
  792. function-name))))))
  793. (gnus-define-keys-1
  794. '(gnus-registry-mark-map "M" gnus-summary-mark-map)
  795. keys-plist)
  796. (add-hook 'gnus-summary-menu-hook
  797. (lambda ()
  798. (easy-menu-add-item
  799. gnus-summary-misc-menu
  800. nil
  801. (cons "Registry Marks" gnus-registry-misc-menus))))))
  802. (make-obsolete 'gnus-registry-user-format-function-M
  803. 'gnus-registry-article-marks-to-chars "24.1") ?
  804. (defalias 'gnus-registry-user-format-function-M
  805. 'gnus-registry-article-marks-to-chars)
  806. ;; use like this:
  807. ;; (defalias 'gnus-user-format-function-M 'gnus-registry-article-marks-to-chars)
  808. (defun gnus-registry-article-marks-to-chars (headers)
  809. "Show the marks for an article by the :char property"
  810. (let* ((id (mail-header-message-id headers))
  811. (marks (when id (gnus-registry-get-id-key id 'mark))))
  812. (mapconcat (lambda (mark)
  813. (plist-get
  814. (cdr-safe
  815. (assoc mark gnus-registry-marks))
  816. :char))
  817. marks "")))
  818. ;; use like this:
  819. ;; (defalias 'gnus-user-format-function-M 'gnus-registry-article-marks-to-names)
  820. (defun gnus-registry-article-marks-to-names (headers)
  821. "Show the marks for an article by name"
  822. (let* ((id (mail-header-message-id headers))
  823. (marks (when id (gnus-registry-get-id-key id 'mark))))
  824. (mapconcat (lambda (mark) (symbol-name mark)) marks ",")))
  825. (defun gnus-registry-read-mark ()
  826. "Read a mark name from the user with completion."
  827. (let ((mark (gnus-completing-read
  828. "Label"
  829. (mapcar 'symbol-name (mapcar 'car gnus-registry-marks))
  830. nil nil nil
  831. (symbol-name gnus-registry-default-mark))))
  832. (when (stringp mark)
  833. (intern mark))))
  834. (defun gnus-registry-set-article-mark (&rest articles)
  835. "Apply a mark to process-marked ARTICLES."
  836. (interactive (gnus-summary-work-articles current-prefix-arg))
  837. (gnus-registry-set-article-mark-internal (gnus-registry-read-mark)
  838. articles nil t))
  839. (defun gnus-registry-remove-article-mark (&rest articles)
  840. "Remove a mark from process-marked ARTICLES."
  841. (interactive (gnus-summary-work-articles current-prefix-arg))
  842. (gnus-registry-set-article-mark-internal (gnus-registry-read-mark)
  843. articles t t))
  844. (defun gnus-registry-set-article-mark-internal (mark
  845. articles
  846. &optional remove
  847. show-message)
  848. "Apply or remove MARK across a list of ARTICLES."
  849. (let ((article-id-list
  850. (mapcar 'gnus-registry-fetch-message-id-fast articles)))
  851. (dolist (id article-id-list)
  852. (let* ((marks (delq mark (gnus-registry-get-id-key id 'mark)))
  853. (marks (if remove marks (cons mark marks))))
  854. (when show-message
  855. (gnus-message 1 "%s mark %s with message ID %s, resulting in %S"
  856. (if remove "Removing" "Adding")
  857. mark id marks))
  858. (gnus-registry-set-id-key id 'mark marks)))))
  859. (defun gnus-registry-get-article-marks (&rest articles)
  860. "Get the Gnus registry marks for ARTICLES and show them if interactive.
  861. Uses process/prefix conventions. For multiple articles,
  862. only the last one's marks are returned."
  863. (interactive (gnus-summary-work-articles 1))
  864. (let* ((article (last articles))
  865. (id (gnus-registry-fetch-message-id-fast article))
  866. (marks (when id (gnus-registry-get-id-key id 'mark))))
  867. (when (interactive-p)
  868. (gnus-message 1 "Marks are %S" marks))
  869. marks))
  870. (defun gnus-registry-group-count (id)
  871. "Get the number of groups of a message, based on the message ID."
  872. (length (gnus-registry-get-id-key id 'group)))
  873. (defun gnus-registry-get-or-make-entry (id)
  874. (let* ((db gnus-registry-db)
  875. ;; safe if not found
  876. (entries (registry-lookup db (list id))))
  877. (when (null entries)
  878. (gnus-registry-insert db id (list (list 'creation-time (current-time))
  879. '(group) '(sender) '(subject)))
  880. (setq entries (registry-lookup db (list id))))
  881. (nth 1 (assoc id entries))))
  882. (defun gnus-registry-delete-entries (idlist)
  883. (registry-delete gnus-registry-db idlist nil))
  884. (defun gnus-registry-get-id-key (id key)
  885. (cdr-safe (assq key (gnus-registry-get-or-make-entry id))))
  886. (defun gnus-registry-set-id-key (id key vals)
  887. (let* ((db gnus-registry-db)
  888. (entry (gnus-registry-get-or-make-entry id)))
  889. (registry-delete db (list id) nil)
  890. (setq entry (cons (cons key vals) (assq-delete-all key entry)))
  891. (gnus-registry-insert db id entry)
  892. entry))
  893. (defun gnus-registry-insert (db id entry)
  894. "Just like `registry-insert' but tries to prune on error."
  895. (when (registry-full db)
  896. (message "Trying to prune the registry because it's full")
  897. (registry-prune db))
  898. (registry-insert db id entry)
  899. entry)
  900. (defun gnus-registry-import-eld (file)
  901. (interactive "fOld registry file to import? ")
  902. ;; example content:
  903. ;; (setq gnus-registry-alist '(
  904. ;; ("<messageID>" ((marks nil)
  905. ;; (mtime 19365 1776 440496)
  906. ;; (sender . "root (Cron Daemon)")
  907. ;; (subject . "Cron"))
  908. ;; "cron" "nnml+private:cron")
  909. (load file t)
  910. (when (boundp 'gnus-registry-alist)
  911. (let* ((old (symbol-value 'gnus-registry-alist))
  912. (count 0)
  913. (expected (length old))
  914. entry)
  915. (while (car-safe old)
  916. (incf count)
  917. ;; don't use progress reporters for backwards compatibility
  918. (when (and (< 0 expected)
  919. (= 0 (mod count 100)))
  920. (message "importing: %d of %d (%.2f%%)"
  921. count expected (/ (* 100 count) expected)))
  922. (setq entry (car-safe old)
  923. old (cdr-safe old))
  924. (let* ((id (car-safe entry))
  925. (new-entry (gnus-registry-get-or-make-entry id))
  926. (rest (cdr-safe entry))
  927. (groups (loop for p in rest
  928. when (stringp p)
  929. collect p))
  930. extra-cell key val)
  931. ;; remove all the strings from the entry
  932. (dolist (elem rest)
  933. (if (stringp elem) (setq rest (delq elem rest))))
  934. (gnus-registry-set-id-key id 'group groups)
  935. ;; just use the first extra element
  936. (setq rest (car-safe rest))
  937. (while (car-safe rest)
  938. (setq extra-cell (car-safe rest)
  939. key (car-safe extra-cell)
  940. val (cdr-safe extra-cell)
  941. rest (cdr-safe rest))
  942. (when (and val (atom val))
  943. (setq val (list val)))
  944. (gnus-registry-set-id-key id key val))))
  945. (message "Import done, collected %d entries" count))))
  946. (ert-deftest gnus-registry-misc-test ()
  947. (should-error (gnus-registry-extract-addresses '("" "")))
  948. (should (equal '("Ted Zlatanov <tzz@lifelogs.com>"
  949. "noname <ed@you.me>"
  950. "noname <cyd@stupidchicken.com>"
  951. "noname <tzz@lifelogs.com>")
  952. (gnus-registry-extract-addresses
  953. (concat "Ted Zlatanov <tzz@lifelogs.com>, "
  954. "ed <ed@you.me>, " ; "ed" is not a valid name here
  955. "cyd@stupidchicken.com, "
  956. "tzz@lifelogs.com")))))
  957. (ert-deftest gnus-registry-usage-test ()
  958. (let* ((n 100)
  959. (tempfile (make-temp-file "gnus-registry-persist"))
  960. (db (gnus-registry-make-db tempfile))
  961. (gnus-registry-db db)
  962. back size)
  963. (message "Adding %d keys to the test Gnus registry" n)
  964. (dotimes (i n)
  965. (let ((id (number-to-string i)))
  966. (gnus-registry-handle-action id
  967. (if (>= 50 i) "fromgroup" nil)
  968. "togroup"
  969. (when (>= 70 i)
  970. (format "subject %d" (mod i 10)))
  971. (when (>= 80 i)
  972. (format "sender %d" (mod i 10))))))
  973. (message "Testing Gnus registry size is %d" n)
  974. (should (= n (registry-size db)))
  975. (message "Looking up individual keys (registry-lookup)")
  976. (should (equal (loop for e
  977. in (mapcar 'cadr
  978. (registry-lookup db '("20" "83" "72")))
  979. collect (assq 'subject e)
  980. collect (assq 'sender e)
  981. collect (assq 'group e))
  982. '((subject "subject 0") (sender "sender 0") (group "togroup")
  983. (subject) (sender) (group "togroup")
  984. (subject) (sender "sender 2") (group "togroup"))))
  985. (message "Looking up individual keys (gnus-registry-id-key)")
  986. (should (equal (gnus-registry-get-id-key "34" 'group) '("togroup")))
  987. (should (equal (gnus-registry-get-id-key "34" 'subject) '("subject 4")))
  988. (message "Trying to insert a duplicate key")
  989. (should-error (gnus-registry-insert db "55" '()))
  990. (message "Looking up individual keys (gnus-registry-get-or-make-entry)")
  991. (should (gnus-registry-get-or-make-entry "22"))
  992. (message "Saving the Gnus registry to %s" tempfile)
  993. (should (gnus-registry-save tempfile db))
  994. (setq size (nth 7 (file-attributes tempfile)))
  995. (message "Saving the Gnus registry to %s: size %d" tempfile size)
  996. (should (< 0 size))
  997. (with-temp-buffer
  998. (insert-file-contents-literally tempfile)
  999. (should (looking-at (concat ";; Object "
  1000. "Gnus Registry"
  1001. "\n;; EIEIO PERSISTENT OBJECT"))))
  1002. (message "Reading Gnus registry back")
  1003. (setq back (eieio-persistent-read tempfile))
  1004. (should back)
  1005. (message "Read Gnus registry back: %d keys, expected %d==%d"
  1006. (registry-size back) n (registry-size db))
  1007. (should (= (registry-size back) n))
  1008. (should (= (registry-size back) (registry-size db)))
  1009. (delete-file tempfile)
  1010. (message "Pruning Gnus registry to 0 by setting :max-soft")
  1011. (oset db :max-soft 0)
  1012. (registry-prune db)
  1013. (should (= (registry-size db) 0)))
  1014. (message "Done with Gnus registry usage testing."))
  1015. ;;;###autoload
  1016. (defun gnus-registry-initialize ()
  1017. "Initialize the Gnus registry."
  1018. (interactive)
  1019. (gnus-message 5 "Initializing the registry")
  1020. (gnus-registry-install-hooks)
  1021. (gnus-registry-install-shortcuts)
  1022. (gnus-registry-read))
  1023. ;;;###autoload
  1024. (defun gnus-registry-install-hooks ()
  1025. "Install the registry hooks."
  1026. (interactive)
  1027. (setq gnus-registry-enabled t)
  1028. (add-hook 'gnus-summary-article-move-hook 'gnus-registry-action)
  1029. (add-hook 'gnus-summary-article-delete-hook 'gnus-registry-action)
  1030. (add-hook 'gnus-summary-article-expire-hook 'gnus-registry-action)
  1031. (add-hook 'nnmail-spool-hook 'gnus-registry-spool-action)
  1032. (add-hook 'gnus-save-newsrc-hook 'gnus-registry-save)
  1033. (add-hook 'gnus-read-newsrc-el-hook 'gnus-registry-read)
  1034. (add-hook 'gnus-summary-prepare-hook 'gnus-registry-register-message-ids))
  1035. (defun gnus-registry-unload-hook ()
  1036. "Uninstall the registry hooks."
  1037. (interactive)
  1038. (remove-hook 'gnus-summary-article-move-hook 'gnus-registry-action)
  1039. (remove-hook 'gnus-summary-article-delete-hook 'gnus-registry-action)
  1040. (remove-hook 'gnus-summary-article-expire-hook 'gnus-registry-action)
  1041. (remove-hook 'nnmail-spool-hook 'gnus-registry-spool-action)
  1042. (remove-hook 'gnus-save-newsrc-hook 'gnus-registry-save)
  1043. (remove-hook 'gnus-read-newsrc-el-hook 'gnus-registry-read)
  1044. (remove-hook 'gnus-summary-prepare-hook 'gnus-registry-register-message-ids)
  1045. (setq gnus-registry-enabled nil))
  1046. (add-hook 'gnus-registry-unload-hook 'gnus-registry-unload-hook)
  1047. (defun gnus-registry-install-p ()
  1048. "If the registry is not already enabled, and `gnus-registry-install' is t,
  1049. the registry is enabled. If `gnus-registry-install' is `ask',
  1050. the user is asked first. Returns non-nil iff the registry is enabled."
  1051. (interactive)
  1052. (unless gnus-registry-enabled
  1053. (when (if (eq gnus-registry-install 'ask)
  1054. (gnus-y-or-n-p
  1055. (concat "Enable the Gnus registry? "
  1056. "See the variable `gnus-registry-install' "
  1057. "to get rid of this query permanently. "))
  1058. gnus-registry-install)
  1059. (gnus-registry-initialize)))
  1060. gnus-registry-enabled)
  1061. ;; TODO: a few things
  1062. (provide 'gnus-registry)
  1063. ;;; gnus-registry.el ends here