(defvar gnus-score-exact-adapt-limit 10
"*Number that says how long a match has to be before using substring matching.
When doing adaptive scoring, one normally uses fuzzy or substring
-matching. However, if the header one matches is short, the possibility
+matching. However, if the header one matches is short, the possibility
for false positives is great, so if the length of the match is less
than this variable, exact matching will be used.
If nil, the user will be asked for a duration.")
+(defvar gnus-score-after-write-file-function nil
+ "*Function called with the name of the score file just written to disk.")
+
\f
;; Internal variables.
(defvar gnus-score-index nil)
(eval-and-compile
- (autoload 'gnus-uu-ctl-map "gnus-uu" nil nil 'keymap)
- (autoload 'appt-select-lowest-window "appt.el"))
+ (autoload 'gnus-uu-ctl-map "gnus-uu" nil nil 'keymap))
;;; Summary mode score maps.
"m" gnus-score-set-mark-below
"x" gnus-score-set-expunge-below
"R" gnus-summary-rescore
- "e" gnus-score-edit-alist
+ "e" gnus-score-edit-current-scores
"f" gnus-score-edit-file
"t" gnus-score-find-trace
"C" gnus-score-customize)
(aref (symbol-name gnus-score-default-type) 0)))
(pchar (and gnus-score-default-duration
(aref (symbol-name gnus-score-default-duration) 0)))
- entry temporary end type match)
+ entry temporary type match)
;; First we read the header to score.
(while (not hchar)
(when (/= (downcase hchar) hchar)
;; This was a majuscle, so we end reading and set the defaults.
(if mimic (message "%c %c" prefix hchar) (message ""))
- (setq tchar (or gnus-score-default-type ?s)
- pchar (or gnus-score-default-duration ?t)))
+ (setq tchar (or tchar ?s)
+ pchar (or pchar ?t)))
;; We continue reading - the type.
(while (not tchar)
;; It was a majuscle, so we end reading and the the default.
(if mimic (message "%c %c %c" prefix hchar tchar)
(message ""))
- (setq pchar (or gnus-score-default-duration ?p)))
+ (setq pchar (or pchar ?p)))
;; We continue reading.
(while (not pchar)
(setq match (substring match (match-end 0))))
(when (string-match "^[^:]* +" match)
(setq match (substring match (match-end 0))))))
+
+ (when (memq type '(r R regexp Regexp))
+ (setq match (regexp-quote match)))
(gnus-summary-score-entry
(nth 1 entry) ; Header
(insert "\n"))
(setq pad (- width 3))
(setq format (concat "%c: %-" (int-to-string pad) "s"))
- (insert (format format (car (car alist)) (nth idx (car alist))))
+ (insert (format format (caar alist) (nth idx (car alist))))
(setq alist (cdr alist))
(setq i (1+ i))))
;; display ourselves in a small window at the bottom
- (appt-select-lowest-window)
+ (gnus-appt-select-lowest-window)
(split-window)
(pop-to-buffer "*Score Help*")
(shrink-window-if-larger-than-buffer)
(if (numberp match)
(int-to-string match)
match))))
+
+ ;; Score the current buffer.
(and (>= (nth 1 (assoc header gnus-header-index)) 0)
(eq (nth 2 (assoc header gnus-header-index)) 'gnus-score-string)
(not silent)
(and (eq (nth 2 (assoc header gnus-header-index)) 'gnus-score-integer)
(setq match (string-to-int match)))
- (if (eq date 'now)
- ()
- (and (= score gnus-score-interactive-default-score)
+ (unless (eq date 'now)
+ ;; Add the score entry to the score file.
+ (when (= score gnus-score-interactive-default-score)
(setq score nil))
(let ((new (cond
(type
(or (nth 1 new)
gnus-score-interactive-default-score)))
;; Nope, we have to add a new elem.
- (gnus-score-set header (if old (cons new old) (list new)))))
- (gnus-score-set 'touched '(t)))))
+ (gnus-score-set header (if old (cons new old) (list new))))
+ (gnus-score-set 'touched '(t))
+ new))))
(defun gnus-summary-score-effect (header match type score)
"Simulate the effect of a score file entry.
(goto-char (point-min))
(let ((regexp (cond ((eq type 'f)
(gnus-simplify-subject-fuzzy match))
- (type match)
- (t (concat "\\`.*" (regexp-quote match) ".*\\'")))))
+ ((eq type 'r)
+ match)
+ ((eq type 'e)
+ (concat "\\`" (regexp-quote match) "\\'"))
+ (t
+ (regexp-quote match)))))
(while (not (eobp))
(let ((content (gnus-summary-header header 'noerr))
(case-fold-search t))
"Add SCORE to all followups to the article in the current buffer."
(interactive "P")
(setq score (gnus-score-default score))
- (save-excursion
- (save-restriction
- (goto-char (point-min))
- (let ((id (mail-fetch-field "message-id")))
- (when id
- (gnus-summary-score-entry
- "references" (concat id "[ \t]*$") 'r
- score (current-time-string) nil t))))))
+ (when (gnus-buffer-live-p gnus-summary-buffer)
+ (save-excursion
+ (set-buffer gnus-summary-buffer)
+ (save-restriction
+ (goto-char (point-min))
+ (let ((id (mail-fetch-field "message-id")))
+ (when id
+ (gnus-summary-score-entry
+ "references" (concat id "[ \t]*$") 'r
+ score (current-time-string) nil t)))))))
(defun gnus-score-followup-thread (&optional score)
"Add SCORE to all later articles in the thread the current buffer is part of."
(interactive "P")
(setq score (gnus-score-default score))
- (save-excursion
- (save-restriction
- (goto-char (point-min))
- (let ((id (mail-fetch-field "message-id")))
- (when id
- (gnus-summary-score-entry
- "references" id 's
- score (current-time-string)))))))
+ (when (gnus-buffer-live-p gnus-summary-buffer)
+ (save-excursion
+ (set-buffer gnus-summary-buffer)
+ (save-restriction
+ (goto-char (point-min))
+ (let ((id (mail-fetch-field "message-id")))
+ (when id
+ (gnus-summary-score-entry
+ "references" id 's
+ score (current-time-string))))))))
(defun gnus-score-set (symbol value &optional alist)
;; Set SYMBOL to VALUE in ALIST.
(gnus-score-load-file file)
(gnus-set-mode-line 'summary))
-(defun gnus-score-edit-alist (file)
+(defun gnus-score-edit-current-scores (file)
"Edit the current score alist."
(interactive (list gnus-current-score-file))
(let ((winconf (current-window-configuration)))
(set-buffer gnus-summary-buffer)
(while local
(and (consp (car local))
- (symbolp (car (car local)))
+ (symbolp (caar local))
(progn
- (make-local-variable (car (car local)))
- (set (car (car local)) (nth 1 (car local)))))
+ (make-local-variable (caar local))
+ (set (caar local) (nth 1 (car local)))))
(setq local (cdr local)))))
(if orphan (setq gnus-orphan-score orphan))
(setq gnus-adaptive-score-alist
(cond
((not (listp (car a)))
(format "Illegal score element %s in %s" (car a) file))
- ((stringp (car (car a)))
+ ((stringp (caar a))
(cond
- ((not (listp (setq sr (cdr (car a)))))
+ ((not (listp (setq sr (cdar a))))
(format "Illegal header match %s in %s" (nth 1 (car a)) file))
(t
(setq type (caar a))
(setq out (cons entry out))
(while scor
(setcar scor
- (list (car (car scor)) (nth 2 (car scor))
+ (list (caar scor) (nth 2 (car scor))
(and (nth 3 (car scor))
(gnus-day-number (nth 3 (car scor))))
(if (nth 1 (car scor)) 'r 's)))
(if (zerop (buffer-size))
(delete-file file)
;; There are scores, so we write the file.
- (and (file-writable-p file)
- (write-region (point-min) (point-max)
- file nil 'silent))))
+ (when (file-writable-p file)
+ (write-region (point-min) (point-max) file nil 'silent)
+ (and gnus-score-after-write-file-function
+ (funcall gnus-score-after-write-file-function file)))))
(and gnus-score-uncacheable-files
(string-match gnus-score-uncacheable-files file)
(gnus-score-remove-from-cache file)))))
(defun gnus-score-headers (score-files &optional trace)
;; Score `gnus-newsgroup-headers'.
- (let (scores)
+ (let (scores news)
;; PLM: probably this is not the best place to clear orphan-score
(setq gnus-orphan-score nil)
(setq gnus-scores-articles nil)
(member (car c) gnus-scores-exclude-files)
(setq scores (delq (car s) scores)))
(setq s (cdr s)))))
+ (setq news scores)
;; Do the scoring.
- (when (and gnus-summary-default-score
- scores
- (> (length gnus-newsgroup-headers)
- (length gnus-newsgroup-scored)))
- (let* ((entries gnus-header-index)
- (now (gnus-day-number (current-time-string)))
- (expire (and gnus-score-expiry-days
- (- now gnus-score-expiry-days)))
- (headers gnus-newsgroup-headers)
- (current-score-file gnus-current-score-file)
- entry header)
- (gnus-message 5 "Scoring...")
- ;; Create articles, an alist of the form `(HEADER . SCORE)'.
- (while headers
- (setq header (car headers)
- headers (cdr headers))
- ;; WARNING: The assq makes the function O(N*S) while it could
- ;; be written as O(N+S), where N is (length gnus-newsgroup-headers)
- ;; and S is (length gnus-newsgroup-scored).
- (or (assq (mail-header-number header) gnus-newsgroup-scored)
- (setq gnus-scores-articles ;Total of 2 * N cons-cells used.
- (cons (cons header (or gnus-summary-default-score 0))
- gnus-scores-articles))))
-
- (save-excursion
- (set-buffer (get-buffer-create "*Headers*"))
- (buffer-disable-undo (current-buffer))
-
- ;; Set the global variant of this variable.
- (setq gnus-current-score-file current-score-file)
- ;; score orphans
- (if gnus-orphan-score
- (progn
- (setq gnus-score-index
- (nth 1 (assoc "references" gnus-header-index)))
- (gnus-score-orphans gnus-orphan-score)))
- ;; Run each header through the score process.
- (while entries
- (setq entry (car entries)
- header (downcase (nth 0 entry))
- entries (cdr entries))
- (setq gnus-score-index (nth 1 (assoc header gnus-header-index)))
- (if (< 0 (apply 'max (mapcar
- (lambda (score)
- (length (gnus-score-get header score)))
- scores)))
+ (while news
+ (setq scores news
+ news nil)
+ (when (and gnus-summary-default-score
+ scores)
+ (let* ((entries gnus-header-index)
+ (now (gnus-day-number (current-time-string)))
+ (expire (and gnus-score-expiry-days
+ (- now gnus-score-expiry-days)))
+ (headers gnus-newsgroup-headers)
+ (current-score-file gnus-current-score-file)
+ entry header new)
+ (gnus-message 5 "Scoring...")
+ ;; Create articles, an alist of the form `(HEADER . SCORE)'.
+ (while (setq header (pop headers))
+ ;; WARNING: The assq makes the function O(N*S) while it could
+ ;; be written as O(N+S), where N is (length gnus-newsgroup-headers)
+ ;; and S is (length gnus-newsgroup-scored).
+ (or (assq (mail-header-number header) gnus-newsgroup-scored)
+ (setq gnus-scores-articles ;Total of 2 * N cons-cells used.
+ (cons (cons header (or gnus-summary-default-score 0))
+ gnus-scores-articles))))
+
+ (save-excursion
+ (set-buffer (get-buffer-create "*Headers*"))
+ (buffer-disable-undo (current-buffer))
+
+ ;; Set the global variant of this variable.
+ (setq gnus-current-score-file current-score-file)
+ ;; score orphans
+ (when gnus-orphan-score
+ (setq gnus-score-index
+ (nth 1 (assoc "references" gnus-header-index)))
+ (gnus-score-orphans gnus-orphan-score))
+ ;; Run each header through the score process.
+ (while entries
+ (setq entry (car entries)
+ header (downcase (nth 0 entry))
+ entries (cdr entries))
+ (setq gnus-score-index (nth 1 (assoc header gnus-header-index)))
+ (when (< 0 (apply 'max (mapcar
+ (lambda (score)
+ (length (gnus-score-get header score)))
+ scores)))
;; Call the scoring function for this type of "header".
- (funcall (nth 2 entry) scores header now expire trace)))
- ;; Remove the buffer.
- (kill-buffer (current-buffer)))
+ (when (setq new (funcall (nth 2 entry) scores header
+ now expire trace))
+ (push new news))))
+ ;; Remove the buffer.
+ (kill-buffer (current-buffer)))
- ;; Add articles to `gnus-newsgroup-scored'.
- (while gnus-scores-articles
- (or (= gnus-summary-default-score (cdr (car gnus-scores-articles)))
- (setq gnus-newsgroup-scored
- (cons (cons (mail-header-number
- (car (car gnus-scores-articles)))
- (cdr (car gnus-scores-articles)))
- gnus-newsgroup-scored)))
- (setq gnus-scores-articles (cdr gnus-scores-articles)))
+ ;; Add articles to `gnus-newsgroup-scored'.
+ (while gnus-scores-articles
+ (or (= gnus-summary-default-score (cdar gnus-scores-articles))
+ (setq gnus-newsgroup-scored
+ (cons (cons (mail-header-number
+ (caar gnus-scores-articles))
+ (cdar gnus-scores-articles))
+ gnus-newsgroup-scored)))
+ (setq gnus-scores-articles (cdr gnus-scores-articles)))
- (gnus-message 5 "Scoring...done")))))
+ (gnus-message 5 "Scoring...done"))))))
(defun gnus-get-new-thread-ids (articles)
;; time than one would gain.
(while articles
(and (funcall match-func
- (or (aref (car (car articles)) gnus-score-index) 0)
+ (or (aref (caar articles) gnus-score-index) 0)
match)
(progn
(and trace (setq gnus-score-trace
kill)
gnus-score-trace)))
(setq found t)
- (setcdr (car articles) (+ score (cdr (car articles))))))
+ (setcdr (car articles) (+ score (cdar articles)))))
(setq articles (cdr articles)))
;; Update expire date
(cond ((null date)) ;Permanent entry.
(gnus-score-set 'touched '(t) alist)
(setcdr entries (cdr rest))
(setq rest entries)))
- (setq entries rest))))))
+ (setq entries rest)))))
+ nil)
(defun gnus-score-date (scores header now expire &optional trace)
(let ((gnus-score-index (nth 1 (assoc header gnus-header-index)))
;; time than one would gain.
(while articles
(and
- (setq l (aref (car (car articles)) gnus-score-index))
+ (setq l (aref (caar articles) gnus-score-index))
(funcall match-func match (timezone-make-date-sortable l))
(progn
(and trace (setq gnus-score-trace
kill)
gnus-score-trace)))
(setq found t)
- (setcdr (car articles) (+ score (cdr (car articles))))))
+ (setcdr (car articles) (+ score (cdar articles)))))
(setq articles (cdr articles)))
;; Update expire date
(cond ((null date)) ;Permanent entry.
(gnus-score-set 'touched '(t) alist)
(setcdr entries (cdr rest))
(setq rest entries)))
- (setq entries rest))))))
+ (setq entries rest)))))
+ nil)
(defun gnus-score-body (scores header now expire &optional trace)
(save-excursion
(set-buffer nntp-server-buffer)
+ (setq gnus-scores-articles
+ (sort gnus-scores-articles
+ (lambda (a1 a2)
+ (< (mail-header-number (car a1))
+ (mail-header-number (car a2))))))
(save-restriction
(let* ((buffer-read-only nil)
(articles gnus-scores-articles)
- (last (mail-header-number (car (car gnus-scores-articles))))
(all-scores scores)
(request-func (cond ((string= "head" (downcase header))
'gnus-request-head)
((string= "body" (downcase header))
'gnus-request-body)
(t 'gnus-request-article)))
- entries alist ofunc article)
+ entries alist ofunc article last)
+ (while (cdr articles)
+ (setq articles (cdr articles)))
+ (setq last (mail-header-number (caar articles)))
+ (setq articles gnus-scores-articles)
;; Not all backends support partial fetching. In that case,
;; we just fetch the entire article.
(or (gnus-check-backend-function
(setq ofunc request-func)
(setq request-func 'gnus-request-article)))
(while articles
- (setq article (mail-header-number (car (car articles))))
+ (setq article (mail-header-number (caar articles)))
(gnus-message 7 "Scoring on article %s of %s..." article last)
(if (not (funcall request-func article gnus-newsgroup-name))
()
(if (funcall search-func match nil t)
;; Found a match, update scores.
(progn
- (setcdr (car articles) (+ score (cdr (car articles))))
+ (setcdr (car articles) (+ score (cdar articles)))
(setq found t)
(and trace (setq gnus-score-trace
(cons
(setcdr entries (cdr rest))
(setq rest entries)))
(setq entries rest)))))
- (setq articles (cdr articles)))))))
+ (setq articles (cdr articles))))))
+ nil)
(defun gnus-score-followup (scores header now expire &optional trace thread)
;; Insert the unique article headers in the buffer.
(current-score-file gnus-current-score-file)
(all-scores scores)
;; gnus-score-index is used as a free variable.
- alike last this art entries alist articles)
+ alike last this art entries alist articles
+ new news)
;; Change score file to the adaptive score file. All entries that
;; this function makes will be put into this file.
- (gnus-score-load-file (gnus-score-file-name
- gnus-newsgroup-name gnus-adaptive-file-suffix))
+ (save-excursion
+ (set-buffer gnus-summary-buffer)
+ (gnus-score-load-file
+ (or gnus-newsgroup-adaptive-score-file
+ (gnus-score-file-name
+ gnus-newsgroup-name gnus-adaptive-file-suffix))))
(setq gnus-scores-articles (sort gnus-scores-articles 'gnus-score-string<)
articles gnus-scores-articles)
(end-of-line)
(setq found (setq arts (get-text-property (point) 'articles)))
;; Found a match, update scores.
- (while arts
- (setq art (car arts)
- arts (cdr arts))
- (gnus-score-add-followups (car art) score all-scores thread))))
+ (while (setq art (pop arts))
+ (when (setq new (gnus-score-add-followups
+ (car art) score all-scores thread))
+ (push new news)))))
;; Update expire date
(cond ((null date)) ;Permanent entry.
((and found gnus-update-score-entry-dates) ;Match, update date.
(setq rest entries)))
(setq entries rest))))
;; We change the score file back to the previous one.
- (gnus-score-load-file current-score-file)))
+ (save-excursion
+ (set-buffer gnus-summary-buffer)
+ (gnus-score-load-file current-score-file))
+ (list (cons "references" news))))
(defun gnus-score-add-followups (header score scores &optional thread)
+ "Add a score entry to the adapt file."
(save-excursion
(set-buffer gnus-summary-buffer)
(let* ((id (mail-header-id header))
(scores (car scores))
entry dont)
;; Don't enter a score if there already is one.
- (while scores
- (setq entry (car scores))
+ (while (setq entry (pop scores))
(and (equal "references" (car entry))
- (or (null (nth 3 (car (cdr entry))))
- (eq 's (nth 3 (car (cdr entry)))))
- (progn
- (if (assoc id entry)
- (setq dont t))))
- (setq scores (cdr scores)))
- (or dont
- (gnus-summary-score-entry
- (if thread "thread" "references")
- id 's score (current-time-string) nil t)))))
-
+ (or (null (nth 3 (cadr entry)))
+ (eq 's (nth 3 (cadr entry))))
+ (assoc id entry)
+ (setq dont t)))
+ (unless dont
+ (gnus-summary-score-entry
+ (if thread "thread" "references")
+ id 's score (current-time-string) nil t)))))
(defun gnus-score-string (score-list header now expire &optional trace)
;; Score ARTICLES according to HEADER in SCORE-LIST.
(gnus-score-set 'touched '(t) alist)
(setcdr entries (cdr rest))
(setq rest entries)))))
- (setq entries rest)))))))
+ (setq entries rest))))))
+ nil)
(defun gnus-score-string< (a1 a2)
;; Compare headers in articles A2 and A2.
(setq elem (cdr elem))
(while elem
(setcdr (car elem)
- (cons (if (eq (car (car elem)) 'followup)
+ (cons (if (eq (caar elem) 'followup)
"references"
- (symbol-name (car (car elem))))
- (cdr (car elem))))
+ (symbol-name (caar elem)))
+ (cdar elem)))
(setcar (car elem)
`(lambda (h)
(,(intern
(concat "mail-header-"
- (if (eq (car (car elem)) 'followup)
+ (if (eq (caar elem) 'followup)
"message-id"
- (downcase (symbol-name (car (car elem)))))))
+ (downcase (symbol-name (caar elem))))))
h)))
(setq elem (cdr elem)))
(setq malist (cdr malist)))
()
(when (setq headers (gnus-data-header (car data)))
(while elem
- (setq match (funcall (car (car elem)) headers))
+ (setq match (funcall (caar elem) headers))
(gnus-summary-score-entry
(nth 1 (car elem)) match
(cond
(defun gnus-score-edit-insert-date ()
"Insert date in numerical format."
(interactive)
- (insert (int-to-string (gnus-day-number (current-time-string)))))
+ (princ (gnus-day-number (current-time-string)) (current-buffer)))
(defun gnus-score-pretty-print ()
"Format the current score file."
(gnus-add-current-to-buffer-list)
(erase-buffer)
(while trace
- (insert (format "%S -> %s\n" (cdr (car trace))
- (file-name-nondirectory (car (car trace)))))
+ (insert (format "%S -> %s\n" (cdar trace)
+ (file-name-nondirectory (caar trace))))
(setq trace (cdr trace)))
(goto-char (point-min))
(pop-to-buffer buf)))
gnus-short-name-score-file-cache nil)
(gnus-message 6 "The score cache is now flushed"))
+(gnus-add-shutdown 'gnus-score-close 'gnus)
+
(defun gnus-score-close ()
"Clear all internal score variables."
(setq gnus-score-cache nil
- gnus-internal-global-score-files nil))
+ gnus-internal-global-score-files nil
+ gnus-score-file-list nil
+ gnus-score-file-alist-cache nil))
;; Summary score marking commands.
(setq gnus-score-file-list
(cons nil
(or gnus-short-name-score-file-cache
- (setq gnus-short-name-score-file-cache
- (gnus-score-score-files-1
- gnus-kill-files-directory)))))
+ (prog2
+ (gnus-message 6 "Finding all score files...")
+ (setq gnus-short-name-score-file-cache
+ (gnus-score-score-files-1
+ gnus-kill-files-directory))
+ (gnus-message 6 "Finding all score files...done")))))
;; We want long file names.
(when (or (not gnus-score-file-list)
(not (car gnus-score-file-list))
;; Add files to the list of score files.
((string-match regexp file)
(push file out))))
- out))
+ (or out
+ ;; Return a dummy value.
+ (list "~/News/this.file.does.not.exist.SCORE"))))
(defun gnus-score-file-regexp ()
"Return a regexp that match all score files."
(defun gnus-score-find-single (group)
"Return list containing the score file for GROUP."
- (list (gnus-score-file-name group gnus-adaptive-file-suffix)
+ (list (or gnus-newsgroup-adaptive-score-file
+ (gnus-score-file-name group gnus-adaptive-file-suffix))
(gnus-score-file-name group)))
(defun gnus-score-find-hierarchical (group)
(cdr score-files) ;ensures caching groups with no matches
;; handle the multiple match alist
(while alist
- (and (string-match (car (car alist)) group)
+ (and (string-match (caar alist) group)
(setq score-files
- (nconc score-files (copy-sequence (cdr (car alist))))))
+ (nconc score-files (copy-sequence (cdar alist)))))
(setq alist (cdr alist)))
(setq alist gnus-score-file-single-match-alist)
;; handle the single match alist
(while alist
- (and (string-match (car (car alist)) group)
+ (and (string-match (caar alist) group)
;; progn used just in case ("regexp") has no files
;; and score-files is still nil. -sj
;; this can be construed as a "stop searching here" feature :>
;; and used to simplify regexps in the single-alist
(progn
(setq score-files
- (nconc score-files (copy-sequence (cdr (car alist)))))
+ (nconc score-files (copy-sequence (cdar alist))))
(setq alist nil)))
(setq alist (cdr alist)))
;; cache the score files