:type 'integer)
(defcustom gnus-agent-expire-days 7
- "Read articles older than this will be expired."
+ "Read articles older than this will be expired.
+If you wish to disable Agent expiring, see `gnus-agent-enable-expiration'."
:group 'gnus-agent
:type '(number :tag "days"))
:group 'gnus-agent)
(defcustom gnus-agent-consider-all-articles nil
- "If non-nil, consider also the read articles for downloading."
+ "When non-`nil', the agent will let the agent predicate decide
+whether articles need to be downloaded or not, for all articles. When
+`nil', the default, the agent will only let the predicate decide
+whether unread articles are downloaded or not. If you enable this,
+groups with large active ranges may open slower and you may also want
+to look into the agent expiry settings to block the expiration of
+read articles as they would just be downloaded again."
:version "21.4"
:type 'boolean
:group 'gnus-agent)
(const :format "Disable " DISABLE)))
(defcustom gnus-agent-expire-unagentized-dirs t
-"Have gnus-agent-expire scan the directories under
-\(gnus-agent-directory) for groups that are no longer agentized. When
-found, offer to remove them.")
+ "*Whether expiration should expire in unagentized directories.
+Have gnus-agent-expire scan the directories under
+\(gnus-agent-directory) for groups that are no longer agentized.
+When found, offer to remove them."
+ :type 'boolean
+ :group 'gnus-agent)
+
+(defcustom gnus-agent-auto-agentize-methods '(nntp nnimap)
+ "Initially, all servers from these methods are agentized.
+The user may remove or add servers using the Server buffer.
+See Info node `(gnus)Server Buffer'."
+ :type '(repeat symbol)
+ :group 'gnus-agent)
+
+(defcustom gnus-agent-queue-mail t
+ "Whether and when outgoing mail should be queued by the agent. When
+`always', always queue outgoing mail. When `nil', never queue.
+Otherwise, queue if and only if unplugged."
+ :group 'gnus-agent
+ :type '(radio (const :format "Always" always)
+ (const :format "Never" nil)
+ (const :format "When plugged" t)))
+
+(defcustom gnus-agent-prompt-send-queue nil
+ "If non-nil, `gnus-group-send-queue' will prompt if called when
+unplugged."
+ :group 'gnus-agent
+ :type 'boolean)
;;; Internal variables
(defvar gnus-agent-file-loading-cache nil)
(defvar gnus-agent-file-header-cache nil)
-(defvar gnus-agent-auto-agentize-methods '(nntp nnimap)
- "Initially, all servers from these methods are agentized.
-The user may remove or add servers using the Server buffer. See Info
-node `(gnus)Server Buffer'.")
-
;; Dynamic variables
(defvar gnus-headers)
(defvar gnus-score)
(gnus-agent-cat-defaccessor
gnus-agent-cat-score-file agent-score-file)
(gnus-agent-cat-defaccessor
- gnus-agent-cat-disable-undownloaded-faces agent-disable-undownloaded-faces)
+ gnus-agent-cat-enable-undownloaded-faces agent-enable-undownloaded-faces)
-(eval-when-compile
+(eval-and-compile
(defsetf gnus-agent-cat-groups (category) (groups)
(list 'gnus-agent-set-cat-groups category groups)))
(unless gnus-agent-send-mail-function
(setq gnus-agent-send-mail-function
(or message-send-mail-real-function
- message-send-mail-function)
+ message-send-mail-function)
message-send-mail-real-function 'gnus-agent-send-mail))
- (unless gnus-agent-covered-methods
+ ;; If the servers file doesn't exist, auto-agentize some servers and
+ ;; save the servers file so this auto-agentizing isn't invoked
+ ;; again.
+ (unless (file-exists-p (nnheader-concat gnus-agent-directory "lib/servers"))
+ (gnus-message 3 "First time agent user, agentizing remote groups...")
(mapc
(lambda (server-or-method)
(let ((method (gnus-server-to-method server-or-method)))
- (when (memq (car method)
- gnus-agent-auto-agentize-methods)
- (push (gnus-method-to-server method)
- gnus-agent-covered-methods)
- (setq gnus-agent-method-p-cache nil))))
- (cons gnus-select-method gnus-secondary-select-methods))))
+ (when (memq (car method)
+ gnus-agent-auto-agentize-methods)
+ (push (gnus-method-to-server method)
+ gnus-agent-covered-methods)
+ (setq gnus-agent-method-p-cache nil))))
+ (cons gnus-select-method gnus-secondary-select-methods))
+ (gnus-agent-write-servers)))
(defun gnus-agent-queue-setup (&optional group-name)
"Make sure the queue group exists.
'gnus-dummy '((gnus-draft-mode)))))
(defun gnus-agent-send-mail ()
- (if gnus-plugged
+ (if (or (not gnus-agent-queue-mail)
+ (and gnus-plugged (not (eq gnus-agent-queue-mail 'always))))
(funcall gnus-agent-send-mail-function)
(goto-char (point-min))
(re-search-forward
(setq gnus-newsgroup-downloadable
(delq article gnus-newsgroup-downloadable))
- ;; The downloadable mark is implemented as a
- ;; type of read mark. Therefore, marking the
- ;; article as unread is sufficient to clear
- ;; its downloadable flag.
(gnus-summary-mark-article article gnus-unread-mark))
(was-marked-downloadable
(gnus-summary-set-agent-mark article t)))
;;; Internal functions
;;;
-;;; NOTES:
-;;; The agent's active range is defined as follows:
-;;; If the agent has no record of the group, use the actual active
-;;; range.
-;;; If the agent has a record, set the agent's active range to
-;;; include the max limit of the actual active range.
-;;; When expiring, update the min limit to match the smallest of the
-;;; min article not expired or the min actual active range.
-
(defun gnus-agent-save-active (method)
- (gnus-agent-save-active-1 method 'gnus-active-to-gnus-format))
-
-(defun gnus-agent-save-active-1 (method function)
(when (gnus-agent-method-p method)
(let* ((gnus-command-method method)
(new (gnus-make-hashtable (count-lines (point-min) (point-max))))
(file (gnus-agent-lib-file "active")))
- (funcall function nil new)
+ (gnus-active-to-gnus-format nil new)
(gnus-agent-write-active file new)
(erase-buffer)
(nnheader-insert-file-contents file))))
-(defun gnus-agent-write-active (file new &optional literal-replacement)
- (let ((old new))
- (when (and (not literal-replacement)
- (file-exists-p file))
- (setq old (gnus-make-hashtable (count-lines (point-min) (point-max))))
- (with-temp-buffer
- (nnheader-insert-file-contents file)
- (gnus-active-to-gnus-format nil old))
- ;; Iterate over the current active groups, the current active
- ;; range may expand, but NOT CONTRACT, the agent's active range.
- (mapatoms
- (lambda (nsym)
- (let ((new-active (and nsym (boundp nsym) (symbol-value nsym))))
- (when new-active
- (let* ((osym (intern (symbol-name nsym) old))
- (old-active (and (boundp osym) (symbol-value osym))))
- (if old-active
- (let ((new-min (car new-active))
- (old-min (car old-active))
- (new-max (cdr new-active))
- (old-max (cdr old-active)))
- (if (and (integerp new-min)
- (< new-min old-min))
- (setcar old-active new-min))
- (if (and (integerp new-max)
- (> new-max old-max))
- (setcdr old-active new-max)))
- (set osym new-active))))))
- new))
+(defun gnus-agent-write-active (file new)
(gnus-make-directory (file-name-directory file))
(let ((nnmail-active-file-coding-system gnus-agent-file-coding-system))
;; The hashtable contains real names of groups. However, do NOT
;; add the foreign server prefix as gnus-active-to-gnus-format
;; will add it while reading the file.
- (gnus-write-active-file file old nil))))
+ (gnus-write-active-file file new nil)))
-(defun gnus-agent-save-groups (method)
- (gnus-agent-save-active-1 method 'gnus-groups-to-gnus-format))
+(defun gnus-agent-possibly-alter-active (group active &optional info)
+ "Possibly expand a group's active range to include articles
+downloaded into the agent."
+ (let* ((gnus-command-method (or gnus-command-method
+ (gnus-find-method-for-group group))))
+ (when (gnus-agent-method-p gnus-command-method)
+ (let* ((local (gnus-agent-get-local group))
+ (active-min (car active))
+ (active-max (cdr active))
+ (agent-min (or (car local) active-min))
+ (agent-max (or (cdr local) active-max)))
+
+ (when (< agent-min active-min)
+ (setcar active agent-min))
+
+ (when (> agent-max active-max)
+ (setcdr active agent-max))
+
+ (when (and info (< agent-max (- active-min 100)))
+ ;; I'm expanding the active range by such a large amount
+ ;; that there is a gap of more than 100 articles between the
+ ;; last article known to the agent and the first article
+ ;; currently available on the server. This gap contains
+ ;; articles that have been lost, mark them as read so that
+ ;; gnus doesn't waste resources trying to fetch them.
+
+ ;; NOTE: I don't do this for smaller gaps (< 100) as I don't
+ ;; want to modify the local file everytime someone restarts
+ ;; gnus. The small gap will cause a tiny performance hit
+ ;; when gnus tries, and fails, to retrieve the articles.
+ ;; Still that should be smaller than opening a buffer,
+ ;; printing this list to the buffer, and then writing it to a
+ ;; file.
+
+ (let ((read (gnus-info-read info)))
+ (gnus-info-set-read
+ info
+ (gnus-range-add
+ read
+ (list (cons (1+ agent-max)
+ (1- active-min))))))
+
+ ;; Lie about the agent's local range for this group to
+ ;; disable the set read each time this server is opened.
+ ;; NOTE: Opening this group will restore the valid local
+ ;; range but it will also expand the local range to
+ ;; incompass the new active range.
+ (gnus-agent-set-local group agent-min (1- active-min)))))))
(defun gnus-agent-save-group-info (method group active)
+ "Update a single group's active range in the agent's copy of the server's active file."
(when (gnus-agent-method-p method)
(let* ((gnus-command-method method)
(coding-system-for-write nnheader-file-coding-system)
(gnus-message 1
"Overview buffer contains garbage '%s'."
(buffer-substring
- p (gnus-point-at-eol))))
+ p (point-at-eol))))
((= cur prev-num)
(or backed-up
(setq backed-up (gnus-agent-backup-overview-buffer)))
(setq uncomp (cons (cons article-id state) uncomp)))
sequence)))
alist)
- (setq alist (sort uncomp
- (lambda (first second)
- (< (car first) (car second))))))))
+ (setq alist (sort uncomp 'car-less-than-car)))))
(when changed-version
(let ((gnus-agent-article-alist alist))
(gnus-agent-save-alist gnus-agent-read-agentview)))
alist))))
-(defun gnus-agent-save-alist (group &optional articles state dir)
+(defun gnus-agent-save-alist (group &optional articles state)
"Save the article-state alist for GROUP."
(let* ((file-name-coding-system nnmail-pathname-coding-system)
(prev (cons nil gnus-agent-article-alist))
(setcdr (cadr prev) state)))
(setq prev (cdr prev)))
(setq gnus-agent-article-alist (cdr all))
- (if dir
- (gnus-make-directory dir)
- (gnus-make-directory (gnus-agent-article-name "" group)))
- (with-temp-file (if dir
- (expand-file-name ".agentview" dir)
- (gnus-agent-article-name ".agentview" group))
+
+ (gnus-agent-set-local group
+ (caar gnus-agent-article-alist)
+ (caar (last gnus-agent-article-alist)))
+
+ (gnus-make-directory (gnus-agent-article-name "" group))
+ (with-temp-file (gnus-agent-article-name ".agentview" group)
(cond ((eq gnus-agent-article-alist-save-format 1)
(princ gnus-agent-article-alist (current-buffer)))
((eq gnus-agent-article-alist-save-format 2)
(princ gnus-agent-article-alist-save-format (current-buffer))
(insert "\n"))))
+(defvar gnus-agent-article-local nil)
+(defvar gnus-agent-file-loading-local nil)
+
+(defun gnus-agent-load-local (&optional method)
+ "Load the METHOD'S local file. The local file contains min/max
+article counts for each of the method's subscribed groups."
+ (let ((gnus-command-method (or method gnus-command-method)))
+ (setq gnus-agent-article-local
+ (gnus-cache-file-contents
+ (gnus-agent-lib-file "local")
+ 'gnus-agent-file-loading-local
+ 'gnus-agent-read-and-cache-local))))
+
+(defun gnus-agent-read-and-cache-local (file)
+ "Load and read FILE then bind its contents to
+gnus-agent-article-local. If that variable had `dirty' (also known as
+modified) original contents, they are first saved to their own file."
+
+ (if (and gnus-agent-article-local
+ (symbol-value (intern "+dirty" gnus-agent-article-local)))
+ (gnus-agent-save-local))
+ (gnus-agent-read-local file))
+
+(defun gnus-agent-read-local (file)
+ "Load FILE and do a `read' there."
+ (let ((obarray (gnus-make-hashtable (count-lines (point-min) (point-max))))
+ (line 1))
+ (with-temp-buffer
+ (condition-case nil
+ (nnheader-insert-file-contents file)
+ (file-error))
+
+ (goto-char (point-min))
+ ;; Skip any comments at the beginning of the file (the only place where they may appear)
+ (while (= (following-char) ?\;)
+ (forward-line 1)
+ (setq line (1+ line)))
+
+ (while (not (eobp))
+ (condition-case err
+ (let (group
+ min
+ max
+ (cur (current-buffer)))
+ (setq group (read cur)
+ min (read cur)
+ max (read cur))
+
+ (when (stringp group)
+ (setq group (intern group obarray)))
+
+ ;; NOTE: The '+ 0' ensure that min and max are both numerics.
+ (set group (cons (+ 0 min) (+ 0 max))))
+ (error
+ (gnus-message 3 "Warning - invalid agent local: %s on line %d: " file line (error-message-string err))))
+ (forward-line 1)
+ (setq line (1+ line))))
+
+ (set (intern "+dirty" obarray) nil)
+ (set (intern "+method" obarray) gnus-command-method)
+ obarray))
+
+(defun gnus-agent-save-local (&optional force)
+ "Save gnus-agent-article-local under it method's agent.lib directory."
+ (let ((obarray gnus-agent-article-local))
+ (when (and obarray
+ (or force (symbol-value (intern "+dirty" obarray))))
+ (let* ((gnus-command-method (symbol-value (intern "+method" obarray)))
+ ;; NOTE: gnus-command-method is used within gnus-agent-lib-file.
+ (dest (gnus-agent-lib-file "local")))
+ (gnus-make-directory (gnus-agent-lib-file ""))
+ (with-temp-file dest
+ (let ((gnus-command-method (symbol-value (intern "+method" obarray)))
+ (file-name-coding-system nnmail-pathname-coding-system)
+ (coding-system-for-write
+ gnus-agent-file-coding-system)
+ print-level print-length item article
+ (standard-output (current-buffer)))
+ (mapatoms (lambda (symbol)
+ (cond ((not (boundp symbol))
+ nil)
+ ((member (symbol-name symbol) '("+dirty" "+method"))
+ nil)
+ (t
+ (prin1 symbol)
+ (let ((range (symbol-value symbol)))
+ (princ " ")
+ (princ (car range))
+ (princ " ")
+ (princ (cdr range))
+ (princ "\n"))))))))))))
+
+(defun gnus-agent-get-local (group)
+ (let* ((gmane (gnus-group-real-name group))
+ (gnus-command-method (gnus-find-method-for-group group))
+ (local (gnus-agent-load-local))
+ (symb (intern gmane local))
+ (minmax (and (boundp symb) (symbol-value symb))))
+ (unless minmax
+ ;; Bind these so that gnus-agent-load-alist doesn't change the
+ ;; current alist (i.e. gnus-agent-article-alist)
+ (let* ((gnus-agent-article-alist gnus-agent-article-alist)
+ (gnus-agent-file-loading-cache gnus-agent-file-loading-cache)
+ (alist (gnus-agent-load-alist group)))
+ (when alist
+ (setq minmax
+ (cons (caar alist)
+ (caar (last alist))))
+ (gnus-agent-set-local group (car minmax) (cdr minmax)
+ gmane gnus-command-method local))))
+ minmax))
+
+(defun gnus-agent-set-local (group min max &optional gmane method local)
+ (let* ((gmane (or gmane (gnus-group-real-name group)))
+ (gnus-command-method (or method (gnus-find-method-for-group group)))
+ (local (or local (gnus-agent-load-local)))
+ (symb (intern gmane local))
+ (minmax (and (boundp symb) (symbol-value symb))))
+
+ (if (cond ((and minmax
+ (or (not (eq min (car minmax)))
+ (not (eq max (cdr minmax)))))
+ (setcar minmax min)
+ (setcdr minmax max)
+ t)
+ (minmax
+ nil)
+ (t
+ (set symb (cons min max))
+ t))
+ (set (intern "+dirty" local) t))))
+
(defun gnus-agent-article-name (article group)
(expand-file-name article
(file-name-as-directory
(gnus-category-position-point)))
(defun gnus-category-name ()
- (or (intern (get-text-property (gnus-point-at-bol) 'gnus-category))
+ (or (intern (get-text-property (point-at-bol) 'gnus-category))
(error "No category on the current line")))
(defun gnus-category-read ()
(defun gnus-predicate-implies-unread (predicate)
"Say whether PREDICATE implies unread articles only.
It is okay to miss some cases, but there must be no false positives.
-That is, if this function returns true, then indeed the predicate must
+That is, if this predicate returns true, then indeed the predicate must
return only unread articles."
- (gnus-function-implies-unread-1 (gnus-category-make-function predicate)))
+ (eq t (gnus-function-implies-unread-1
+ (gnus-category-make-function-1 predicate))))
(defun gnus-function-implies-unread-1 (function)
- (cond ((eq function (symbol-function 'gnus-agent-read-p))
- nil)
- ((not function)
- nil)
- ((functionp function)
- 'ignore)
- ((memq (car function) '(or and not))
- (apply (car function)
- (mapcar 'gnus-function-implies-unread-1 (cdr function))))
- (t
- (error "Unknown function: %s" function))))
+ "Recursively evaluate a predicate function to determine whether it can select
+any read articles. Returns t if the function is known to never
+return read articles, nil when it is known to always return read
+articles, and t_nil when the function may return both read and unread
+articles."
+ (let ((func (car function))
+ (args (mapcar 'gnus-function-implies-unread-1 (cdr function))))
+ (cond ((eq func 'and)
+ (cond ((memq t args) ; if any argument returns only unread articles
+ ;; then that argument constrains the result to only unread articles.
+ t)
+ ((memq 't_nil args) ; if any argument is indeterminate
+ ;; then the result is indeterminate
+ 't_nil)))
+ ((eq func 'or)
+ (cond ((memq nil args) ; if any argument returns read articles
+ ;; then that argument ensures that the results includes read articles.
+ nil)
+ ((memq 't_nil args) ; if any argument is indeterminate
+ ;; then that argument ensures that the results are indeterminate
+ 't_nil)
+ (t ; if all arguments return only unread articles
+ ;; then the result returns only unread articles
+ t)))
+ ((eq func 'not)
+ (cond ((eq (car args) 't_nil) ; if the argument is indeterminate
+ ; then the result is indeterminate
+ (car args))
+ (t ; otherwise
+ ; toggle the result to be the opposite of the argument
+ (not (car args)))))
+ ((eq func 'gnus-agent-read-p)
+ nil) ; The read predicate NEVER returns unread articles
+ ((eq func 'gnus-agent-false)
+ t) ; The false predicate returns t as the empty set excludes all read articles
+ ((eq func 'gnus-agent-true)
+ nil) ; The true predicate ALWAYS returns read articles
+ ((catch 'found-match
+ (let ((alist gnus-category-predicate-alist))
+ (while alist
+ (if (eq func (cdar alist))
+ (throw 'found-match t)
+ (setq alist (cdr alist))))))
+ 't_nil) ; All other predicates return read and unread articles
+ (t
+ (error "Unknown predicate function: %s" function)))))
(defun gnus-group-category (group)
"Return the category GROUP belongs to."
(save-excursion
(gnus-agent-expire-group-1
group overview (gnus-gethash-safe group orig)
- articles force))
- (gnus-agent-write-active active-file orig t)))
+ articles force))))
(kill-buffer overview))))
(gnus-message 4 (gnus-agent-expire-done-message)))))
(while dlist
(let ((new-completed (truncate (* 100.0
(/ (setq cnt (1+ cnt))
- len)))))
+ len))))
+ message-log-max)
(when (> new-completed completed)
(setq completed new-completed)
(gnus-message 7 "%3d%% completed..." completed)))
;; Kept articles are unread, marked, or special.
(keep
(gnus-agent-message 10
- "gnus-agent-expire: Article %d: Kept %s article%s."
- article-number keep (if fetch-date " and file" ""))
+ "gnus-agent-expire: %s:%d: Kept %s article%s."
+ group article-number keep (if fetch-date " and file" ""))
(when fetch-date
(unless (file-exists-p
(concat dir (number-to-string
article-number)))
(setf (nth 1 entry) nil)
(gnus-agent-message 3 "gnus-agent-expire cleared \
-download flag on article %d as the cached article file is missing."
- (caar dlist)))
+download flag on %s:%d as the cached article file is missing."
+ group (caar dlist)))
(unless marker
(gnus-message 1 "gnus-agent-expire detected a \
missing NOV entry. Run gnus-agent-regenerate-group to restore it.")))
(incf (nth 0 stats))
- (let ((from (gnus-point-at-bol))
+ (let ((from (point-at-bol))
(to (progn (forward-line 1) (point))))
(incf (nth 2 stats) (- to from))
(delete-region from to)))
(push (format "Removed %s article number from \
article alist" type) actions))
- (gnus-agent-message 8 "gnus-agent-expire: Article %d: %s"
- article-number
- (mapconcat 'identity actions ", "))))
+ (when actions
+ (gnus-agent-message 8 "gnus-agent-expire: %s:%d: %s"
+ group article-number
+ (mapconcat 'identity actions ", ")))))
(t
(gnus-agent-message
- 10 "gnus-agent-expire: Article %d: Article kept as \
-expiration tests failed." article-number)
+ 10 "gnus-agent-expire: %s:%d: Article kept as \
+expiration tests failed." group article-number)
(gnus-agent-append-to-list
tail-alist (cons article-number fetch-date)))
)
(let ((inhibit-quit t))
(unless (equal alist gnus-agent-article-alist)
(setq gnus-agent-article-alist alist)
- (gnus-agent-save-alist group)
-
- ;; The active list changed, set the agent's active range
- ;; to match the beginning of the list.
- (if alist
- (setcar active (caar alist))))
+ (gnus-agent-save-alist group))
(when (buffer-modified-p)
(let ((coding-system-for-write
(when active
(save-excursion
(gnus-agent-expire-group-1
- expiring-group overview active articles force)))))
- (gnus-agent-write-active active-file orig t))))
+ expiring-group overview active articles force))))))))
(kill-buffer overview))
(gnus-agent-expire-unagentized-dirs)
(gnus-message 4 (gnus-agent-expire-done-message))))))
def
select)))
(catch 'mark
- (while (let ((c (read-char-exclusive
- "Mark as unread: (n)one / (a)ll / all (d)ownloaded articles? (n)"
- )))
+ (while (let (c
+ (cursor-in-echo-area t)
+ (echo-keystrokes 0))
+ (message "Mark as unread: (n)one / (a)ll / all (d)ownloaded articles? (n) ")
+ (setq c (read-char-exclusive))
+
(cond ((or (eq c ?\r) (eq c ?n) (eq c ?N))
(throw 'mark nil))
((or (eq c ?a) (eq c ?A))
(throw 'mark t))
((or (eq c ?d) (eq c ?D))
(throw 'mark 'some)))
- (message "Ignoring unexpected input")
+ (gnus-message 3 "Ignoring unexpected input")
(sit-for 1)
t)))))
- (gnus-message 5 "Regenerating in %s" group)
- (let* ((gnus-command-method (or gnus-command-method
- (gnus-find-method-for-group group)))
- (file (gnus-agent-article-name ".overview" group))
- (dir (file-name-directory file))
- point
- (downloaded (if (file-exists-p dir)
- (sort (mapcar (lambda (name) (string-to-int name))
- (directory-files dir nil "^[0-9]+$" t))
- '>)
- (progn (gnus-make-directory dir) nil)))
- dl nov-arts
- alist header
- regenerated)
-
- (mm-with-unibyte-buffer
- (if (file-exists-p file)
- (let ((nnheader-file-coding-system
- gnus-agent-file-coding-system))
- (nnheader-insert-file-contents file)))
- (set-buffer-modified-p nil)
-
- ;; Load the article IDs found in the overview file. As a
- ;; side-effect, validate the file contents.
- (let ((load t))
- (while load
- (setq load nil)
- (goto-char (point-min))
- (while (< (point) (point-max))
- (cond ((and (looking-at "[0-9]+\t")
- (<= (- (match-end 0) (match-beginning 0)) 9))
- (push (read (current-buffer)) nov-arts)
- (forward-line 1)
- (let ((l1 (car nov-arts))
- (l2 (cadr nov-arts)))
- (cond ((not l2)
- nil)
- ((< l1 l2)
- (gnus-message 3 "gnus-agent-regenerate-group: NOV\
+
+ (when group
+ (gnus-message 5 "Regenerating in %s" group)
+ (let* ((gnus-command-method (or gnus-command-method
+ (gnus-find-method-for-group group)))
+ (file (gnus-agent-article-name ".overview" group))
+ (dir (file-name-directory file))
+ point
+ (downloaded (if (file-exists-p dir)
+ (sort (mapcar (lambda (name) (string-to-int name))
+ (directory-files dir nil "^[0-9]+$" t))
+ '>)
+ (progn (gnus-make-directory dir) nil)))
+ dl nov-arts
+ alist header
+ regenerated)
+
+ (mm-with-unibyte-buffer
+ (if (file-exists-p file)
+ (let ((nnheader-file-coding-system
+ gnus-agent-file-coding-system))
+ (nnheader-insert-file-contents file)))
+ (set-buffer-modified-p nil)
+
+ ;; Load the article IDs found in the overview file. As a
+ ;; side-effect, validate the file contents.
+ (let ((load t))
+ (while load
+ (setq load nil)
+ (goto-char (point-min))
+ (while (< (point) (point-max))
+ (cond ((and (looking-at "[0-9]+\t")
+ (<= (- (match-end 0) (match-beginning 0)) 9))
+ (push (read (current-buffer)) nov-arts)
+ (forward-line 1)
+ (let ((l1 (car nov-arts))
+ (l2 (cadr nov-arts)))
+ (cond ((not l2)
+ nil)
+ ((< l1 l2)
+ (gnus-message 3 "gnus-agent-regenerate-group: NOV\
entries are NOT in ascending order.")
- ;; Don't sort now as I haven't verified
- ;; that every line begins with a number
- (setq load t))
- ((= l1 l2)
- (forward-line -1)
- (gnus-message 4 "gnus-agent-regenerate-group: NOV\
+ ;; Don't sort now as I haven't verified
+ ;; that every line begins with a number
+ (setq load t))
+ ((= l1 l2)
+ (forward-line -1)
+ (gnus-message 4 "gnus-agent-regenerate-group: NOV\
entries contained duplicate of article %s. Duplicate deleted." l1)
- (gnus-delete-line)
- (setq nov-arts (cdr nov-arts))))))
- (t
- (gnus-message 1 "gnus-agent-regenerate-group: NOV\
+ (gnus-delete-line)
+ (setq nov-arts (cdr nov-arts))))))
+ (t
+ (gnus-message 1 "gnus-agent-regenerate-group: NOV\
entries contained line that did not begin with an article number. Deleted\
line.")
- (gnus-delete-line))))
- (if load
- (progn
- (gnus-message 5 "gnus-agent-regenerate-group: Sorting NOV\
+ (gnus-delete-line))))
+ (if load
+ (progn
+ (gnus-message 5 "gnus-agent-regenerate-group: Sorting NOV\
entries into ascending order.")
- (sort-numeric-fields 1 (point-min) (point-max))
+ (sort-numeric-fields 1 (point-min) (point-max))
(setq nov-arts nil)))))
- (gnus-agent-check-overview-buffer)
-
- ;; Construct a new article alist whose nodes match every header
- ;; in the .overview file. As a side-effect, missing headers are
- ;; reconstructed from the downloaded article file.
- (while (or downloaded nov-arts)
- (cond ((and downloaded
- (or (not nov-arts)
- (> (car downloaded) (car nov-arts))))
- ;; This entry is missing from the overview file
- (gnus-message 3 "Regenerating NOV %s %d..." group
- (car downloaded))
- (let ((file (concat dir (number-to-string (car downloaded)))))
- (mm-with-unibyte-buffer
- (nnheader-insert-file-contents file)
- (nnheader-remove-body)
- (setq header (nnheader-parse-naked-head)))
- (mail-header-set-number header (car downloaded))
- (if nov-arts
- (let ((key (concat "^" (int-to-string (car nov-arts))
- "\t")))
- (or (re-search-backward key nil t)
- (re-search-forward key))
- (forward-line 1))
- (goto-char (point-min)))
- (nnheader-insert-nov header))
- (setq nov-arts (cons (car downloaded) nov-arts)))
- ((eq (car downloaded) (car nov-arts))
- ;; This entry in the overview has been downloaded
- (push (cons (car downloaded)
- (time-to-days
- (nth 5 (file-attributes
- (concat dir (number-to-string
- (car downloaded))))))) alist)
- (setq downloaded (cdr downloaded))
- (setq nov-arts (cdr nov-arts)))
- (t
- ;; This entry in the overview has not been downloaded
- (push (cons (car nov-arts) nil) alist)
- (setq nov-arts (cdr nov-arts)))))
-
- ;; When gnus-agent-consider-all-articles is set,
- ;; gnus-agent-regenerate-group should NOT remove article IDs from
- ;; the alist. Those IDs serve as markers to indicate that an
- ;; attempt has been made to fetch that article's header.
-
- ;; When gnus-agent-consider-all-articles is NOT set,
- ;; gnus-agent-regenerate-group can remove the article ID of every
- ;; article (with the exception of the last ID in the list - it's
- ;; special) that no longer appears in the overview. In this
- ;; situtation, the last article ID in the list implies that it,
- ;; and every article ID preceeding it, have been fetched from the
- ;; server.
- (if gnus-agent-consider-all-articles
- ;; Restore all article IDs that were not found in the overview file.
- (let* ((n (cons nil alist))
- (merged n)
- (o (gnus-agent-load-alist group)))
- (while o
- (let ((nID (caadr n))
- (oID (caar o)))
- (cond ((not nID)
- (setq n (setcdr n (list (list oID))))
- (setq o (cdr o)))
- ((< oID nID)
- (setcdr n (cons (list oID) (cdr n)))
- (setq o (cdr o)))
- ((= oID nID)
- (setq o (cdr o))
- (setq n (cdr n)))
- (t
- (setq n (cdr n))))))
- (setq alist (cdr merged)))
- ;; Restore the last article ID if it is not already in the new alist
- (let ((n (last alist))
- (o (last (gnus-agent-load-alist group))))
- (cond ((not o)
- nil)
- ((not n)
- (push (cons (caar o) nil) alist))
- ((< (caar n) (caar o))
- (setcdr n (list (car o)))))))
-
- (let ((inhibit-quit t))
- (if (setq regenerated (buffer-modified-p))
- (let ((coding-system-for-write gnus-agent-file-coding-system))
- (write-region (point-min) (point-max) file nil 'silent)))
-
- (setq regenerated (or regenerated
- (and reread gnus-agent-article-alist)
- (not (equal alist gnus-agent-article-alist)))
- )
-
- (setq gnus-agent-article-alist alist)
-
- (when regenerated
- (gnus-agent-save-alist group)))
- )
-
- (when (and reread gnus-agent-article-alist)
- (gnus-make-ascending-articles-unread
- group
- (delq nil (mapcar (function (lambda (c)
- (cond ((eq reread t)
- (car c))
- ((cdr c)
- (car c)))))
- gnus-agent-article-alist)))
-
- (when (gnus-buffer-live-p gnus-group-buffer)
- (gnus-group-update-group group t)
- (sit-for 0))
- )
-
- (gnus-message 5 nil)
- regenerated))
+ (gnus-agent-check-overview-buffer)
+
+ ;; Construct a new article alist whose nodes match every header
+ ;; in the .overview file. As a side-effect, missing headers are
+ ;; reconstructed from the downloaded article file.
+ (while (or downloaded nov-arts)
+ (cond ((and downloaded
+ (or (not nov-arts)
+ (> (car downloaded) (car nov-arts))))
+ ;; This entry is missing from the overview file
+ (gnus-message 3 "Regenerating NOV %s %d..." group
+ (car downloaded))
+ (let ((file (concat dir (number-to-string (car downloaded)))))
+ (mm-with-unibyte-buffer
+ (nnheader-insert-file-contents file)
+ (nnheader-remove-body)
+ (setq header (nnheader-parse-naked-head)))
+ (mail-header-set-number header (car downloaded))
+ (if nov-arts
+ (let ((key (concat "^" (int-to-string (car nov-arts))
+ "\t")))
+ (or (re-search-backward key nil t)
+ (re-search-forward key))
+ (forward-line 1))
+ (goto-char (point-min)))
+ (nnheader-insert-nov header))
+ (setq nov-arts (cons (car downloaded) nov-arts)))
+ ((eq (car downloaded) (car nov-arts))
+ ;; This entry in the overview has been downloaded
+ (push (cons (car downloaded)
+ (time-to-days
+ (nth 5 (file-attributes
+ (concat dir (number-to-string
+ (car downloaded))))))) alist)
+ (setq downloaded (cdr downloaded))
+ (setq nov-arts (cdr nov-arts)))
+ (t
+ ;; This entry in the overview has not been downloaded
+ (push (cons (car nov-arts) nil) alist)
+ (setq nov-arts (cdr nov-arts)))))
+
+ ;; When gnus-agent-consider-all-articles is set,
+ ;; gnus-agent-regenerate-group should NOT remove article IDs from
+ ;; the alist. Those IDs serve as markers to indicate that an
+ ;; attempt has been made to fetch that article's header.
+
+ ;; When gnus-agent-consider-all-articles is NOT set,
+ ;; gnus-agent-regenerate-group can remove the article ID of every
+ ;; article (with the exception of the last ID in the list - it's
+ ;; special) that no longer appears in the overview. In this
+ ;; situtation, the last article ID in the list implies that it,
+ ;; and every article ID preceeding it, have been fetched from the
+ ;; server.
+
+ (if gnus-agent-consider-all-articles
+ ;; Restore all article IDs that were not found in the overview file.
+ (let* ((n (cons nil alist))
+ (merged n)
+ (o (gnus-agent-load-alist group)))
+ (while o
+ (let ((nID (caadr n))
+ (oID (caar o)))
+ (cond ((not nID)
+ (setq n (setcdr n (list (list oID))))
+ (setq o (cdr o)))
+ ((< oID nID)
+ (setcdr n (cons (list oID) (cdr n)))
+ (setq o (cdr o)))
+ ((= oID nID)
+ (setq o (cdr o))
+ (setq n (cdr n)))
+ (t
+ (setq n (cdr n))))))
+ (setq alist (cdr merged)))
+ ;; Restore the last article ID if it is not already in the new alist
+ (let ((n (last alist))
+ (o (last (gnus-agent-load-alist group))))
+ (cond ((not o)
+ nil)
+ ((not n)
+ (push (cons (caar o) nil) alist))
+ ((< (caar n) (caar o))
+ (setcdr n (list (car o)))))))
+
+ (let ((inhibit-quit t))
+ (if (setq regenerated (buffer-modified-p))
+ (let ((coding-system-for-write gnus-agent-file-coding-system))
+ (write-region (point-min) (point-max) file nil 'silent)))
+
+ (setq regenerated (or regenerated
+ (and reread gnus-agent-article-alist)
+ (not (equal alist gnus-agent-article-alist))))
+
+ (setq gnus-agent-article-alist alist)
+
+ (when regenerated
+ (gnus-agent-save-alist group)
+
+ ;; I have to alter the group's active range NOW as
+ ;; gnus-make-ascending-articles-unread will use it to
+ ;; recalculate the number of unread articles in the group
+
+ (let ((group (gnus-group-real-name group))
+ (group-active (gnus-active group)))
+ (gnus-agent-possibly-alter-active group group-active)))))
+
+ (when (and reread gnus-agent-article-alist)
+ (gnus-make-ascending-articles-unread
+ group
+ (delq nil (mapcar (function (lambda (c)
+ (cond ((eq reread t)
+ (car c))
+ ((cdr c)
+ (car c)))))
+ gnus-agent-article-alist)))
+
+ (when (gnus-buffer-live-p gnus-group-buffer)
+ (gnus-group-update-group group t)
+ (sit-for 0)))
+
+ (gnus-message 5 nil)
+ regenerated)))
;;;###autoload
(defun gnus-agent-regenerate (&optional clean reread)
"Regenerate all agent covered files.
-If CLEAN, don't read existing active files."
+If CLEAN, obsolete (ignore)."
(interactive "P")
(let (regenerated)
(gnus-message 4 "Regenerating Gnus agent files...")
(dolist (gnus-command-method (gnus-agent-covered-methods))
- (let ((active-file (gnus-agent-lib-file "active"))
- active-hashtb active-changed
- point)
- (gnus-make-directory (file-name-directory active-file))
- (if clean
- (setq active-hashtb (gnus-make-hashtable 1000))
- (mm-with-unibyte-buffer
- (if (file-exists-p active-file)
- (let ((nnheader-file-coding-system
- gnus-agent-file-coding-system))
- (nnheader-insert-file-contents active-file))
- (setq active-changed t))
- (gnus-active-to-gnus-format
- nil (setq active-hashtb
- (gnus-make-hashtable
- (count-lines (point-min) (point-max)))))))
(dolist (group (gnus-groups-from-server gnus-command-method))
(setq regenerated (or (gnus-agent-regenerate-group group reread)
- regenerated))
- (let ((min (or (caar gnus-agent-article-alist) 1))
- (max (or (caar (last gnus-agent-article-alist)) 0))
- (active (gnus-gethash-safe (gnus-group-real-name group)
- active-hashtb))
- (read (gnus-info-read (gnus-get-info group))))
- (if (not active)
- (progn
- (setq active (cons min max)
- active-changed t)
- (gnus-sethash group active active-hashtb))
- (when (> (car active) min)
- (setcar active min)
- (setq active-changed t))
- (when (< (cdr active) max)
- (setcdr active max)
- (setq active-changed t)))))
- (when active-changed
- (setq regenerated t)
- (gnus-message 4 "Regenerate %s" active-file)
- (let ((nnmail-active-file-coding-system
- gnus-agent-file-coding-system))
- (gnus-write-active-file active-file active-hashtb)))))
+ regenerated))))
(gnus-message 4 "Regenerating Gnus agent files...done")
+
regenerated))
(defun gnus-agent-go-online (&optional force)