*** empty log message ***
[gnus] / lisp / gnus-score.el
1 ;;; gnus-score --- scoring code for Gnus
2 ;; Copyright (C) 1995 Free Software Foundation, Inc.
3
4 ;; Author: Per Abrahamsen <amanda@iesd.auc.dk>
5 ;;      Lars Magne Ingebrigtsen <larsi@ifi.uio.no>
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
22 ;; the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA.
23
24 ;;; Commentary:
25
26 ;;; Code:
27
28 (require 'gnus)
29
30 (defvar gnus-score-find-score-files-function 'gnus-score-find-bnews
31   "*Function used to find SCORE files.
32 The function will be called with the group name as the argument, and
33 should return a list of score files to apply to that group.  The score
34 files do not actually have to exist.
35
36 Predefined values are:
37
38 gnus-score-find-single: Only apply the group's own SCORE file.
39 gnus-score-find-hierarchical: Also apply SCORE files from parent groups.
40 gnus-score-find-bnews: Apply SCORE files whose names matches.
41
42 See the documentation to these functions for more information.
43
44 This variable can also be a list of functions to be called.  Each
45 function should either return a list of score files, or a list of
46 score alists.")
47
48 (defvar gnus-adaptive-file-suffix "ADAPT"
49   "*Suffix of the adaptive score files.")
50
51 (defvar gnus-score-expiry-days 7
52   "*Number of days before unused score file entries are expired.")
53
54 (defvar gnus-orphan-score nil
55   "*All orphans get this score added. Set in the score file.")
56
57 (defvar gnus-default-adaptive-score-alist
58   '((gnus-unread-mark)
59     (gnus-ticked-mark (from 4))
60     (gnus-dormant-mark (from 5))
61     (gnus-del-mark (from -4) (subject -1))
62     (gnus-read-mark (from 4) (subject 2))
63     (gnus-expirable-mark (from -1) (subject -1))
64     (gnus-killed-mark (from -1) (subject -3))
65     (gnus-kill-file-mark)
66     (gnus-catchup-mark (from -1) (subject -1)))
67   "*Alist of marks and scores.")
68
69 \f
70
71 ;; Internal variables.
72
73 (defvar gnus-internal-global-score-files nil)
74 (defvar gnus-score-file-list nil)
75 (defvar gnus-current-score-file nil)
76 (defvar gnus-adaptive-score-alist gnus-default-adaptive-score-alist)
77
78 (defvar gnus-score-alist nil
79   "Alist containing score information.
80 The keys can be symbols or strings.  The following symbols are defined. 
81
82 touched: If this alist has been modified.
83 mark:    Automatically mark articles below this.
84 expunge: Automatically expunge articles below this.
85 files:   List of other SCORE files to load when loading this one.
86 eval:    Sexp to be evaluated when the score file is loaded.
87
88 String entries have the form (HEADER (MATCH TYPE SCORE DATE) ...) 
89 where HEADER is the header being scored, MATCH is the string we are
90 looking for, TYPE is a flag indicating whether it should use regexp or
91 substring matching, SCORE is the score to add and DATE is the date
92 of the last succesful match.")
93
94 (defvar gnus-score-cache nil)
95 (defvar gnus-scores-articles nil)
96 (defvar gnus-scores-exclude-files nil)
97 (defvar gnus-header-index nil)
98 (defvar gnus-score-index nil)
99
100 (autoload 'gnus-uu-ctl-map "gnus-uu" nil nil 'keymap)
101
102 ;;; Summary mode score maps.
103
104 (defvar gnus-summary-score-map nil)
105 (defvar gnus-summary-increase-map nil)
106 (defvar gnus-summary-inc-subject-map nil)
107 (defvar gnus-summary-inc-fuzzy-map nil)
108 (defvar gnus-summary-inc-author-map nil)
109 (defvar gnus-summary-inc-body-map nil)
110 (defvar gnus-summary-inc-id-map nil)
111 (defvar gnus-summary-inc-xref-map nil)
112 (defvar gnus-summary-inc-thread-map nil)
113 (defvar gnus-summary-inc-fol-map nil)
114 (defvar gnus-summary-lower-map nil)
115 (defvar gnus-summary-low-subject-map nil)
116 (defvar gnus-summary-low-fuzzy-map nil)
117 (defvar gnus-summary-low-author-map nil)
118 (defvar gnus-summary-low-body-map nil)
119 (defvar gnus-summary-low-id-map nil)
120 (defvar gnus-summary-low-xref-map nil)
121 (defvar gnus-summary-low-thread-map nil)
122 (defvar gnus-summary-low-fol-map nil)
123
124   (define-prefix-command 'gnus-summary-score-map)
125   (define-key gnus-summary-various-map "S" 'gnus-summary-score-map)
126   (define-key gnus-summary-score-map "s" 'gnus-summary-set-score)
127   (define-key gnus-summary-score-map "c" 'gnus-score-change-score-file)
128   (define-key gnus-summary-score-map "m" 'gnus-score-set-mark-below)
129   (define-key gnus-summary-score-map "x" 'gnus-score-set-expunge-below)
130   (define-key gnus-summary-score-map "e" 'gnus-score-edit-alist)
131   (define-key gnus-summary-score-map "f" 'gnus-score-edit-file)
132
133
134   (define-prefix-command 'gnus-summary-increase-map)
135   (define-key gnus-summary-mode-map "I" gnus-summary-increase-map)
136
137   (define-key gnus-summary-increase-map "i" 'gnus-summary-raise-same-subject-and-select)
138   (define-key gnus-summary-increase-map "I" 'gnus-summary-raise-same-subject)
139   (define-key gnus-summary-increase-map "\C-i" 'gnus-summary-raise-score)
140
141   (define-prefix-command 'gnus-summary-inc-subject-map)
142   (define-key gnus-summary-increase-map "s" gnus-summary-inc-subject-map)
143   (define-key gnus-summary-increase-map "S" 'gnus-summary-temporarily-raise-by-subject)
144   (define-key gnus-summary-inc-subject-map "s" 'gnus-summary-temporarily-raise-by-subject)
145   (define-key gnus-summary-inc-subject-map "S" 'gnus-summary-raise-by-subject)
146   (define-key gnus-summary-inc-subject-map "i" 'gnus-summary-immediately-raise-by-subject)
147   (define-key gnus-summary-inc-subject-map "t" 'gnus-summary-temporarily-raise-by-subject)
148   (define-key gnus-summary-inc-subject-map "p" 'gnus-summary-raise-by-subject)
149
150   (define-prefix-command 'gnus-summary-inc-fuzzy-map)
151   (define-key gnus-summary-increase-map "z" gnus-summary-inc-fuzzy-map)
152   (define-key gnus-summary-increase-map "Z" 'gnus-summary-temporarily-raise-by-fuzzy)
153   (define-key gnus-summary-inc-fuzzy-map "z" 'gnus-summary-temporarily-raise-by-fuzzy)
154   (define-key gnus-summary-inc-fuzzy-map "Z" 'gnus-summary-raise-by-fuzzy)
155   (define-key gnus-summary-inc-fuzzy-map "i" 'gnus-summary-immediately-raise-by-fuzzy)
156   (define-key gnus-summary-inc-fuzzy-map "t" 'gnus-summary-temporarily-raise-by-fuzzy)
157   (define-key gnus-summary-inc-fuzzy-map "p" 'gnus-summary-raise-by-fuzzy)
158
159   (define-prefix-command 'gnus-summary-inc-author-map)
160   (define-key gnus-summary-increase-map "a" 'gnus-summary-inc-author-map)
161   (define-key gnus-summary-increase-map "A" 'gnus-summary-temporarily-raise-by-author)
162   (define-key gnus-summary-inc-author-map "a" 'gnus-summary-temporarily-raise-by-author)
163   (define-key gnus-summary-inc-author-map "A" 'gnus-summary-raise-by-author)
164   (define-key gnus-summary-inc-author-map "i" 'gnus-summary-immediately-raise-by-author)
165   (define-key gnus-summary-inc-author-map "t" 'gnus-summary-temporarily-raise-by-author)
166   (define-key gnus-summary-inc-author-map "p" 'gnus-summary-raise-by-author)
167
168   (define-prefix-command 'gnus-summary-inc-body-map)
169   (define-key gnus-summary-increase-map "b" 'gnus-summary-inc-body-map)
170   (define-key gnus-summary-increase-map "B" 'gnus-summary-temporarily-raise-by-body)
171   (define-key gnus-summary-inc-body-map "b" 'gnus-summary-temporarily-raise-by-body)
172   (define-key gnus-summary-inc-body-map "B" 'gnus-summary-raise-by-body)
173   (define-key gnus-summary-inc-body-map "i" 'gnus-summary-immediately-raise-by-body)
174   (define-key gnus-summary-inc-body-map "t" 'gnus-summary-temporarily-raise-by-body)
175   (define-key gnus-summary-inc-body-map "p" 'gnus-summary-raise-by-body)
176
177   (define-prefix-command 'gnus-summary-inc-id-map)
178   (define-key gnus-summary-increase-map "i" 'gnus-summary-inc-id-map)
179   (define-key gnus-summary-increase-map "I" 'gnus-summary-temporarily-raise-by-id)
180   (define-key gnus-summary-inc-id-map "i" 'gnus-summary-temporarily-raise-by-id)
181   (define-key gnus-summary-inc-id-map "I" 'gnus-summary-raise-by-id)
182   (define-key gnus-summary-inc-id-map "i" 'gnus-summary-immediately-raise-by-id)
183   (define-key gnus-summary-inc-id-map "t" 'gnus-summary-temporarily-raise-by-id)
184   (define-key gnus-summary-inc-id-map "p" 'gnus-summary-raise-by-id)
185
186   (define-prefix-command 'gnus-summary-inc-thread-map)
187   (define-key gnus-summary-increase-map "t" 'gnus-summary-inc-thread-map)
188   (define-key gnus-summary-increase-map "T" 'gnus-summary-temporarily-raise-by-thread)
189   (define-key gnus-summary-inc-thread-map "t" 'gnus-summary-temporarily-raise-by-thread)
190   (define-key gnus-summary-inc-thread-map "T" 'gnus-summary-raise-by-thread)
191   (define-key gnus-summary-inc-thread-map "i" 'gnus-summary-immediately-raise-by-thread)
192   (define-key gnus-summary-inc-thread-map "t" 'gnus-summary-temporarily-raise-by-thread)
193   (define-key gnus-summary-inc-thread-map "p" 'gnus-summary-raise-by-thread)
194
195   (define-prefix-command 'gnus-summary-inc-xref-map)
196   (define-key gnus-summary-increase-map "x" 'gnus-summary-inc-xref-map)
197   (define-key gnus-summary-increase-map "X" 'gnus-summary-temporarily-raise-by-xref)
198   (define-key gnus-summary-inc-xref-map "x" 'gnus-summary-temporarily-raise-by-xref)
199   (define-key gnus-summary-inc-xref-map "X" 'gnus-summary-raise-by-xref)
200   (define-key gnus-summary-inc-xref-map "i" 'gnus-summary-immediately-raise-by-xref)
201   (define-key gnus-summary-inc-xref-map "t" 'gnus-summary-temporarily-raise-by-xref)
202   (define-key gnus-summary-inc-xref-map "p" 'gnus-summary-raise-by-xref)
203
204   (define-prefix-command 'gnus-summary-inc-fol-map)
205   (define-key gnus-summary-increase-map "f" 'gnus-summary-inc-fol-map)
206   (define-key gnus-summary-increase-map "F" 'gnus-summary-temporarily-raise-followups-to-author)
207   (define-key gnus-summary-inc-fol-map "f" 'gnus-summary-temporarily-raise-followups-to-author)
208   (define-key gnus-summary-inc-fol-map "F" 'gnus-summary-raise-followups-to-author)
209   (define-key gnus-summary-inc-fol-map "i" 'gnus-summary-immediately-raise-followups-to-author)
210   (define-key gnus-summary-inc-fol-map "t" 'gnus-summary-temporarily-raise-followups-to-author)
211   (define-key gnus-summary-inc-fol-map "p" 'gnus-summary-raise-followups-to-author)
212
213
214   (define-prefix-command 'gnus-summary-lower-map)
215   (define-key gnus-summary-mode-map "L" 'gnus-summary-lower-map)
216
217   (define-key gnus-summary-lower-map "l" 'gnus-summary-lower-same-subject-and-select)
218   (define-key gnus-summary-lower-map "L" 'gnus-summary-lower-same-subject)
219   (define-key gnus-summary-lower-map "\C-l" 'gnus-summary-lower-score)
220
221   (define-prefix-command 'gnus-summary-low-subject-map)
222   (define-key gnus-summary-lower-map "s" 'gnus-summary-low-subject-map)
223   (define-key gnus-summary-lower-map "S" 'gnus-summary-temporarily-lower-by-subject)
224   (define-key gnus-summary-low-subject-map "s" 'gnus-summary-temporarily-lower-by-subject)
225   (define-key gnus-summary-low-subject-map "S" 'gnus-summary-lower-by-subject)
226   (define-key gnus-summary-low-subject-map "i" 'gnus-summary-immediately-lower-by-subject)
227   (define-key gnus-summary-low-subject-map "t" 'gnus-summary-temporarily-lower-by-subject)
228   (define-key gnus-summary-low-subject-map "p" 'gnus-summary-lower-by-subject)
229
230   (define-prefix-command 'gnus-summary-low-fuzzy-map)
231   (define-key gnus-summary-lower-map "z" 'gnus-summary-low-fuzzy-map)
232   (define-key gnus-summary-lower-map "Z" 'gnus-summary-temporarily-lower-by-fuzzy)
233   (define-key gnus-summary-low-fuzzy-map "z" 'gnus-summary-temporarily-lower-by-fuzzy)
234   (define-key gnus-summary-low-fuzzy-map "Z" 'gnus-summary-lower-by-fuzzy)
235   (define-key gnus-summary-low-fuzzy-map "i" 'gnus-summary-immediately-lower-by-fuzzy)
236   (define-key gnus-summary-low-fuzzy-map "t" 'gnus-summary-temporarily-lower-by-fuzzy)
237   (define-key gnus-summary-low-fuzzy-map "p" 'gnus-summary-lower-by-fuzzy)
238
239   (define-prefix-command 'gnus-summary-low-body-map)
240   (define-key gnus-summary-lower-map "b" 'gnus-summary-low-body-map)
241   (define-key gnus-summary-lower-map "B" 'gnus-summary-temporarily-lower-by-body)
242   (define-key gnus-summary-low-body-map "b" 'gnus-summary-temporarily-lower-by-body)
243   (define-key gnus-summary-low-body-map "B" 'gnus-summary-lower-by-body)
244   (define-key gnus-summary-low-body-map "i" 'gnus-summary-immediately-lower-by-body)
245   (define-key gnus-summary-low-body-map "t" 'gnus-summary-temporarily-lower-by-body)
246   (define-key gnus-summary-low-body-map "p" 'gnus-summary-lower-by-body)
247
248   (define-prefix-command 'gnus-summary-low-author-map)
249   (define-key gnus-summary-lower-map "a" 'gnus-summary-low-author-map)
250   (define-key gnus-summary-lower-map "A" 'gnus-summary-temporarily-lower-by-author)
251   (define-key gnus-summary-low-author-map "a" 'gnus-summary-temporarily-lower-by-author)
252   (define-key gnus-summary-low-author-map "A" 'gnus-summary-lower-by-author)
253   (define-key gnus-summary-low-author-map "i" 'gnus-summary-immediately-lower-by-author)
254   (define-key gnus-summary-low-author-map "t" 'gnus-summary-temporarily-lower-by-author)
255   (define-key gnus-summary-low-author-map "p" 'gnus-summary-lower-by-author)
256
257   (define-prefix-command 'gnus-summary-low-id-map)
258   (define-key gnus-summary-lower-map "i" 'gnus-summary-low-id-map)
259   (define-key gnus-summary-lower-map "I" 'gnus-summary-temporarily-lower-by-id)
260   (define-key gnus-summary-low-id-map "i" 'gnus-summary-temporarily-lower-by-id)
261   (define-key gnus-summary-low-id-map "I" 'gnus-summary-lower-by-id)
262   (define-key gnus-summary-low-id-map "i" 'gnus-summary-immediately-lower-by-id)
263   (define-key gnus-summary-low-id-map "t" 'gnus-summary-temporarily-lower-by-id)
264   (define-key gnus-summary-low-id-map "p" 'gnus-summary-lower-by-id)
265
266   (define-prefix-command 'gnus-summary-low-thread-map)
267   (define-key gnus-summary-lower-map "t" 'gnus-summary-low-thread-map)
268   (define-key gnus-summary-lower-map "T" 'gnus-summary-temporarily-lower-by-thread)
269   (define-key gnus-summary-low-thread-map "t" 'gnus-summary-temporarily-lower-by-thread)
270   (define-key gnus-summary-low-thread-map "T" 'gnus-summary-lower-by-thread)
271   (define-key gnus-summary-low-thread-map "i" 'gnus-summary-immediately-lower-by-thread)
272   (define-key gnus-summary-low-thread-map "t" 'gnus-summary-temporarily-lower-by-thread)
273   (define-key gnus-summary-low-thread-map "p" 'gnus-summary-lower-by-thread)
274
275   (define-prefix-command 'gnus-summary-low-xref-map)
276   (define-key gnus-summary-lower-map "x" 'gnus-summary-low-xref-map)
277   (define-key gnus-summary-lower-map "X" 'gnus-summary-temporarily-lower-by-xref)
278   (define-key gnus-summary-low-xref-map "x" 'gnus-summary-temporarily-lower-by-xref)
279   (define-key gnus-summary-low-xref-map "X" 'gnus-summary-lower-by-xref)
280   (define-key gnus-summary-low-xref-map "i" 'gnus-summary-immediately-lower-by-xref)
281   (define-key gnus-summary-low-xref-map "t" 'gnus-summary-temporarily-lower-by-xref)
282   (define-key gnus-summary-low-xref-map "p" 'gnus-summary-lower-by-xref)
283
284   (define-prefix-command 'gnus-summary-low-fol-map)
285   (define-key gnus-summary-lower-map "f" 'gnus-summary-low-fol-map)
286   (define-key gnus-summary-lower-map "F" 'gnus-summary-temporarily-lower-followups-to-author)
287   (define-key gnus-summary-low-fol-map "f" 'gnus-summary-temporarily-lower-followups-to-author)
288   (define-key gnus-summary-low-fol-map "F" 'gnus-summary-lower-followups-to-author)
289   (define-key gnus-summary-low-fol-map "i" 'gnus-summary-immediately-lower-followups-to-author)
290   (define-key gnus-summary-low-fol-map "t" 'gnus-summary-temporarily-lower-followups-to-author)
291   (define-key gnus-summary-low-fol-map "p" 'gnus-summary-lower-followups-to-author)
292
293
294 ;; Summary score file commands
295
296 ;; Much modification of the kill (ahem, score) code and lots of the
297 ;; functions are written by Per Abrahamsen <amanda@iesd.auc.dk>.
298
299 (defun gnus-summary-header (header)
300   ;; Return HEADER for current articles, or error.
301   (let ((article (gnus-summary-article-number)))
302     (if article
303         (aref (gnus-get-header-by-number article)
304               (nth 1 (assoc header gnus-header-index)))
305       (error "No article on current line"))))
306
307 (defun gnus-summary-score-entry 
308   (header match type score date &optional prompt silent)
309   "Enter score file entry.
310 HEADER is the header being scored.
311 MATCH is the string we are looking for.
312 TYPE is a flag indicating if it is a regexp or substring.
313 SCORE is the score to add.
314 DATE is the expire date, or nil for no expire, or 'now for immediate expire.
315 If optional argument `PROMPT' is non-nil, allow user to edit match.
316 If optional argument `SILENT' is nil, show effect of score entry."
317   (interactive
318    (list (completing-read "Header: "
319                           gnus-header-index
320                           (lambda (x) (fboundp (nth 2 x)))
321                           t)
322          (read-string "Match: ")
323          (y-or-n-p "Use regexp match? ")
324          (prefix-numeric-value current-prefix-arg)
325          (cond ((not (y-or-n-p "Add to SCORE file? "))
326                 'now)
327                ((y-or-n-p "Expire kill? ")
328                 (current-time-string))
329                (t nil))))
330   (let ((score (gnus-score-default score))
331         (header (downcase header)))
332     (and prompt (setq match (read-string 
333                              (format "Match %s on %s, %s: " 
334                                      (cond ((eq date 'now)
335                                             "now")
336                                            ((stringp date)
337                                             "temp")
338                                            (t "permanent"))
339                                      header
340                                      (if (< score 0) "lower" "raise"))
341                              match)))
342     (and (>= (nth 1 (assoc header gnus-header-index)) 0)
343          (not silent)
344          (gnus-summary-score-effect header match type score))
345     (if (eq date 'now)
346         ()
347       (and (= score gnus-score-interactive-default-score)
348            (setq score nil))
349       (let ((new (cond ((eq type 'f)
350                     (list (gnus-simplify-subject-fuzzy match)
351                           score (and date (gnus-day-number date)) type))
352                    (type
353                     (list match score (and date (gnus-day-number date)) type))
354                    (date
355                     (list match score (gnus-day-number date)))
356                    (score
357                     (list match score))
358                    (t
359                     (list match))))
360             (old (gnus-score-get header))
361             elem)
362         ;; We see whether we can collapse some score entries.
363         ;; This isn't quite correct, because there may be more elements
364         ;; later on with the same key that have matching elems... Hm.
365         (if (and old
366                  (setq elem (assoc match old))
367                  (eq (nth 3 elem) (nth 3 new))
368                  (or (and (numberp (nth 2 elem)) (numberp (nth 2 new)))
369                      (and (not (nth 2 elem)) (not (nth 2 new)))))
370             ;; Yup, we just add this new score to the old elem.
371             (setcar (cdr elem) (+ (or (nth 1 elem) 
372                                       gnus-score-interactive-default-score)
373                                   (or (nth 1 new)
374                                       gnus-score-interactive-default-score)))
375           ;; Nope, we have to add a new elem.
376           (gnus-score-set header (if old (cons new old) (list new)))))
377       (gnus-score-set 'touched '(t)))))
378
379 (defun gnus-summary-score-effect (header match type score)
380   "Simulate the effect of a score file entry.
381 HEADER is the header being scored.
382 MATCH is the string we are looking for.
383 TYPE is a flag indicating if it is a regexp or substring.
384 SCORE is the score to add."
385   (interactive (list (completing-read "Header: "
386                                       gnus-header-index
387                                       (lambda (x) (fboundp (nth 2 x)))
388                                       t)
389                      (read-string "Match: ")
390                      (y-or-n-p "Use regexp match? ")
391                      (prefix-numeric-value current-prefix-arg)))
392   (save-excursion
393     (or (and (stringp match) (> (length match) 0))
394       (error "No match"))
395     (goto-char (point-min))
396     (let ((regexp (cond ((eq type 'f)
397                          (gnus-simplify-subject-fuzzy match))
398                         (type match)
399                         (t (concat "\\`.*" (regexp-quote match) ".*\\'")))))
400       (while (not (eobp))
401         (let ((content (gnus-summary-header header))
402               (case-fold-search t))
403           (and content
404                (if (if (eq type 'f)
405                        (string-equal (gnus-simplify-subject-fuzzy content)
406                                      regexp)
407                      (string-match regexp content))
408                    (gnus-summary-raise-score score))))
409         (beginning-of-line 2)))))
410
411 (defun gnus-summary-score-crossposting (score date)
412    ;; Enter score file entry for current crossposting.
413    ;; SCORE is the score to add.
414    ;; DATE is the expire date.
415    (let ((xref (gnus-summary-header "xref"))
416          (start 0)
417          group)
418      (or xref (error "This article is not crossposted"))
419      (while (string-match " \\([^ \t]+\\):" xref start)
420        (setq start (match-end 0))
421        (if (not (string= 
422                  (setq group 
423                        (substring xref (match-beginning 1) (match-end 1)))
424                  gnus-newsgroup-name))
425            (gnus-summary-score-entry
426             "xref" (concat " " group ":") nil score date t)))))
427
428 (defun gnus-summary-immediately-lower-by-subject (level)
429   "Immediately lower score by LEVEL for current subject.
430 See `gnus-score-expiry-days'."
431   (interactive "P")
432   (gnus-summary-score-entry
433    "subject" (gnus-simplify-subject-re (gnus-summary-header "subject"))
434    nil (- (gnus-score-default level)) 'now t))
435
436 (defun gnus-summary-immediately-lower-by-fuzzy (level)
437   "Immediately lower score by LEVEL for current fuzzy subject.
438 See `gnus-score-expiry-days'."
439   (interactive "P")
440   (gnus-summary-score-entry
441    "subject" (gnus-summary-header "subject")
442    'f (- (gnus-score-default level)) 'now))
443
444 (defun gnus-summary-immediately-lower-by-author (level)
445   "Immediately lower score by LEVEL for current author.
446 See `gnus-score-expiry-days'."
447   (interactive "P")
448   (gnus-summary-score-entry
449    "from" (gnus-summary-header "from") nil (- (gnus-score-default level)) 
450    'now t))
451
452 (defun gnus-summary-immediately-lower-by-body (level)
453   "Immediately lower score by LEVEL for a match on the body of the article.
454 See `gnus-score-expiry-days'."
455   (interactive "P")
456   (error "Not yet implemented")
457   (gnus-summary-score-entry 
458    "body" "" nil (- (gnus-score-default level)) 'now t))
459
460 (defun gnus-summary-immediately-lower-by-id (level)
461   "Immediately lower score by LEVEL for current message-id.
462 See `gnus-score-expiry-days'."
463   (interactive "P")
464   (gnus-summary-score-entry
465    "message-id" (gnus-summary-header "message-id") 
466    nil (- (gnus-score-default level)) 'now))
467
468 (defun gnus-summary-immediately-lower-by-xref (level)
469   "Immediately lower score by LEVEL for current xref.
470 See `gnus-score-expiry-days'."
471   (interactive "P")
472   (gnus-summary-score-crossposting (- (gnus-score-default level)) 'now))
473
474 (defun gnus-summary-immediately-lower-by-thread (level)
475   "Immediately lower score by LEVEL for current thread.
476 See `gnus-score-expiry-days'."
477   (interactive "P")
478   (gnus-summary-score-entry
479    "references" (gnus-summary-header "message-id")
480    nil (- (gnus-score-default level)) 'now))
481
482 (defun gnus-summary-immediately-lower-followups-to-author (level)
483   "Lower score by LEVEL for all followups to the current author."
484   (interactive "P")
485   (error "Not yet implemented")
486   (gnus-summary-score-entry 
487    "followup" (gnus-summary-header "from") 
488    nil (- (gnus-score-default level)) 'now t t))
489
490 (defun gnus-summary-temporarily-lower-by-subject (level)
491   "Temporarily lower score by LEVEL for current subject.
492 See `gnus-score-expiry-days'."
493   (interactive "P")
494   (gnus-summary-score-entry
495    "subject" (gnus-simplify-subject-re (gnus-summary-header "subject"))
496    nil (- (gnus-score-default level))
497    (current-time-string) t))
498
499 (defun gnus-summary-temporarily-lower-by-fuzzy (level)
500   "Temporarily lower score by LEVEL for current fuzzy subject.
501 See `gnus-score-expiry-days'."
502   (interactive "P")
503   (gnus-summary-score-entry
504    "subject" (gnus-summary-header "subject")
505    'f (- (gnus-score-default level))
506    (current-time-string)))
507
508 (defun gnus-summary-temporarily-lower-by-author (level)
509   "Temporarily lower score by LEVEL for current author.
510 See `gnus-score-expiry-days'."
511   (interactive "P")
512   (gnus-summary-score-entry
513    "from" (gnus-summary-header "from") nil (- (gnus-score-default level)) 
514    (current-time-string) t))
515
516 (defun gnus-summary-temporarily-lower-by-body (level)
517   "Temporarily lower score by LEVEL for a match on the body of the article.
518 See `gnus-score-expiry-days'."
519   (interactive "P")
520   (gnus-summary-score-entry
521    "body" "" nil (- (gnus-score-default level)) (current-time-string) t))
522
523 (defun gnus-summary-temporarily-lower-by-id (level)
524   "Temporarily lower score by LEVEL for current message-id.
525 See `gnus-score-expiry-days'."
526   (interactive "P")
527   (gnus-summary-score-entry
528    "message-id" (gnus-summary-header "message-id") 
529    nil (- (gnus-score-default level)) 
530    (current-time-string)))
531
532 (defun gnus-summary-temporarily-lower-by-xref (level)
533   "Temporarily lower score by LEVEL for current xref.
534 See `gnus-score-expiry-days'."
535   (interactive "P")
536   (gnus-summary-score-crossposting 
537    (- (gnus-score-default level)) (current-time-string)))
538
539 (defun gnus-summary-temporarily-lower-by-thread (level)
540   "Temporarily lower score by LEVEL for current thread.
541 See `gnus-score-expiry-days'."
542   (interactive "P")
543   (gnus-summary-score-entry
544    "references" (gnus-summary-header "message-id")
545    nil (- (gnus-score-default level)) (current-time-string)))
546
547 (defun gnus-summary-temporarily-lower-followups-to-author (level)
548   "Lower score by LEVEL for all followups to the current author."
549   (interactive "P")
550   (gnus-summary-score-entry 
551    "followup" (gnus-summary-header "from")
552    nil (- (gnus-score-default level)) (current-time-string) t t))
553
554 (defun gnus-summary-lower-by-subject (level)
555   "Lower score by LEVEL for current subject."
556   (interactive "P")
557   (gnus-summary-score-entry
558    "subject" (gnus-simplify-subject-re (gnus-summary-header "subject"))
559    nil (- (gnus-score-default level)) 
560    nil t))
561
562 (defun gnus-summary-lower-by-fuzzy (level)
563   "Lower score by LEVEL for current fuzzy subject."
564   (interactive "P")
565   (gnus-summary-score-entry
566    "subject" (gnus-summary-header "subject")
567    'f (- (gnus-score-default level)) 
568    nil))
569
570 (defun gnus-summary-lower-by-author (level)
571   "Lower score by LEVEL for current author."
572   (interactive "P")
573   (gnus-summary-score-entry
574    "from" (gnus-summary-header "from") nil 
575    (- (gnus-score-default level)) nil t))
576
577 (defun gnus-summary-lower-by-body (level)
578   "Lower score by LEVEL for a match on the body of the article."
579   (interactive "P")
580   (gnus-summary-score-entry
581    "body" "" nil (- (gnus-score-default level)) nil t))
582
583 (defun gnus-summary-lower-by-id (level)
584   "Lower score by LEVEL for current message-id."
585   (interactive "P")
586   (gnus-summary-score-entry
587    "message-id" (gnus-summary-header "message-id") nil 
588    (- (gnus-score-default level)) nil))
589
590 (defun gnus-summary-lower-by-xref (level)
591   "Lower score by LEVEL for current xref."
592   (interactive "P")
593   (gnus-summary-score-crossposting (- (gnus-score-default level)) nil))
594
595 (defun gnus-summary-lower-followups-to-author (level)
596   "Lower score by LEVEL for all followups to the current author."
597   (interactive "P")
598   (gnus-summary-score-entry 
599    "followup" (gnus-summary-header "from")
600    nil (- (gnus-score-default level)) nil t t))
601
602 (defun gnus-summary-immediately-raise-by-subject (level)
603   "Immediately raise score by LEVEL for current subject.
604 See `gnus-score-expiry-days'."
605   (interactive "P")
606   (gnus-summary-score-entry
607    "subject" (gnus-simplify-subject-re (gnus-summary-header "subject"))
608    nil level 'now t))
609
610 (defun gnus-summary-immediately-raise-by-fuzzy (level)
611   "Immediately raise score by LEVEL for current fuzzy subject.
612 See `gnus-score-expiry-days'."
613   (interactive "P")
614   (gnus-summary-score-entry
615    "subject" (gnus-summary-header "subject")
616    'f level 'now))
617
618 (defun gnus-summary-immediately-raise-by-author (level)
619   "Immediately raise score by LEVEL for current author.
620 See `gnus-score-expiry-days'."
621   (interactive "P")
622   (gnus-summary-score-entry
623    "from" (gnus-summary-header "from") nil level 'now t))
624
625 (defun gnus-summary-immediately-raise-by-body (level)
626   "Immediately raise score by LEVEL for a match on the body of the article.
627 See `gnus-score-expiry-days'."
628   (interactive "P")
629   (error "Not yet implemented")
630   (gnus-summary-score-entry "body" "" nil level 'now t))
631
632 (defun gnus-summary-immediately-raise-by-id (level)
633   "Immediately raise score by LEVEL for current message-id.
634 See `gnus-score-expiry-days'."
635   (interactive "P")
636   (gnus-summary-score-entry
637    "message-id" (gnus-summary-header "message-id") 
638    nil level 'now))
639
640 (defun gnus-summary-immediately-raise-by-xref (level)
641   "Immediately raise score by LEVEL for current xref.
642 See `gnus-score-expiry-days'."
643   (interactive "P")
644   (gnus-summary-score-crossposting level 'now))
645
646 (defun gnus-summary-immediately-raise-by-thread (level)
647   "Immediately raise score by LEVEL for current thread.
648 See `gnus-score-expiry-days'."
649   (interactive "P")
650   (gnus-summary-score-entry
651    "references" (gnus-summary-header "message-id")
652    nil level 'now))
653
654 (defun gnus-summary-immediately-raise-followups-to-author (level)
655   "Raise score by LEVEL for all followups to the current author."
656   (interactive "P")
657   (error "Not yet implemented")
658   (gnus-summary-score-entry 
659    "followup" (gnus-summary-header "from")
660    nil (gnus-score-default level) 'now t t))
661
662 (defun gnus-summary-temporarily-raise-by-subject (level)
663   "Temporarily raise score by LEVEL for current subject.
664 See `gnus-score-expiry-days'."
665   (interactive "P")
666   (gnus-summary-score-entry
667    "subject" (gnus-simplify-subject-re (gnus-summary-header "subject"))
668    nil level (current-time-string) t))
669
670 (defun gnus-summary-temporarily-raise-by-fuzzy (level)
671   "Temporarily raise score by LEVEL for current fuzzy subject.
672 See `gnus-score-expiry-days'."
673   (interactive "P")
674   (gnus-summary-score-entry
675    "subject" (gnus-summary-header "subject")
676    'f level (current-time-string)))
677
678 (defun gnus-summary-temporarily-raise-by-author (level)
679   "Temporarily raise score by LEVEL for current author.
680 See `gnus-score-expiry-days'."
681   (interactive "P")
682   (gnus-summary-score-entry
683    "from" (gnus-summary-header "from") nil level (current-time-string) t))
684
685 (defun gnus-summary-temporarily-raise-by-body (level)
686   "Temporarily raise score by LEVEL for a match on the body of the article.
687 See `gnus-score-expiry-days'."
688   (interactive "P")
689   (gnus-summary-score-entry "body" "" nil level (current-time-string) t))
690
691 (defun gnus-summary-temporarily-raise-by-id (level)
692   "Temporarily raise score by LEVEL for current message-id.
693 See `gnus-score-expiry-days'."
694   (interactive "P")
695   (gnus-summary-score-entry
696    "message-id" (gnus-summary-header "message-id") 
697    nil level (current-time-string)))
698
699 (defun gnus-summary-temporarily-raise-by-xref (level)
700   "Temporarily raise score by LEVEL for current xref.
701 See `gnus-score-expiry-days'."
702   (interactive "P")
703   (gnus-summary-score-crossposting level (current-time-string)))
704
705 (defun gnus-summary-temporarily-raise-by-thread (level)
706   "Temporarily raise score by LEVEL for current thread.
707 See `gnus-score-expiry-days'."
708   (interactive "P")
709   (gnus-summary-score-entry
710    "references" (gnus-summary-header "message-id")
711    nil level (current-time-string)))
712
713 (defun gnus-summary-temporarily-raise-followups-to-author (level)
714   "Raise score by LEVEL for all followups to the current author."
715   (interactive "P")
716   (gnus-summary-score-entry 
717    "followup" (gnus-summary-header "from")
718    nil (gnus-score-default level) (current-time-string) t t))
719
720 (defun gnus-summary-raise-by-subject (level)
721   "Raise score by LEVEL for current subject."
722   (interactive "P")
723   (gnus-summary-score-entry
724    "subject" (gnus-simplify-subject-re (gnus-summary-header "subject"))
725    nil level nil t))
726
727 (defun gnus-summary-raise-by-fuzzy (level)
728   "Raise score by LEVEL for current fuzzy subject."
729   (interactive "P")
730   (gnus-summary-score-entry
731    "subject" (gnus-summary-header "subject")
732    nil level nil))
733
734 (defun gnus-summary-raise-by-author (level)
735   "Raise score by LEVEL for current author."
736   (interactive "P")
737   (gnus-summary-score-entry
738    "from" (gnus-summary-header "from") nil level nil t))
739
740 (defun gnus-summary-raise-by-body (level)
741   "Raise score by LEVEL for a match on the body of the article."
742   (interactive "P")
743   (gnus-summary-score-entry "body" "" nil level nil t))
744
745 (defun gnus-summary-raise-by-id (level)
746   "Raise score by LEVEL for current message-id."
747   (interactive "P")
748   (gnus-summary-score-entry
749    "message-id" (gnus-summary-header "message-id") nil level nil))
750
751 (defun gnus-summary-raise-by-xref (level)
752   "Raise score by LEVEL for current xref."
753   (interactive "P")
754   (gnus-summary-score-crossposting level nil))
755
756 (defun gnus-summary-raise-followups-to-author (level)
757   "Raise score by LEVEL for all followups to the current author."
758   (interactive "P")
759   (gnus-summary-score-entry 
760    "followup" (gnus-summary-header "from")
761    nil (gnus-score-default level) nil t t))
762
763
764 \f
765 ;;;
766 ;;; Gnus Score Files
767 ;;;
768
769 ;; All score code written by Per Abrahamsen <abraham@iesd.auc.dk>.
770
771 ;; Added by Per Abrahamsen <amanda@iesd.auc.dk>.
772 (defun gnus-score-set-mark-below (score)
773   "Automatically mark articles with score below SCORE as read."
774   (interactive 
775    (list (or (and current-prefix-arg (prefix-numeric-value current-prefix-arg))
776              (string-to-int (read-string "Mark below: ")))))
777   (setq score (or score gnus-summary-default-score 0))
778   (gnus-score-set 'mark (list score))
779   (gnus-score-set 'touched '(t))
780   (setq gnus-summary-mark-below score)
781   (gnus-summary-update-lines))
782
783 (defun gnus-score-set-expunge-below (score)
784   "Automatically expunge articles with score below SCORE."
785   (interactive 
786    (list (or (and current-prefix-arg (prefix-numeric-value current-prefix-arg))
787              (string-to-int (read-string "Expunge below: ")))))
788   (setq score (or score gnus-summary-default-score 0))
789   (gnus-score-set 'expunge (list score))
790   (gnus-score-set 'touched '(t)))
791
792 (defun gnus-score-set (symbol value &optional alist)
793   ;; Set SYMBOL to VALUE in ALIST.
794   (let* ((alist 
795           (or alist 
796               gnus-score-alist
797               (progn
798                 (gnus-score-load (gnus-score-file-name gnus-newsgroup-name))
799                 gnus-score-alist)))
800          (entry (assoc symbol alist)))
801     (cond ((gnus-score-get 'read-only alist)
802            ;; This is a read-only score file, so we do nothing.
803            )
804           (entry
805            (setcdr entry value))
806           ((null alist)
807            (error "Empty alist"))
808           (t
809            (setcdr alist
810                    (cons (cons symbol value) (cdr alist)))))))
811
812 (defun gnus-score-get (symbol &optional alist)
813   ;; Get SYMBOL's definition in ALIST.
814   (cdr (assoc symbol 
815               (or alist 
816                   gnus-score-alist
817                   (progn
818                     (gnus-score-load 
819                      (gnus-score-file-name gnus-newsgroup-name))
820                     gnus-score-alist)))))
821
822 (defun gnus-score-change-score-file (file)
823   "Change current score alist."
824   (interactive (list (completing-read "Score file: " gnus-score-cache)))
825   (gnus-score-load-file file)
826   (gnus-set-mode-line 'summary))
827
828 (defun gnus-score-edit-alist (file)
829   "Edit the current score alist."
830   (interactive (list gnus-current-score-file))
831   (and (buffer-name gnus-summary-buffer) (gnus-score-save))
832   (let ((winconf (current-window-configuration)))
833     (gnus-configure-windows 'article)
834     (pop-to-buffer (find-file-noselect file))
835     (make-local-variable 'gnus-prev-winconf)
836     (setq gnus-prev-winconf winconf))
837   (gnus-message 
838    4 (substitute-command-keys 
839       "\\<gnus-score-mode-map>\\[gnus-score-edit-done] to save edits"))
840   (gnus-score-mode))
841   
842 (defun gnus-score-edit-file (file)
843   "Edit a score file."
844   (interactive 
845    (list (read-file-name "Edit score file: " gnus-kill-files-directory)))
846   (and (buffer-name gnus-summary-buffer) (gnus-score-save))
847   (let ((winconf (current-window-configuration)))
848     (gnus-configure-windows 'article)
849     (pop-to-buffer (find-file-noselect file))
850     (make-local-variable 'gnus-prev-winconf)
851     (setq gnus-prev-winconf winconf))
852   (gnus-message 
853    4 (substitute-command-keys 
854       "\\<gnus-score-mode-map>\\[gnus-score-edit-done] to save edits"))
855   (gnus-score-mode))
856   
857 (defun gnus-score-load-file (file)
858   ;; Load score file FILE.  Returns a list a retrieved score-alists.
859   (setq gnus-kill-files-directory (or gnus-kill-files-directory "~/News/"))
860   (let* ((file (expand-file-name 
861                 (or (and (string-match
862                           (concat "^" (expand-file-name
863                                        gnus-kill-files-directory)) 
864                           (expand-file-name file))
865                          file)
866                     (concat gnus-kill-files-directory file))))
867          (cached (assoc file gnus-score-cache))
868          (global (member file gnus-internal-global-score-files))
869          lists alist)
870     (if cached
871         ;; The score file was already loaded.
872         (setq alist (cdr cached))
873       ;; We load the score file.
874       (setq gnus-score-alist nil)
875       (setq alist (gnus-score-load-score-alist file))
876       ;; We add '(touched) to the alist to signify that it hasn't been
877       ;; touched (yet). 
878       (or (assq 'touched alist) (setq alist (cons (list 'touched nil) alist)))
879       ;; If it is a global score file, we make it read-only.
880       (and global
881            (not (assq 'read-only alist))
882            (setq alist (cons (list 'read-only t) alist)))
883       ;; Update cache.
884       (setq gnus-score-cache
885             (cons (cons file alist) gnus-score-cache)))
886     ;; If there are actual scores in the alist, we add it to the
887     ;; return value of this function.
888     (if (memq t (mapcar (lambda (e) (stringp (car e))) alist))
889         (setq lists (list alist)))
890     ;; Treat the other possible atoms in the score alist.
891     (let ((mark (car (gnus-score-get 'mark alist)))
892           (expunge (car (gnus-score-get 'expunge alist)))
893           (mark-and-expunge 
894            (car (gnus-score-get 'mark-and-expunge alist)))
895           (read-only (gnus-score-get 'read-only alist))
896           (files (gnus-score-get 'files alist))
897           (exclude-files (gnus-score-get 'exclude-files alist))
898           (orphan (car (gnus-score-get 'orphan alist)))
899           (adapt (gnus-score-get 'adapt alist))
900           (eval (gnus-score-get 'eval alist)))
901       ;; We do not respect eval and files atoms from global score
902       ;; files. 
903       (and files (not global)
904            (setq lists (apply 'append lists
905                               (mapcar (lambda (file)
906                                         (gnus-score-load-file file)) 
907                                       files))))
908       (and eval (not global) (eval eval))
909       (setq gnus-scores-exclude-files exclude-files)
910       (if orphan (setq gnus-orphan-score orphan))
911       (setq gnus-adaptive-score-alist
912             (cond ((equal adapt '(t))
913                    (setq gnus-newsgroup-adaptive t)
914                    gnus-default-adaptive-score-alist)
915                   ((equal adapt '(ignore))
916                    (setq gnus-newsgroup-adaptive nil))
917                   ((consp adapt)
918                    (setq gnus-newsgroup-adaptive t)
919                    adapt)
920                   (t
921                    (setq gnus-newsgroup-adaptive gnus-use-adaptive-scoring)
922                    gnus-default-adaptive-score-alist)))
923       (setq gnus-summary-mark-below 
924             (or mark mark-and-expunge gnus-summary-mark-below))
925       (setq gnus-summary-expunge-below 
926             (or expunge mark-and-expunge gnus-summary-expunge-below)))
927     (setq gnus-current-score-file file)
928     (setq gnus-score-alist alist)
929     lists))
930
931 (defun gnus-score-load (file)
932   ;; Load score FILE.
933   (let ((cache (assoc file gnus-score-cache)))
934     (if cache
935         (setq gnus-score-alist (cdr cache))
936       (setq gnus-score-alist nil)
937       (gnus-score-load-score-alist file)
938       (or gnus-score-alist
939           (setq gnus-score-alist (copy-alist '((touched nil)))))
940       (setq gnus-score-cache
941             (cons (cons file gnus-score-alist) gnus-score-cache)))))
942
943 (defun gnus-score-remove-from-cache (file)
944   (setq gnus-score-cache 
945         (delq (assoc file gnus-score-cache) gnus-score-cache)))
946
947 (defun gnus-score-load-score-alist (file)
948   (let (alist)
949     (if (file-readable-p file)
950         (progn
951           (save-excursion
952             (gnus-set-work-buffer)
953             (insert-file-contents file)
954             (goto-char (point-min))
955             ;; Only do the loading if the score file isn't empty.
956             (if (save-excursion (re-search-forward "[()0-9a-zA-Z]" nil t))
957                 (setq alist
958                       (condition-case ()
959                           (read (current-buffer))
960                         (error 
961                          (progn
962                            (gnus-message 3 "Problem with score file %s" file)
963                            (ding) 
964                            (sit-for 2)
965                            nil))))))
966           (if (eq (car alist) 'setq)
967               (setq gnus-score-alist (gnus-score-transform-old-to-new alist))
968             (setq gnus-score-alist alist))
969           (setq gnus-score-alist
970                 (gnus-score-check-syntax gnus-score-alist file)))
971       (setq gnus-score-alist nil))))
972
973 (defun gnus-score-check-syntax (alist file)
974   (cond 
975    ((null alist)
976     nil)
977    ((not (consp alist))
978     (gnus-message 1 "Score file is not a list: %s" file)
979     (ding)
980     nil)
981    (t
982     (let ((a alist)
983           err)
984       (while (and a (not err))
985         (cond ((not (listp (car a)))
986                (gnus-message 3 "Illegal score element %s in %s" (car a) file)
987                (setq err t))
988               ((and (stringp (car (car a)))
989                     (not (listp (nth 1 (car a)))))
990                (gnus-message 3 "Illegal header match %s in %s" (nth 1 (car a)) file)
991                (setq err t))
992               (t
993                (setq a (cdr a)))))
994       (if err
995           (progn
996             (ding)
997             nil)
998         alist)))))    
999
1000 (defun gnus-score-transform-old-to-new (alist)
1001   (let* ((alist (nth 2 alist))
1002          out entry)
1003     (if (eq (car alist) 'quote)
1004         (setq alist (nth 1 alist)))
1005     (while alist
1006       (setq entry (car alist))
1007       (if (stringp (car entry))
1008           (let ((scor (cdr entry)))
1009             (setq out (cons entry out))
1010             (while scor
1011               (setcar scor
1012                       (list (car (car scor)) (nth 2 (car scor))
1013                             (and (nth 3 (car scor))
1014                                  (gnus-day-number (nth 3 (car scor))))
1015                             (if (nth 1 (car scor)) 'r 's)))
1016               (setq scor (cdr scor))))
1017         (setq out (cons (if (not (listp (cdr entry))) 
1018                             (list (car entry) (cdr entry))
1019                           entry)
1020                         out)))
1021       (setq alist (cdr alist)))
1022     (cons (list 'touched t) (nreverse out))))
1023   
1024 (defun gnus-score-save ()
1025   ;; Save all SCORE information.
1026   (let ((cache gnus-score-cache))
1027     (save-excursion
1028       (setq gnus-score-alist nil)
1029       (set-buffer (get-buffer-create "*Score*"))
1030       (buffer-disable-undo (current-buffer))
1031       (let (entry score file)
1032         (while cache
1033           (setq entry (car cache)
1034                 cache (cdr cache)
1035                 file (car entry)
1036                 score (cdr entry))
1037           (if (or (not (equal (gnus-score-get 'touched score) '(t)))
1038                   (gnus-score-get 'read-only score)
1039                   (and (file-exists-p file)
1040                        (not (file-writable-p file))))
1041               ()
1042             (setq score (setcdr entry (delq (assq 'touched score) score)))
1043             (erase-buffer)
1044             (let (emacs-lisp-mode-hook)
1045               (if (string-match (concat gnus-adaptive-file-suffix) file)
1046                   ;; This is an adaptive score file, so we do not run
1047                   ;; it through `pp'.  These files can get huge, and
1048                   ;; are not meant to be edited by human hands.
1049                   (insert (format "%S" score))
1050                 ;; This is a normal score file, so we print it very
1051                 ;; prettily. 
1052                 (pp score (current-buffer))))
1053             (gnus-make-directory (file-name-directory file))
1054             (write-region (point-min) (point-max) file nil 'silent))))
1055       (kill-buffer (current-buffer)))))
1056   
1057 (defun gnus-score-headers (score-files)
1058   ;; Score `gnus-newsgroup-headers'.
1059   (let (scores)
1060     ;; PLM: probably this is not the best place to clear orphan-score
1061     (setq gnus-orphan-score nil)
1062     ;; Load the SCORE files.
1063     (while score-files
1064       (if (stringp (car score-files))
1065           ;; It is a string, which means that it's a score file name,
1066           ;; so we load the score file and add the score alist to
1067           ;; the list of alists.
1068           (setq scores (nconc (gnus-score-load-file (car score-files)) scores))
1069         ;; It is an alist, so we just add it to the list directly.
1070         (setq scores (nconc (car score-files) scores)))
1071       (setq score-files (cdr score-files)))
1072     ;; Prune the score files that are to be excluded, if any.
1073     (if (not gnus-scores-exclude-files)
1074         ()
1075       (let ((s scores)
1076             c)
1077         (while s
1078           (and (setq c (rassq (car s) gnus-score-cache))
1079                (member (car c) gnus-scores-exclude-files)
1080                (setq scores (delq (car s) scores)))
1081           (setq s (cdr s)))))
1082     (if (not (and gnus-summary-default-score
1083                   scores
1084                   (> (length gnus-newsgroup-headers)
1085                      (length gnus-newsgroup-scored))))
1086         ()
1087       (let* ((entries gnus-header-index)
1088              (now (gnus-day-number (current-time-string)))
1089              (expire (- now gnus-score-expiry-days))
1090              (headers gnus-newsgroup-headers)
1091              entry header)
1092         (gnus-message 5 "Scoring...")
1093         ;; Create articles, an alist of the form `(HEADER . SCORE)'.
1094         (while headers
1095           (setq header (car headers)
1096                 headers (cdr headers))
1097           ;; WARNING: The assq makes the function O(N*S) while it could
1098           ;; be written as O(N+S), where N is (length gnus-newsgroup-headers)
1099           ;; and S is (length gnus-newsgroup-scored).
1100           (or (assq (header-number header) gnus-newsgroup-scored)
1101               (setq gnus-scores-articles       ;Total of 2 * N cons-cells used.
1102                     (cons (cons header (or gnus-summary-default-score 0))
1103                           gnus-scores-articles))))
1104
1105         (save-excursion
1106           (set-buffer (get-buffer-create "*Headers*"))
1107           (buffer-disable-undo (current-buffer))
1108           ;; score orphans
1109           (if gnus-orphan-score 
1110               (progn
1111                 (setq gnus-score-index 
1112                       (nth 1 (assoc "references" gnus-header-index)))
1113                 (gnus-score-orphans gnus-orphan-score)))
1114           ;; Run each header through the score process.
1115           (while entries
1116             (setq entry (car entries)
1117                   header (nth 0 entry)
1118                   entries (cdr entries))
1119             (setq gnus-score-index (nth 1 (assoc header gnus-header-index)))
1120             (if (< 0 (apply 'max (mapcar
1121                                   (lambda (score)
1122                                     (length (gnus-score-get header score)))
1123                                   scores)))
1124                 (funcall (nth 2 entry) scores header now expire)))
1125           ;; Remove the buffer.
1126           (kill-buffer (current-buffer)))
1127
1128         ;; Add articles to `gnus-newsgroup-scored'.
1129         (while gnus-scores-articles
1130           (or (= gnus-summary-default-score (cdr (car gnus-scores-articles)))
1131               (setq gnus-newsgroup-scored
1132                     (cons (cons (header-number 
1133                                  (car (car gnus-scores-articles)))
1134                                 (cdr (car gnus-scores-articles)))
1135                           gnus-newsgroup-scored)))
1136           (setq gnus-scores-articles (cdr gnus-scores-articles)))
1137
1138         (gnus-message 5 "Scoring...done")))))
1139
1140
1141 (defun gnus-get-new-thread-ids (articles)
1142   (let ((index (nth 1 (assoc "message-id" gnus-header-index)))
1143         (refind gnus-score-index)
1144         id-list art this tref)
1145     (while articles
1146       (setq art (car articles)
1147             this (aref (car art) index)
1148             tref (aref (car art) refind)
1149             articles (cdr articles))
1150       (if (string-equal tref "")        ;no references line
1151           (setq id-list (cons this id-list))))
1152     id-list))
1153
1154 ;; Orphan functions written by plm@atcmp.nl (Peter Mutsaers).
1155 (defun gnus-score-orphans (score)
1156   (let ((new-thread-ids (gnus-get-new-thread-ids gnus-scores-articles))
1157         (index (nth 1 (assoc "references" gnus-header-index)))
1158         alike articles art arts this last this-id)
1159     
1160     (setq gnus-scores-articles (sort gnus-scores-articles 'gnus-score-string<)
1161           articles gnus-scores-articles)
1162
1163     ;;more or less the same as in gnus-score-string
1164     (erase-buffer)
1165     (while articles
1166       (setq art (car articles)
1167             this (aref (car art) gnus-score-index)
1168             articles (cdr articles))
1169       ;;completely skip if this is empty (not a child, so not an orphan)
1170       (if (not (string= this ""))
1171           (if (equal last this)
1172               ;; O(N*H) cons-cells used here, where H is the number of
1173               ;; headers.
1174               (setq alike (cons art alike))
1175             (if last
1176                 (progn
1177                   ;; Insert the line, with a text property on the
1178                   ;; terminating newline refering to the articles with
1179                   ;; this line.
1180                   (insert last ?\n)
1181                   (put-text-property (1- (point)) (point) 'articles alike)))
1182             (setq alike (list art)
1183                   last this))))
1184     (and last                           ; Bwadr, duplicate code.
1185          (progn
1186            (insert last ?\n)                    
1187            (put-text-property (1- (point)) (point) 'articles alike)))
1188
1189     ;; PLM: now delete those lines that contain an entry from new-thread-ids
1190     (while new-thread-ids
1191       (setq this-id (car new-thread-ids)
1192             new-thread-ids (cdr new-thread-ids))
1193       (goto-char (point-min))
1194       (while (search-forward this-id nil t)
1195         ;; found a match. remove this line
1196         (beginning-of-line)
1197         (kill-line 1)))
1198
1199     ;; now for each line: update its articles with score by moving to
1200     ;; every end-of-line in the buffer and read the articles property
1201     (goto-char (point-min))
1202     (while (eq 0 (progn
1203                    (end-of-line)
1204                    (setq arts (get-text-property (point) 'articles))
1205                    (while arts
1206                      (setq art (car arts)
1207                            arts (cdr arts))
1208                      (setcdr art (+ score (cdr art))))
1209                    (forward-line))))))
1210              
1211
1212 (defun gnus-score-integer (scores header now expire)
1213   (let ((gnus-score-index (nth 1 (assoc header gnus-header-index)))
1214         alike last this art entries alist articles)
1215
1216     ;; Find matches.
1217     (while scores
1218       (setq alist (car scores)
1219             scores (cdr scores)
1220             entries (assoc header alist))
1221       (while (cdr entries)              ;First entry is the header index.
1222         (let* ((rest (cdr entries))             
1223                (kill (car rest))
1224                (match (nth 0 kill))
1225                (type (or (nth 3 kill) '>))
1226                (score (or (nth 1 kill) gnus-score-interactive-default-score))
1227                (date (nth 2 kill))
1228                (found nil)
1229                (match-func (if (or (eq type '>) (eq type '<) (eq type '<=)
1230                                    (eq type '>=) (eq type '=))
1231                                type
1232                              (error "Illegal match type: %s" type)))
1233                (articles gnus-scores-articles)
1234                arts art)
1235           ;; Instead of doing all the clever stuff that
1236           ;; `gnus-score-string' does to minimize searches and stuff,
1237           ;; I will assume that people generally will put so few
1238           ;; matches on numbers that any cleverness will take more
1239           ;; time than one would gain.
1240           (while articles
1241             (and (funcall match-func 
1242                           (or (aref (car (car articles)) gnus-score-index) 0)
1243                           match)
1244                  (progn
1245                    (setq found t)
1246                    (setcdr (car articles) (+ score (cdr (car articles))))))
1247             (setq articles (cdr articles)))
1248           ;; Update expire date
1249           (cond ((null date))           ;Permanent entry.
1250                 (found                  ;Match, update date.
1251                  (gnus-score-set 'touched '(t) alist)
1252                  (setcar (nthcdr 2 kill) now))
1253                 ((< date expire) ;Old entry, remove.
1254                  (gnus-score-set 'touched '(t) alist)
1255                  (setcdr entries (cdr rest))
1256                  (setq rest entries)))
1257           (setq entries rest))))))
1258
1259 (defun gnus-score-date (scores header now expire)
1260   (let ((gnus-score-index (nth 1 (assoc header gnus-header-index)))
1261         alike last this art entries alist articles)
1262
1263     ;; Find matches.
1264     (while scores
1265       (setq alist (car scores)
1266             scores (cdr scores)
1267             entries (assoc header alist))
1268       (while (cdr entries)              ;First entry is the header index.
1269         (let* ((rest (cdr entries))             
1270                (kill (car rest))
1271                (match (timezone-make-date-sortable (nth 0 kill)))
1272                (type (or (nth 3 kill) 'before))
1273                (score (or (nth 1 kill) gnus-score-interactive-default-score))
1274                (date (nth 2 kill))
1275                (found nil)
1276                (match-func 
1277                 (cond ((eq type 'after) 'string<)
1278                       ((eq type 'before) 'gnus-string>)
1279                       ((eq type 'at) 'string=)
1280                       (t (error "Illegal match type: %s" type))))
1281                (articles gnus-scores-articles)
1282                arts art l)
1283           ;; Instead of doing all the clever stuff that
1284           ;; `gnus-score-string' does to minimize searches and stuff,
1285           ;; I will assume that people generally will put so few
1286           ;; matches on numbers that any cleverness will take more
1287           ;; time than one would gain.
1288           (while articles
1289             (and
1290              (setq l (aref (car (car articles)) gnus-score-index))
1291              (funcall match-func match (timezone-make-date-sortable l))
1292              (progn
1293                (setq found t)
1294                (setcdr (car articles) (+ score (cdr (car articles))))))
1295             (setq articles (cdr articles)))
1296           ;; Update expire date
1297           (cond ((null date))           ;Permanent entry.
1298                 (found                  ;Match, update date.
1299                  (gnus-score-set 'touched '(t) alist)
1300                  (setcar (nthcdr 2 kill) now))
1301                 ((< date expire) ;Old entry, remove.
1302                  (gnus-score-set 'touched '(t) alist)
1303                  (setcdr entries (cdr rest))
1304                  (setq rest entries)))
1305           (setq entries rest))))))
1306
1307 (defun gnus-score-body (scores header now expire)
1308   (save-excursion
1309     (set-buffer nntp-server-buffer)
1310     (save-restriction
1311       (let* ((buffer-read-only nil)
1312              (articles gnus-scores-articles)
1313              (all-scores scores)
1314              (request-func (cond ((string= "head" (downcase header))
1315                                   'gnus-request-head)
1316                                  ((string= "body" (downcase header))
1317                                   'gnus-request-body)
1318                                  (t 'gnus-request-article)))
1319              alike last this art entries alist ofunc article)
1320         ;; Not all backends support partial fetching.  In that case,
1321         ;; we just fetch the entire article.
1322         (or (gnus-check-backend-function request-func gnus-newsgroup-name)
1323             (progn
1324               (setq ofunc request-func)
1325               (setq request-func 'gnus-request-article)))
1326         (while articles
1327           (setq article (header-number (car (car articles))))
1328           (gnus-message 7 "Scoring on article %s..." article)
1329           (if (not (funcall request-func article gnus-newsgroup-name))
1330               ()
1331             (widen)
1332             (goto-char (point-min))
1333             ;; If just parts of the article is to be searched, but the
1334             ;; backend didn't support partial fetching, we just narrow
1335             ;; to the relevant parts.
1336             (if ofunc
1337                 (if (eq ofunc 'gnus-request-head)
1338                     (narrow-to-region
1339                      (point)
1340                      (or (search-forward "\n\n" nil t) (point-max)))
1341                   (narrow-to-region
1342                    (or (search-forward "\n\n" nil t) (point))
1343                    (point-max))))
1344             (setq scores all-scores)
1345             ;; Find matches.
1346             (while scores
1347               (setq alist (car scores)
1348                     scores (cdr scores)
1349                     entries (assoc header alist))
1350               (while (cdr entries)      ;First entry is the header index.
1351                 (let* ((rest (cdr entries))             
1352                        (kill (car rest))
1353                        (match (nth 0 kill))
1354                        (type (or (nth 3 kill) 's))
1355                        (score (or (nth 1 kill) 
1356                                   gnus-score-interactive-default-score))
1357                        (date (nth 2 kill))
1358                        (found nil)
1359                        (case-fold-search 
1360                         (not (or (eq type 'R) (eq type 'S)
1361                                  (eq type 'Regexp) (eq type 'String))))
1362                        (search-func 
1363                         (cond ((or (eq type 'r) (eq type 'R)
1364                                    (eq type 'regexp) (eq type 'Regexp))
1365                                're-search-forward)
1366                               ((or (eq type 's) (eq type 'S)
1367                                    (eq type 'string) (eq type 'String))
1368                                'search-forward)
1369                               (t
1370                                (error "Illegal match type: %s" type))))
1371                        arts art)
1372                   (goto-char (point-min))
1373                   (if (funcall search-func match nil t)
1374                       ;; Found a match, update scores.
1375                       (progn
1376                         (setcdr (car articles) (+ score (cdr (car articles))))
1377                         (setq found t)))
1378                   ;; Update expire date
1379                   (cond ((null date))   ;Permanent entry.
1380                         (found          ;Match, update date.
1381                          (gnus-score-set 'touched '(t) alist)
1382                          (setcar (nthcdr 2 kill) now))
1383                         ((< date expire) ;Old entry, remove.
1384                          (gnus-score-set 'touched '(t) alist)
1385                          (setcdr entries (cdr rest))
1386                          (setq rest entries)))
1387                   (setq entries rest)))))
1388           (setq articles (cdr articles)))))))
1389
1390
1391
1392 (defun gnus-score-followup (scores header now expire)
1393   ;; Insert the unique article headers in the buffer.
1394   (let ((gnus-score-index (nth 1 (assoc header gnus-header-index)))
1395         (current-score-file gnus-current-score-file)
1396         ;; gnus-score-index is used as a free variable.
1397         alike last this art entries alist articles)
1398
1399     ;; Change score file to the adaptive score file.  All entries that
1400     ;; this function makes will be put into this file.
1401     (gnus-score-load-file (gnus-score-file-name 
1402                            gnus-newsgroup-name gnus-adaptive-file-suffix))
1403
1404     (setq gnus-scores-articles (sort gnus-scores-articles 'gnus-score-string<)
1405           articles gnus-scores-articles)
1406
1407     (erase-buffer)
1408     (while articles
1409       (setq art (car articles)
1410             this (aref (car art) gnus-score-index)
1411             articles (cdr articles))
1412       (if (equal last this)
1413           (setq alike (cons art alike))
1414         (if last
1415             (progn
1416               (insert last ?\n)
1417               (put-text-property (1- (point)) (point) 'articles alike)))
1418         (setq alike (list art)
1419               last this)))
1420     (and last                           ; Bwadr, duplicate code.
1421          (progn
1422            (insert last ?\n)                    
1423            (put-text-property (1- (point)) (point) 'articles alike)))
1424   
1425     ;; Find matches.
1426     (while scores
1427       (setq alist (car scores)
1428             scores (cdr scores)
1429             entries (assoc header alist))
1430       (while (cdr entries)              ;First entry is the header index.
1431         (let* ((rest (cdr entries))             
1432                (kill (car rest))
1433                (match (nth 0 kill))
1434                (type (or (nth 3 kill) 's))
1435                (score (or (nth 1 kill) gnus-score-interactive-default-score))
1436                (date (nth 2 kill))
1437                (found nil)
1438                (mt (aref (symbol-name type) 0))
1439                (case-fold-search 
1440                 (not (or (= mt ?R) (= mt ?S) (= mt ?E) (= mt ?F))))
1441                (dmt (downcase mt))
1442                (search-func 
1443                 (cond ((= dmt ?r) 're-search-forward)
1444                       ((or (= dmt ?e) (= dmt ?s) (= dmt ?f)) 'search-forward)
1445                       (t (error "Illegal match type: %s" type))))
1446                arts art)
1447           (goto-char (point-min))
1448           (if (= dmt ?e)
1449               (while (funcall search-func match nil t)
1450                 (and (= (progn (beginning-of-line) (point))
1451                         (match-beginning 0))
1452                      (= (progn (end-of-line) (point))
1453                         (match-end 0))
1454                      (progn
1455                        (setq found (setq arts (get-text-property 
1456                                                (point) 'articles)))
1457                        ;; Found a match, update scores.
1458                        (while arts
1459                          (setq art (car arts)
1460                                arts (cdr arts))
1461                          (gnus-score-add-followups (car art) score)))))
1462             (while (funcall search-func match nil t)
1463               (end-of-line)
1464               (setq found (setq arts (get-text-property (point) 'articles)))
1465               ;; Found a match, update scores.
1466               (while arts
1467                 (setq art (car arts)
1468                       arts (cdr arts))
1469                 (gnus-score-add-followups (car art) score))))
1470           ;; Update expire date
1471           (cond ((null date))           ;Permanent entry.
1472                 (found                  ;Match, update date.
1473                  (gnus-score-set 'touched '(t) alist)
1474                  (setcar (nthcdr 2 kill) now))
1475                 ((< date expire) ;Old entry, remove.
1476                  (gnus-score-set 'touched '(t) alist)
1477                  (setcdr entries (cdr rest))
1478                  (setq rest entries)))
1479           (setq entries rest))))
1480     ;; We change the score file back to the previous one.
1481     (gnus-score-load-file current-score-file)))
1482
1483 (defun gnus-score-add-followups (header score)
1484   (save-excursion
1485     (set-buffer gnus-summary-buffer)
1486     (gnus-summary-score-entry 
1487      "references" (header-id header) 's score 
1488      (current-time-string) nil t)))
1489
1490
1491 (defun gnus-score-string (score-list header now expire)
1492   ;; Score ARTICLES according to HEADER in SCORE-LIST.
1493   ;; Update matches entries to NOW and remove unmatched entried older
1494   ;; than EXPIRE.
1495   
1496   ;; Insert the unique article headers in the buffer.
1497   (let ((gnus-score-index (nth 1 (assoc header gnus-header-index)))
1498         ;; gnus-score-index is used as a free variable.
1499         alike last this art entries alist articles scores fuzzy)
1500
1501     ;; Sorting the articles costs os O(N*log N) but will allow us to
1502     ;; only match with each unique header.  Thus the actual matching
1503     ;; will be O(M*U) where M is the number of strings to match with,
1504     ;; and U is the number of unique headers.  It is assumed (but
1505     ;; untested) this will be a net win because of the large constant
1506     ;; factor involved with string matching.
1507     (setq gnus-scores-articles (sort gnus-scores-articles 'gnus-score-string<)
1508           articles gnus-scores-articles)
1509
1510     (erase-buffer)
1511     (while articles
1512       (setq art (car articles)
1513             this (aref (car art) gnus-score-index)
1514             articles (cdr articles))
1515       (if (equal last this)
1516           ;; O(N*H) cons-cells used here, where H is the number of
1517           ;; headers.
1518           (setq alike (cons art alike))
1519         (if last
1520             (progn
1521               ;; Insert the line, with a text property on the
1522               ;; terminating newline refering to the articles with
1523               ;; this line.
1524               (insert last ?\n)
1525               (put-text-property (1- (point)) (point) 'articles alike)))
1526         (setq alike (list art)
1527               last this)))
1528     (and last                           ; Bwadr, duplicate code.
1529          (progn
1530            (insert last ?\n)                    
1531            (put-text-property (1- (point)) (point) 'articles alike)))
1532   
1533     ;; Find ordinary matches.
1534     (setq scores score-list) 
1535     (while scores
1536       (setq alist (car scores)
1537             scores (cdr scores)
1538             entries (assoc header alist))
1539       (while (cdr entries)              ;First entry is the header index.
1540         (let* ((rest (cdr entries))             
1541                (kill (car rest))
1542                (match (nth 0 kill))
1543                (type (or (nth 3 kill) 's))
1544                (score (or (nth 1 kill) gnus-score-interactive-default-score))
1545                (date (nth 2 kill))
1546                (found nil)
1547                (mt (aref (symbol-name type) 0))
1548                (case-fold-search 
1549                 (not (or (= mt ?R) (= mt ?S) (= mt ?E) (= mt ?F))))
1550                (dmt (downcase mt))
1551                (search-func 
1552                 (cond ((= dmt ?r) 're-search-forward)
1553                       ((or (= dmt ?e) (= dmt ?s) (= dmt ?f)) 'search-forward)
1554                       (t (error "Illegal match type: %s" type))))
1555                arts art)
1556           (if (= dmt ?f)
1557               (setq fuzzy t)
1558             (goto-char (point-min))
1559             (if (= dmt ?e)
1560                 (while (and (not (eobp)) 
1561                             (funcall search-func match nil t))
1562                   (and (= (progn (beginning-of-line) (point))
1563                           (match-beginning 0))
1564                        (= (progn (end-of-line) (point))
1565                           (match-end 0))
1566                        (progn
1567                          (setq found (setq arts (get-text-property 
1568                                                  (point) 'articles)))
1569                          ;; Found a match, update scores.
1570                          (while arts
1571                            (setq art (car arts)
1572                                  arts (cdr arts))
1573                            (setcdr art (+ score (cdr art))))))
1574                   (forward-line 1))
1575               (and (string= match "") (setq match "\n"))
1576               (while (funcall search-func match nil t)
1577                 (end-of-line)
1578                 (setq found (setq arts (get-text-property (point) 'articles)))
1579                 ;; Found a match, update scores.
1580                 (while arts
1581                   (setq art (car arts)
1582                         arts (cdr arts))
1583                   (setcdr art (+ score (cdr art))))))
1584             ;; Update expire date
1585             (cond ((null date))         ;Permanent entry.
1586                   (found                ;Match, update date.
1587                    (gnus-score-set 'touched '(t) alist)
1588                    (setcar (nthcdr 2 kill) now))
1589                   ((< date expire)      ;Old entry, remove.
1590                    (gnus-score-set 'touched '(t) alist)
1591                    (setcdr entries (cdr rest))
1592                    (setq rest entries))))
1593           (setq entries rest))))
1594   
1595     ;; Find fuzzy matches.
1596     (setq scores (and fuzzy score-list))
1597     (if fuzzy (gnus-simplify-buffer-fuzzy))
1598     (while scores
1599       (setq alist (car scores)
1600             scores (cdr scores)
1601             entries (assoc header alist))
1602       (while (cdr entries)              ;First entry is the header index.
1603         (let* ((rest (cdr entries))             
1604                (kill (car rest))
1605                (match (nth 0 kill))
1606                (type (or (nth 3 kill) 's))
1607                (score (or (nth 1 kill) gnus-score-interactive-default-score))
1608                (date (nth 2 kill))
1609                (found nil)
1610                (mt (aref (symbol-name type) 0))
1611                (case-fold-search 
1612                 (not (or (= mt ?R) (= mt ?S) (= mt ?E) (= mt ?F))))
1613                (dmt (downcase mt))
1614                (search-func 
1615                 (cond ((= dmt ?r) 're-search-forward)
1616                       ((or (= dmt ?e) (= dmt ?s) (= dmt ?f)) 'search-forward)
1617                       (t (error "Illegal match type: %s" type))))
1618                arts art)
1619           (if (/= dmt ?f)
1620               ()
1621             (goto-char (point-min))
1622             (while (and (not (eobp)) 
1623                         (funcall search-func match nil t))
1624               (and (= (progn (beginning-of-line) (point))
1625                       (match-beginning 0))
1626                    (= (progn (end-of-line) (point))
1627                       (match-end 0))
1628                    (progn
1629                      (setq found (setq arts (get-text-property 
1630                                              (point) 'articles)))
1631                      ;; Found a match, update scores.
1632                      (while arts
1633                        (setq art (car arts)
1634                              arts (cdr arts))
1635                        (setcdr art (+ score (cdr art))))))
1636               (forward-line 1))
1637             ;; Update expire date
1638             (cond ((null date))         ;Permanent entry.
1639                   (found                ;Match, update date.
1640                    (gnus-score-set 'touched '(t) alist)
1641                    (setcar (nthcdr 2 kill) now))
1642                   ((< date expire)      ;Old entry, remove.
1643                    (gnus-score-set 'touched '(t) alist)
1644                    (setcdr entries (cdr rest))
1645                    (setq rest entries))))
1646           (setq entries rest))))))
1647
1648 (defun gnus-score-string< (a1 a2)
1649   ;; Compare headers in articles A2 and A2.
1650   ;; The header index used is the free variable `gnus-score-index'.
1651   (string-lessp (aref (car a1) gnus-score-index)
1652                 (aref (car a2) gnus-score-index)))
1653
1654 (defun gnus-score-build-cons (article)
1655   ;; Build a `gnus-newsgroup-scored' type cons from ARTICLE.
1656   (cons (header-number (car article)) (cdr article)))
1657
1658 (defconst gnus-header-index
1659   ;; Name to index alist.
1660   '(("number" 0 gnus-score-integer)
1661     ("subject" 1 gnus-score-string)
1662     ("from" 2 gnus-score-string)
1663     ("date" 3 gnus-score-date)
1664     ("message-id" 4 gnus-score-string) 
1665     ("references" 5 gnus-score-string) 
1666     ("chars" 6 gnus-score-integer) 
1667     ("lines" 7 gnus-score-integer) 
1668     ("xref" 8 gnus-score-string)
1669     ("head" -1 gnus-score-body)
1670     ("body" -1 gnus-score-body)
1671     ("all" -1 gnus-score-body)
1672     ("followup" 2 gnus-score-followup)))
1673
1674 (defun gnus-current-score-file-nondirectory (&optional score-file)
1675   (let ((score-file (or score-file gnus-current-score-file)))
1676     (if score-file 
1677         (gnus-short-group-name (file-name-nondirectory score-file))
1678       "none")))
1679
1680 (defun gnus-score-adaptive ()
1681   (save-excursion
1682     (let* ((malist (gnus-copy-sequence gnus-adaptive-score-alist))
1683            (alist malist)
1684            (date (current-time-string)) 
1685            elem headers)
1686       ;; First we transform the adaptive rule alist into something
1687       ;; that's faster to process.
1688       (while malist
1689         (setq elem (car malist))
1690         (if (symbolp (car elem))
1691             (setcar elem (symbol-value (car elem))))
1692         (setq elem (cdr elem))
1693         (while elem
1694           (setcdr (car elem) 
1695                   (cons (symbol-name (car (car elem))) (cdr (car elem))))
1696           (setcar (car elem) 
1697                   (intern 
1698                    (concat "gnus-header-" 
1699                            (downcase (symbol-name (car (car elem)))))))
1700           (setq elem (cdr elem)))
1701         (setq malist (cdr malist)))
1702       ;; We change the score file to the adaptive score file.
1703       (gnus-score-load-file (gnus-score-file-name 
1704                              gnus-newsgroup-name gnus-adaptive-file-suffix))
1705       ;; The we score away.
1706       (goto-char (point-min))
1707       (while (not (eobp))
1708         (setq elem (cdr (assq (gnus-summary-article-mark) alist)))
1709         (if (or (not elem)
1710                 (get-text-property (point) 'gnus-pseudo))
1711             ()
1712           (setq headers (gnus-get-header-by-number 
1713                          (gnus-summary-article-number)))
1714           (while elem
1715             (gnus-summary-score-entry 
1716              (nth 1 (car elem)) (funcall (car (car elem)) headers)
1717              's (nth 2 (car elem)) date nil t)
1718             (setq elem (cdr elem))))
1719         (forward-line 1)))))
1720
1721 (defun gnus-score-remove-lines-adaptive (marks)
1722   (save-excursion
1723     (let* ((malist (gnus-copy-sequence gnus-adaptive-score-alist))
1724            (alist malist)
1725            (date (current-time-string)) 
1726            elem headers)
1727       ;; First we transform the adaptive rule alist into something
1728       ;; that's faster to process.
1729       (while malist
1730         (setq elem (car malist))
1731         (if (symbolp (car elem))
1732             (setcar elem (symbol-value (car elem))))
1733         (setq elem (cdr elem))
1734         (while elem
1735           (setcdr (car elem) 
1736                   (cons (symbol-name (car (car elem))) (cdr (car elem))))
1737           (setcar (car elem) 
1738                   (intern 
1739                    (concat "gnus-header-" 
1740                            (downcase (symbol-name (car (car elem)))))))
1741           (setq elem (cdr elem)))
1742         (setq malist (cdr malist)))
1743       ;; The we score away.
1744       (goto-char (point-min))
1745       ;; We change the score file to the adaptive score file.
1746       (gnus-score-load-file (gnus-score-file-name 
1747                              gnus-newsgroup-name gnus-adaptive-file-suffix))
1748       (while (re-search-forward marks nil t)
1749         (beginning-of-line)
1750         (setq elem (cdr (assq (gnus-summary-article-mark) alist)))
1751         (if (or (not elem)
1752                 (get-text-property (gnus-point-at-bol) 'gnus-pseudo))
1753             ()
1754           (setq headers (gnus-get-header-by-number 
1755                          (gnus-summary-article-number)))
1756           (while elem
1757             (gnus-summary-score-entry 
1758              (nth 1 (car elem)) (funcall (car (car elem)) headers)
1759              'e (nth 2 (car elem)) date nil t)
1760             (setq elem (cdr elem))))
1761         (delete-region (point) (progn (forward-line 1) (point)))))))
1762
1763 ;;;
1764 ;;; Score mode.
1765 ;;;
1766
1767 (defvar gnus-score-mode-map nil)
1768 (defvar gnus-score-mode-hook nil)
1769
1770 (if gnus-score-mode-map
1771     ()
1772   (setq gnus-score-mode-map (copy-keymap emacs-lisp-mode-map))
1773   (define-key gnus-score-mode-map "\C-c\C-c" 'gnus-score-edit-done)
1774   (define-key gnus-score-mode-map "\C-c\C-d" 'gnus-score-edit-insert-date))
1775
1776 (defun gnus-score-mode ()
1777   "Mode for editing score files.
1778 This mode is an extended emacs-lisp mode.
1779
1780 \\{gnus-score-mode-map}"
1781   (interactive)
1782   (kill-all-local-variables)
1783   (use-local-map gnus-score-mode-map)
1784   (set-syntax-table emacs-lisp-mode-syntax-table)
1785   (setq major-mode 'gnus-score-mode)
1786   (setq mode-name "Score")
1787   (lisp-mode-variables nil)
1788   (run-hooks 'emacs-lisp-mode-hook 'gnus-score-mode-hook))
1789
1790 (defun gnus-score-edit-insert-date ()
1791   "Insert date in numerical format."
1792   (interactive)
1793   (insert (int-to-string (gnus-day-number (current-time-string)))))
1794
1795 (defun gnus-score-edit-done ()
1796   "Save the score file and return to the summary buffer."
1797   (interactive)
1798   (let ((bufnam (buffer-file-name (current-buffer)))
1799         (winconf gnus-prev-winconf))
1800     (save-buffer)
1801     (kill-buffer (current-buffer))
1802     (and winconf (set-window-configuration winconf))
1803     (gnus-score-remove-from-cache bufnam)
1804     (gnus-score-load-file bufnam)))
1805
1806 ;;; Finding score files. 
1807
1808 (defvar gnus-global-score-files nil
1809   "*List of global score files and directories.
1810 Set this variable if you want to use people's score files.  One entry
1811 for each score file or each score file directory.  Gnus will decide
1812 by itself what score files are applicable to which group.
1813
1814 Say you want to use the single score file
1815 \"/ftp.ifi.uio.no@ftp:/pub/larsi/ding/score/soc.motss.SCORE\" and all
1816 score files in the \"/ftp.some-where:/pub/score\" directory.
1817
1818  (setq gnus-global-score-files
1819        '(\"/ftp.ifi.uio.no:/pub/larsi/ding/score/soc.motss.SCORE\"
1820          \"/ftp.some-where:/pub/score\"))")
1821
1822 (defun gnus-score-score-files (group)
1823   "Return a list of all possible score files."
1824   ;; Search and set any global score files.
1825   (and gnus-global-score-files 
1826        (or gnus-internal-global-score-files
1827            (gnus-score-search-global-directories gnus-global-score-files)))
1828   ;; Fix the kill-file dir variable.
1829   (setq gnus-kill-files-directory 
1830         (file-name-as-directory
1831          (or gnus-kill-files-directory "~/News/")))
1832   ;; If er can't read it, there's no score files.
1833   (if (not (file-readable-p gnus-kill-files-directory))
1834       (setq gnus-score-file-list nil)
1835     (if (gnus-use-long-file-name 'not-score)
1836         ;; We want long file names.
1837         (if (or (not gnus-score-file-list)
1838                 (gnus-file-newer-than gnus-kill-files-directory
1839                                       (car gnus-score-file-list)))
1840               (setq gnus-score-file-list 
1841                     (cons (nth 5 (file-attributes gnus-kill-files-directory))
1842                           (nreverse 
1843                            (directory-files 
1844                             gnus-kill-files-directory t 
1845                             (gnus-score-file-regexp))))))
1846       ;; We do not use long file names, so we have to do some
1847       ;; directory traversing.  
1848       (let ((dir (expand-file-name
1849                   (concat gnus-kill-files-directory
1850                           (gnus-replace-chars-in-string group ?. ?/))))
1851             (mdir (length (expand-file-name gnus-kill-files-directory)))
1852             (suffixes (list gnus-score-file-suffix gnus-adaptive-file-suffix))
1853             files suffix)
1854         (while suffixes
1855           (setq suffix (car suffixes)
1856                 suffixes (cdr suffixes))
1857           (if (file-exists-p (concat dir "/" suffix))
1858               (setq files (list (concat dir "/" suffix))))
1859           (while (>= (1+ (length dir)) mdir)
1860             (and (file-exists-p (concat dir "/all/" suffix))
1861                  (setq files (cons (concat dir "/all/" suffix) files)))
1862             (string-match "/[^/]*$" dir)
1863             (setq dir (substring dir 0 (match-beginning 0)))))
1864         (setq gnus-score-file-list 
1865               (cons nil (nreverse files)))))
1866     (cdr gnus-score-file-list)))
1867
1868 (defun gnus-score-file-regexp ()
1869   (concat "\\(" gnus-score-file-suffix 
1870           "\\|" gnus-adaptive-file-suffix "\\)$"))
1871         
1872 (defun gnus-score-find-bnews (group)
1873   "Return a list of score files for GROUP.
1874 The score files are those files in the ~/News directory which matches
1875 GROUP using BNews sys file syntax."
1876   (let* ((sfiles (append (gnus-score-score-files group)
1877                          gnus-internal-global-score-files))
1878          (kill-dir (file-name-as-directory 
1879                     (expand-file-name gnus-kill-files-directory)))
1880          (klen (length kill-dir))
1881          ofiles not-match regexp)
1882     (save-excursion
1883       (set-buffer (get-buffer-create "*gnus score files*"))
1884       (buffer-disable-undo (current-buffer))
1885       ;; Go through all score file names and create regexp with them
1886       ;; as the source.  
1887       (while sfiles
1888         (erase-buffer)
1889         (insert (car sfiles))
1890         (goto-char (point-min))
1891         ;; First remove the suffix itself.
1892         (re-search-forward (concat "." (gnus-score-file-regexp)))
1893         (replace-match "" t t) 
1894         (goto-char (point-min))
1895         (if (looking-at (regexp-quote kill-dir))
1896             ;; If the file name was just "SCORE", `klen' is one character
1897             ;; too much.
1898             (delete-char (min (1- (point-max)) klen))
1899           (goto-char (point-max))
1900           (search-backward "/")
1901           (delete-region (1+ (point)) (point-min)))
1902         ;; Translate "all" to ".*".
1903         (while (search-forward "all" nil t)
1904           (replace-match ".*" t t))
1905         (goto-char (point-min))
1906         ;; Deal with "not."s.
1907         (if (looking-at "not.")
1908             (progn
1909               (setq not-match t)
1910               (setq regexp (buffer-substring 5 (point-max))))
1911           (setq regexp (buffer-substring 1 (point-max)))
1912           (setq not-match nil))
1913         ;; Finally - if this resulting regexp matches the group name,
1914         ;; we add this score file to the list of score files
1915         ;; applicable to this group.
1916         (if (or (and not-match
1917                      (not (string-match regexp group)))
1918                 (and (not not-match)
1919                      (string-match regexp group)))
1920             (setq ofiles (cons (car sfiles) ofiles)))
1921         (setq sfiles (cdr sfiles)))
1922       (kill-buffer (current-buffer))
1923       ;; Slight kludge here - the last score file returned should be
1924       ;; the local score file, whether it exists or not. This is so
1925       ;; that any score commands the user enters will go to the right
1926       ;; file, and not end up in some global score file.
1927       (let ((localscore
1928              (expand-file-name
1929               (if (gnus-use-long-file-name 'not-score)
1930                   (concat gnus-kill-files-directory group "." 
1931                           gnus-score-file-suffix)
1932                 (concat gnus-kill-files-directory
1933                         (gnus-replace-chars-in-string group ?. ?/)
1934                         "/" gnus-score-file-suffix)))))
1935         (and (member localscore ofiles)
1936              (delete localscore ofiles))
1937         (setq ofiles (cons localscore ofiles)))
1938       (nreverse ofiles))))
1939
1940 (defun gnus-score-find-single (group)
1941   "Return list containing the score file for GROUP."
1942   (list (gnus-score-file-name group)))
1943
1944 (defun gnus-score-find-hierarchical (group)
1945   "Return list of score files for GROUP.
1946 This includes the score file for the group and all its parents."
1947   (let ((all (copy-sequence '(nil)))
1948         (start 0))
1949     (while (string-match "\\." group (1+ start))
1950       (setq start (match-beginning 0))
1951       (setq all (cons (substring group 0 start) all)))
1952     (setq all (cons group all))
1953     (mapcar 'gnus-score-file-name (nreverse all))))
1954
1955 (defun gnus-possibly-score-headers ()
1956   (let ((func gnus-score-find-score-files-function)
1957         score-files scores)
1958     (and func (not (listp func))
1959          (setq func (list func)))
1960     ;; Go through all the functions for finding score files (or actual
1961     ;; scores) and add them to a list.
1962     (while func
1963       (and (symbolp (car func))
1964            (fboundp (car func))
1965            (setq score-files 
1966                  (nconc score-files (funcall (car func) gnus-newsgroup-name))))
1967       (setq func (cdr func)))
1968     (if score-files (gnus-score-headers score-files))))
1969
1970 (defun gnus-score-file-name (newsgroup &optional suffix)
1971   "Return the name of a score file for NEWSGROUP."
1972   (let ((suffix (or suffix gnus-score-file-suffix)))
1973     (cond  ((or (null newsgroup)
1974                 (string-equal newsgroup ""))
1975             ;; The global score file is placed at top of the directory.
1976             (expand-file-name 
1977              suffix (or gnus-kill-files-directory "~/News")))
1978            ((gnus-use-long-file-name 'not-score)
1979             ;; Append ".SCORE" to newsgroup name.
1980             (expand-file-name (concat newsgroup "." suffix)
1981                               (or gnus-kill-files-directory "~/News")))
1982            (t
1983             ;; Place "SCORE" under the hierarchical directory.
1984             (expand-file-name (concat (gnus-newsgroup-directory-form newsgroup)
1985                                       "/" suffix)
1986                               (or gnus-kill-files-directory "~/News"))))))
1987
1988 (defun gnus-score-search-global-directories (files)
1989   "Scan all global score directories for score files."
1990   ;; Set the variable `gnus-internal-global-score-files' to all
1991   ;; available global score files.
1992   (interactive (list gnus-global-score-files))
1993   (let (out)
1994     (while files
1995       (if (string-match "/$" (car files))
1996           (setq out (nconc (directory-files 
1997                             (car files) t
1998                             (concat (gnus-score-file-regexp) "$"))))
1999         (setq out (cons (car files) out)))
2000       (setq files (cdr files)))
2001     (setq gnus-internal-global-score-files out)))
2002
2003 (provide 'gnus-score)
2004
2005 ;;; gnus-score.el ends here