30060b4105a118948f92fc8044cbe07ba234ba79
[gnus] / lisp / gnus-registry.el
1 ;;; gnus-registry.el --- article registry for Gnus
2 ;; Copyright (C) 1996, 1997, 1998, 1999, 2000, 2001, 2002, 2003
3 ;;        Free Software Foundation, Inc.
4
5 ;; Author: Ted Zlatanov <tzz@lifelogs.com>
6 ;; Keywords: news
7
8 ;; This file is part of GNU Emacs.
9
10 ;; GNU Emacs is free software; you can redistribute it and/or modify
11 ;; it under the terms of the GNU General Public License as published by
12 ;; the Free Software Foundation; either version 2, or (at your option)
13 ;; any later version.
14
15 ;; GNU Emacs is distributed in the hope that it will be useful,
16 ;; but WITHOUT ANY WARRANTY; without even the implied warranty of
17 ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
18 ;; GNU General Public License for more details.
19
20 ;; You should have received a copy of the GNU General Public License
21 ;; along with GNU Emacs; see the file COPYING.  If not, write to the
22 ;; Free Software Foundation, Inc., 59 Temple Place - Suite 330,
23 ;; Boston, MA 02111-1307, USA.
24
25 ;;; Commentary:
26
27 ;; This is the gnus-registry.el package, works with other backends
28 ;; besides nnmail.  The major issue is that it doesn't go across
29 ;; backends, so for instance if an article is in nnml:sys and you see
30 ;; a reference to it in nnimap splitting, the article will end up in
31 ;; nnimap:sys
32
33 ;; gnus-registry.el intercepts article respooling, moving, deleting,
34 ;; and copying for all backends.  If it doesn't work correctly for
35 ;; you, submit a bug report and I'll be glad to fix it.  It needs
36 ;; documentation in the manual (also on my to-do list).
37
38 ;; Put this in your startup file (~/.gnus.el for instance)
39
40 ;; (setq gnus-registry-max-entries 2500
41 ;;       gnus-registry-use-long-group-names t)
42
43 ;; (gnus-registry-initialize)
44
45 ;; Then use this in your fancy-split:
46
47 ;; (: gnus-registry-split-fancy-with-parent)
48
49 ;; TODO:
50
51 ;; - get the correct group on spool actions
52
53 ;; - articles that are spooled to a different backend should be handled
54
55 ;;; Code:
56
57 (eval-when-compile (require 'cl))
58
59 (require 'gnus)
60 (require 'gnus-int)
61 (require 'gnus-sum)
62 (require 'nnmail)
63
64 (defvar gnus-registry-dirty t
65  "Boolean set to t when the registry is modified")
66
67 (defgroup gnus-registry nil
68   "The Gnus registry."
69   :group 'gnus)
70
71 (defvar gnus-registry-hashtb nil
72   "*The article registry by Message ID.")
73
74 (defcustom gnus-registry-unfollowed-groups '("delayed" "drafts" "queue")
75   "List of groups that gnus-registry-split-fancy-with-parent won't follow.
76 The group names are matched, they don't have to be fully qualified."
77   :group 'gnus-registry
78   :type '(repeat string))
79
80 (defcustom gnus-registry-install nil
81   "Whether the registry should be installed."
82   :group 'gnus-registry
83   :type 'boolean)
84
85 (defcustom gnus-registry-clean-empty t
86   "Whether the empty registry entries should be deleted.
87 Registry entries are considered empty when they have no groups."
88   :group 'gnus-registry
89   :type 'boolean)
90
91 (defcustom gnus-registry-use-long-group-names nil
92   "Whether the registry should use long group names (BUGGY)."
93   :group 'gnus-registry
94   :type 'boolean)
95
96 (defcustom gnus-registry-track-extra nil
97   "Whether the registry should track extra data about a message.
98 The Subject and Sender (From:) headers are currently tracked this
99 way."
100   :group 'gnus-registry
101   :type      
102   '(set :tag "Tracking choices"
103     (const :tag "Track by subject (Subject: header)" subject)
104     (const :tag "Track by sender (From: header)"  sender)))
105
106 (defcustom gnus-registry-entry-caching t
107   "Whether the registry should cache extra information."
108   :group 'gnus-registry
109   :type 'boolean)
110
111 (defcustom gnus-registry-minimum-subject-length 5
112   "The minimum length of a subject before it's considered trackable."
113   :group 'gnus-registry
114   :type 'integer)
115
116 (defcustom gnus-registry-trim-articles-without-groups t
117   "Whether the registry should clean out message IDs without groups."
118   :group 'gnus-registry
119   :type 'boolean)
120
121 (defcustom gnus-registry-cache-file 
122   (nnheader-concat 
123    (or gnus-dribble-directory gnus-home-directory "~/") 
124    ".gnus.registry.eld")
125   "File where the Gnus registry will be stored."
126   :group 'gnus-registry
127   :type 'file)
128
129 (defcustom gnus-registry-max-entries nil
130   "Maximum number of entries in the registry, nil for unlimited."
131   :group 'gnus-registry
132   :type '(radio (const :format "Unlimited " nil)
133                 (integer :format "Maximum number: %v\n" :size 0)))
134
135 (defun gnus-registry-track-subject-p ()
136   (memq 'subject gnus-registry-track-extra))
137
138 (defun gnus-registry-track-sender-p ()
139   (memq 'sender gnus-registry-track-extra))
140
141 (defun gnus-registry-cache-read ()
142   "Read the registry cache file."
143   (interactive)
144   (let ((file gnus-registry-cache-file))
145     (when (file-exists-p file)
146       (gnus-message 5 "Reading %s..." file)
147       (gnus-load file)
148       (gnus-message 5 "Reading %s...done" file))))
149
150 (defun gnus-registry-cache-save ()
151   "Save the registry cache file."
152   (interactive)
153   (let ((file gnus-registry-cache-file))
154     (save-excursion
155       (set-buffer (gnus-get-buffer-create " *Gnus-registry-cache*"))
156       (make-local-variable 'version-control)
157     (setq version-control gnus-backup-startup-file)
158     (setq buffer-file-name file)
159     (setq default-directory (file-name-directory buffer-file-name))
160     (buffer-disable-undo)
161     (erase-buffer)
162     (gnus-message 5 "Saving %s..." file)
163     (if gnus-save-startup-file-via-temp-buffer
164         (let ((coding-system-for-write gnus-ding-file-coding-system)
165               (standard-output (current-buffer)))
166           (gnus-gnus-to-quick-newsrc-format t "gnus registry startup file" 'gnus-registry-alist)
167           (gnus-registry-cache-whitespace file)
168           (save-buffer))
169       (let ((coding-system-for-write gnus-ding-file-coding-system)
170             (version-control gnus-backup-startup-file)
171             (startup-file file)
172             (working-dir (file-name-directory file))
173             working-file
174             (i -1))
175         ;; Generate the name of a non-existent file.
176         (while (progn (setq working-file
177                             (format
178                              (if (and (eq system-type 'ms-dos)
179                                       (not (gnus-long-file-names)))
180                                  "%s#%d.tm#" ; MSDOS limits files to 8+3
181                                (if (memq system-type '(vax-vms axp-vms))
182                                    "%s$tmp$%d"
183                                  "%s#tmp#%d"))
184                              working-dir (setq i (1+ i))))
185                       (file-exists-p working-file)))
186         
187         (unwind-protect
188             (progn
189               (gnus-with-output-to-file working-file
190                 (gnus-gnus-to-quick-newsrc-format t "gnus registry startup file" 'gnus-registry-alist))
191               
192               ;; These bindings will mislead the current buffer
193               ;; into thinking that it is visiting the startup
194               ;; file.
195               (let ((buffer-backed-up nil)
196                     (buffer-file-name startup-file)
197                     (file-precious-flag t)
198                     (setmodes (file-modes startup-file)))
199                 ;; Backup the current version of the startup file.
200                 (backup-buffer)
201                 
202                 ;; Replace the existing startup file with the temp file.
203                 (rename-file working-file startup-file t)
204                 (set-file-modes startup-file setmodes)))
205           (condition-case nil
206               (delete-file working-file)
207             (file-error nil)))))
208     
209     (gnus-kill-buffer (current-buffer))
210     (gnus-message 5 "Saving %s...done" file))))
211
212 ;; Idea from Dan Christensen <jdc@chow.mat.jhu.edu>
213 ;; Save the gnus-registry file with extra line breaks.
214 (defun gnus-registry-cache-whitespace (filename)
215   (gnus-message 5 "Adding whitespace to %s" filename)
216   (save-excursion
217     (goto-char (point-min))
218     (while (re-search-forward "^(\\|(\\\"" nil t)
219       (replace-match "\n\\&" t))
220     (goto-char (point-min))
221     (while (re-search-forward " $" nil t)
222       (replace-match "" t t))))
223
224 (defun gnus-registry-save (&optional force)
225   (when (or gnus-registry-dirty force)
226     (let ((caching gnus-registry-entry-caching))
227       ;; turn off entry caching, so mtime doesn't get recorded
228       (setq gnus-registry-entry-caching nil)
229       ;; remove entry caches
230       (maphash
231        (lambda (key value)
232          (if (hash-table-p value)
233              (remhash key gnus-registry-hashtb)))
234        gnus-registry-hashtb)
235       ;; remove empty entries
236       (when gnus-registry-clean-empty 
237         (gnus-registry-clean-empty-function))
238       ;; now trim the registry appropriately
239       (setq gnus-registry-alist (gnus-registry-trim 
240                                  (hashtable-to-alist gnus-registry-hashtb)))
241       ;; really save
242       (gnus-registry-cache-save)
243       (setq gnus-registry-entry-caching caching)
244       (setq gnus-registry-dirty nil))))
245
246 (defun gnus-registry-clean-empty-function ()
247   "Remove all empty entries from the registry.  Returns count thereof."
248   (let ((count 0))
249     (maphash
250      (lambda (key value)
251        (unless (gnus-registry-fetch-group key)
252          (incf count)
253          (remhash key gnus-registry-hashtb)))
254      gnus-registry-hashtb)
255     count))
256
257 (defun gnus-registry-read ()
258   (gnus-registry-cache-read)
259   (setq gnus-registry-hashtb (alist-to-hashtable gnus-registry-alist))
260   (setq gnus-registry-dirty nil))
261
262 (defun gnus-registry-trim (alist)
263   "Trim alist to size, using gnus-registry-max-entries."
264   (if (null gnus-registry-max-entries)
265       alist                             ; just return the alist
266     ;; else, when given max-entries, trim the alist
267     (let ((timehash (make-hash-table                        
268                      :size 4096
269                      :test 'equal)))
270       (maphash
271        (lambda (key value)
272          (puthash key (gnus-registry-fetch-extra key 'mtime) timehash))
273        gnus-registry-hashtb)
274
275       ;; we use the return value of this setq, which is the trimmed alist
276       (setq alist
277             (nthcdr
278              (- (length alist) gnus-registry-max-entries)
279              (sort alist 
280                    (lambda (a b)
281                      (time-less-p 
282                       (cdr (gethash (car a) timehash))
283                       (cdr (gethash (car b) timehash))))))))))
284
285 (defun alist-to-hashtable (alist)
286   "Build a hashtable from the values in ALIST."
287   (let ((ht (make-hash-table                        
288              :size 4096
289              :test 'equal)))
290     (mapc
291      (lambda (kv-pair)
292        (puthash (car kv-pair) (cdr kv-pair) ht))
293      alist)
294      ht))
295
296 (defun hashtable-to-alist (hash)
297   "Build an alist from the values in HASH."
298   (let ((list nil))
299     (maphash
300      (lambda (key value)
301        (setq list (cons (cons key value) list)))
302      hash)
303     list))
304
305 (defun gnus-registry-action (action data-header from &optional to method)
306   (let* ((id (mail-header-id data-header))
307          (subject (gnus-registry-simplify-subject 
308                    (mail-header-subject data-header)))
309          (sender (mail-header-from data-header))
310          (from (gnus-group-guess-full-name-from-command-method from))
311          (to (if to (gnus-group-guess-full-name-from-command-method to) nil))
312          (to-name (if to to "the Bit Bucket"))
313          (old-entry (gethash id gnus-registry-hashtb)))
314     (gnus-message 5 "Registry: article %s %s from %s to %s"
315                   id
316                   (if method "respooling" "going")
317                   from
318                   to)
319
320     ;; All except copy will need a delete
321     (gnus-registry-delete-group id from)
322
323     (when (equal 'copy action) 
324       (gnus-registry-add-group id from subject sender)) ; undo the delete
325
326     (gnus-registry-add-group id to subject sender)))
327
328 (defun gnus-registry-spool-action (id group &optional subject sender)
329   (let ((group (gnus-group-guess-full-name-from-command-method group)))
330     (when (and (stringp id) (string-match "\r$" id))
331       (setq id (substring id 0 -1)))
332     (gnus-message 5 "Registry: article %s spooled to %s"
333                   id
334                   group)
335     (gnus-registry-add-group id group subject sender)))
336
337 ;; Function for nn{mail|imap}-split-fancy: look up all references in
338 ;; the cache and if a match is found, return that group.
339 (defun gnus-registry-split-fancy-with-parent ()
340   "Split this message into the same group as its parent.  The parent
341 is obtained from the registry.  This function can be used as an entry
342 in `nnmail-split-fancy' or `nnimap-split-fancy', for example like
343 this: (: gnus-registry-split-fancy-with-parent) 
344
345 This function tracks ALL backends, unlike
346 `nnmail-split-fancy-with-parent' which tracks only nnmail
347 messages.
348
349 For a message to be split, it looks for the parent message in the
350 References or In-Reply-To header and then looks in the registry to
351 see which group that message was put in.  This group is returned.
352
353 See the Info node `(gnus)Fancy Mail Splitting' for more details."
354   (let ((refstr (or (message-fetch-field "references")
355                     (message-fetch-field "in-reply-to")))
356         (nnmail-split-fancy-with-parent-ignore-groups
357          (if (listp nnmail-split-fancy-with-parent-ignore-groups)
358              nnmail-split-fancy-with-parent-ignore-groups
359            (list nnmail-split-fancy-with-parent-ignore-groups)))
360         references res)
361     (if refstr
362         (progn
363           (setq references (nreverse (gnus-split-references refstr)))
364           (mapcar (lambda (x)
365                     (setq res (or (gnus-registry-fetch-group x) res))
366                     (when (or (gnus-registry-grep-in-list
367                                res
368                                gnus-registry-unfollowed-groups)
369                               (gnus-registry-grep-in-list 
370                                res
371                                nnmail-split-fancy-with-parent-ignore-groups))
372                       (setq res nil)))
373                   references))
374
375       ;; else: there were no references, now try the extra tracking
376       (let ((sender (message-fetch-field "from"))
377             (subject (gnus-registry-simplify-subject
378                       (message-fetch-field "subject")))
379             (single-match t))
380         (when (and single-match
381                    (gnus-registry-track-sender-p)
382                    sender)
383           (maphash
384            (lambda (key value)
385              (let ((this-sender (cdr 
386                                  (gnus-registry-fetch-extra key 'sender))))
387                (when (and single-match
388                           this-sender
389                           (equal sender this-sender))
390                  ;; too many matches, bail
391                  (unless (equal res (gnus-registry-fetch-group key))
392                    (setq single-match nil))
393                  (setq res (gnus-registry-fetch-group key))
394                  (when (and sender res)
395                    (gnus-message
396                     ;; raise level of messaging if gnus-registry-track-extra
397                     (if gnus-registry-track-extra 5 9)
398                     "%s (extra tracking) traced sender %s to group %s"
399                     "gnus-registry-split-fancy-with-parent"
400                     sender
401                     res)))))
402            gnus-registry-hashtb))
403         (when (and single-match
404                    (gnus-registry-track-subject-p)
405                    subject
406                    (< gnus-registry-minimum-subject-length (length subject)))
407           (maphash
408            (lambda (key value)
409              (let ((this-subject (cdr 
410                                   (gnus-registry-fetch-extra key 'subject))))
411                (when (and single-match
412                           this-subject
413                           (equal subject this-subject))
414                  ;; too many matches, bail
415                  (unless (equal res (gnus-registry-fetch-group key))
416                    (setq single-match nil))
417                  (setq res (gnus-registry-fetch-group key))
418                  (when (and subject res)
419                    (gnus-message
420                     ;; raise level of messaging if gnus-registry-track-extra
421                     (if gnus-registry-track-extra 5 9)
422                     "%s (extra tracking) traced subject %s to group %s"
423                     "gnus-registry-split-fancy-with-parent"
424                     subject
425                     res)))))
426            gnus-registry-hashtb))
427         (unless single-match
428           (gnus-message
429            5
430            "gnus-registry-split-fancy-with-parent: too many extra matches for %s"
431            refstr)
432           (setq res nil))))
433     (when (and refstr res)
434       (gnus-message
435        5
436        "gnus-registry-split-fancy-with-parent traced %s to group %s"
437        refstr res))
438
439     (when (and res gnus-registry-use-long-group-names)
440       (let ((m1 (gnus-find-method-for-group res))
441             (m2 (or gnus-command-method 
442                     (gnus-find-method-for-group gnus-newsgroup-name)))
443             (short-res (gnus-group-short-name res)))
444       (if (gnus-methods-equal-p m1 m2)
445           (progn
446             (gnus-message
447              9 
448              "gnus-registry-split-fancy-with-parent stripped group %s to %s"
449              res
450              short-res)
451             (setq res short-res))
452         ;; else...
453         (gnus-message
454          5 
455          "gnus-registry-split-fancy-with-parent ignored foreign group %s"
456          res)
457         (setq res nil))))
458     res))
459
460 (defun gnus-registry-register-message-ids ()
461   "Register the Message-ID of every article in the group"
462   (unless (gnus-parameter-registry-ignore gnus-newsgroup-name)
463     (dolist (article gnus-newsgroup-articles)
464       (let ((id (gnus-registry-fetch-message-id-fast article)))
465         (unless (gnus-registry-fetch-group id)
466           (gnus-message 9 "Registry: Registering article %d with group %s" 
467                         article gnus-newsgroup-name)
468           (gnus-registry-add-group 
469            (gnus-registry-fetch-message-id-fast article)
470            gnus-newsgroup-name
471            (gnus-registry-fetch-simplified-message-subject-fast article)
472            (gnus-registry-fetch-sender-fast article)))))))
473
474 (defun gnus-registry-fetch-message-id-fast (article)
475   "Fetch the Message-ID quickly, using the internal gnus-data-list function"
476   (if (and (numberp article)
477            (assoc article (gnus-data-list nil)))
478       (mail-header-id (gnus-data-header (assoc article (gnus-data-list nil))))
479     nil))
480
481 (defun gnus-registry-simplify-subject (subject)
482   (if (stringp subject)
483       (gnus-simplify-subject subject)
484     nil))
485
486 (defun gnus-registry-fetch-simplified-message-subject-fast (article)
487   "Fetch the Subject quickly, using the internal gnus-data-list function"
488   (if (and (numberp article)
489            (assoc article (gnus-data-list nil)))
490       (gnus-registry-simplify-subject
491        (mail-header-subject (gnus-data-header
492                              (assoc article (gnus-data-list nil)))))
493     nil))
494
495 (defun gnus-registry-fetch-sender-fast (article)
496   "Fetch the Sender quickly, using the internal gnus-data-list function"
497   (if (and (numberp article)
498            (assoc article (gnus-data-list nil)))
499       (mail-header-from (gnus-data-header
500                          (assoc article (gnus-data-list nil))))
501     nil))
502
503 (defun gnus-registry-grep-in-list (word list)
504   (when word
505     (memq nil
506           (mapcar 'not
507                   (mapcar 
508                    (lambda (x)
509                      (string-match x word))
510                    list)))))
511
512 (defun gnus-registry-fetch-extra (id &optional entry)
513   "Get the extra data of a message, based on the message ID.
514 Returns the first place where the trail finds a nonstring."
515   (let ((entry-cache (gethash entry gnus-registry-hashtb)))
516     (if (and entry
517              (hash-table-p entry-cache)
518              (gethash id entry-cache))
519         (gethash id entry-cache)
520       ;; else, if there is no caching possible...
521       (let ((trail (gethash id gnus-registry-hashtb)))
522         (when (listp trail)
523           (dolist (crumb trail)
524             (unless (stringp crumb)
525               (return (gnus-registry-fetch-extra-entry crumb entry id)))))))))
526
527 (defun gnus-registry-fetch-extra-entry (alist &optional entry id)
528   "Get the extra data of a message, or a specific entry in it.
529 Update the entry cache if needed."
530   (if (and entry id)
531       (let ((entry-cache (gethash entry gnus-registry-hashtb))
532             entree)
533         (when gnus-registry-entry-caching
534           ;; create the hash table
535           (unless (hash-table-p entry-cache)
536             (setq entry-cache (make-hash-table
537                                :size 4096
538                                :test 'equal))
539             (puthash entry entry-cache gnus-registry-hashtb))
540
541           ;; get the entree from the hash table or from the alist
542           (setq entree (gethash id entry-cache)))
543         
544         (unless entree
545           (setq entree (assq entry alist))
546           (when gnus-registry-entry-caching
547             (puthash id entree entry-cache)))
548         entree)
549     alist))
550
551 (defun gnus-registry-store-extra (id extra)
552   "Store the extra data of a message, based on the message ID.
553 The message must have at least one group name."
554   (when (gnus-registry-group-count id)
555     ;; we now know the trail has at least 1 group name, so it's not empty
556     (let ((trail (gethash id gnus-registry-hashtb))
557           (old-extra (gnus-registry-fetch-extra id))
558           entry-cache)
559       (dolist (crumb trail)
560         (unless (stringp crumb)
561           (dolist (entry crumb)
562             (setq entry-cache (gethash (car entry) gnus-registry-hashtb))
563           (when entry-cache
564             (remhash id entry-cache))))
565       (puthash id (cons extra (delete old-extra trail))
566                gnus-registry-hashtb)
567       (setq gnus-registry-dirty t)))))
568
569 (defun gnus-registry-store-extra-entry (id key value)
570   "Put a specific entry in the extras field of the registry entry for id."
571   (let* ((extra (gnus-registry-fetch-extra id))
572          (alist (cons (cons key value)
573                  (gnus-assq-delete-all key (gnus-registry-fetch-extra id)))))
574     (gnus-registry-store-extra id alist)))
575
576 (defun gnus-registry-fetch-group (id)
577   "Get the group of a message, based on the message ID.
578 Returns the first place where the trail finds a group name."
579   (when (gnus-registry-group-count id)
580     ;; we now know the trail has at least 1 group name
581     (let ((trail (gethash id gnus-registry-hashtb)))
582       (dolist (crumb trail)
583         (when (stringp crumb)
584           (return (if gnus-registry-use-long-group-names 
585                        crumb 
586                      (gnus-group-short-name crumb))))))))
587
588 (defun gnus-registry-group-count (id)
589   "Get the number of groups of a message, based on the message ID."
590   (let ((trail (gethash id gnus-registry-hashtb)))
591     (if (and trail (listp trail))
592         (apply '+ (mapcar (lambda (x) (if (stringp x) 1 0)) trail))
593       0)))
594
595 (defun gnus-registry-delete-group (id group)
596   "Delete a group for a message, based on the message ID."
597   (when group
598     (when id
599       (let ((trail (gethash id gnus-registry-hashtb))
600             (group (gnus-group-short-name group)))
601         (puthash id (if trail
602                         (delete group trail)
603                       nil)
604                  gnus-registry-hashtb))
605       ;; now, clear the entry if there are no more groups
606       (when gnus-registry-trim-articles-without-groups
607         (unless (gnus-registry-group-count id)
608           (gnus-registry-delete-id id)))
609       (gnus-registry-store-extra-entry id 'mtime (current-time)))))
610
611 (defun gnus-registry-delete-id (id)
612   "Delete a message ID from the registry."
613   (when (stringp id)
614     (remhash id gnus-registry-hashtb)
615     (maphash
616      (lambda (key value)
617        (when (hash-table-p value)
618          (remhash id value)))
619      gnus-registry-hashtb)))
620
621 (defun gnus-registry-add-group (id group &optional subject sender)
622   "Add a group for a message, based on the message ID."
623   (when group
624     (when (and id
625                (not (string-match "totally-fudged-out-message-id" id)))
626       (let ((full-group group)
627             (group (if gnus-registry-use-long-group-names 
628                        group 
629                      (gnus-group-short-name group))))
630         (gnus-registry-delete-group id group)
631
632         (unless gnus-registry-use-long-group-names ;; unnecessary in this case
633           (gnus-registry-delete-group id full-group))
634
635         (let ((trail (gethash id gnus-registry-hashtb)))
636           (puthash id (if trail
637                           (cons group trail)
638                         (list group))
639                    gnus-registry-hashtb)
640
641           (when (and (gnus-registry-track-subject-p)
642                      subject)
643             (gnus-registry-store-extra-entry
644              id 
645              'subject 
646              (gnus-registry-simplify-subject subject)))
647           (when (and (gnus-registry-track-sender-p)
648                      sender)
649             (gnus-registry-store-extra-entry
650              id 
651              'sender
652              sender))
653           
654           (gnus-registry-store-extra-entry id 'mtime (current-time)))))))
655
656 (defun gnus-registry-clear ()
657   "Clear the Gnus registry."
658   (interactive)
659   (setq gnus-registry-alist nil)
660   (setq gnus-registry-hashtb (alist-to-hashtable gnus-registry-alist))
661   (setq gnus-registry-dirty t))
662
663 ;;;###autoload
664 (defun gnus-registry-initialize ()
665   (interactive)
666   (setq gnus-registry-install t)
667   (gnus-registry-install-hooks)
668   (gnus-registry-read))
669
670 ;;;###autoload
671 (defun gnus-registry-install-hooks ()
672   "Install the registry hooks."
673   (interactive)
674   (add-hook 'gnus-summary-article-move-hook 'gnus-registry-action) 
675   (add-hook 'gnus-summary-article-delete-hook 'gnus-registry-action)
676   (add-hook 'gnus-summary-article-expire-hook 'gnus-registry-action)
677   (add-hook 'nnmail-spool-hook 'gnus-registry-spool-action)
678   
679   (add-hook 'gnus-save-newsrc-hook 'gnus-registry-save)
680   (add-hook 'gnus-read-newsrc-el-hook 'gnus-registry-read)
681
682   (add-hook 'gnus-summary-prepare-hook 'gnus-registry-register-message-ids))
683
684 (defun gnus-registry-unload-hook ()
685   "Uninstall the registry hooks."
686   (interactive)
687   (remove-hook 'gnus-summary-article-move-hook 'gnus-registry-action) 
688   (remove-hook 'gnus-summary-article-delete-hook 'gnus-registry-action)
689   (remove-hook 'gnus-summary-article-expire-hook 'gnus-registry-action)
690   (remove-hook 'nnmail-spool-hook 'gnus-registry-spool-action)
691   
692   (remove-hook 'gnus-save-newsrc-hook 'gnus-registry-save)
693   (remove-hook 'gnus-read-newsrc-el-hook 'gnus-registry-read)
694
695   (remove-hook 'gnus-summary-prepare-hook 'gnus-registry-register-message-ids))
696
697 (when gnus-registry-install
698   (gnus-registry-install-hooks)
699   (gnus-registry-read))
700
701 ;; TODO: a lot of things
702
703 (provide 'gnus-registry)
704
705 ;;; gnus-registry.el ends here