;;; gnus-agent.el --- unplugged support for Gnus
-;; Copyright (C) 1997, 1998, 1999, 2000, 2001, 2002
+;; Copyright (C) 1997, 1998, 1999, 2000, 2001, 2002, 2003
;; Free Software Foundation, Inc.
;; Author: Lars Magne Ingebrigtsen <larsi@gnus.org>
(require 'cl))
(eval-and-compile
- (autoload 'gnus-server-update-server "gnus-srvr")
- (autoload 'number-at-point "thingatpt"))
-
-(defface gnus-agent-downloaded-article-face
- '((((class color) (background light)) (:foreground "Orange" :bold t))
- (((class color) (background dark)) (:foreground "Yellow" :bold t))
- (t (:inverse-video t :bold t)))
- "Face used for displaying downloaded articles"
- :group 'gnus-agent)
+ (autoload 'gnus-server-update-server "gnus-srvr"))
(defcustom gnus-agent-directory (nnheader-concat gnus-directory "agent/")
"Where the Gnus agent will store its files."
:group 'gnus-agent
:type 'integer)
-(defcustom gnus-agent-expire-days 7
+(defcustom gnus-agent-expire-days nil
"Read articles older than this will be expired.
-This can also be a list of regexp/day pairs. The regexps will
-be matched against group names."
+This can also be a list of regexp/day pairs. The regexps will be
+matched against group names. If nil, articles in the agent cache are
+never expired."
:group 'gnus-agent
- :type 'integer)
+ :type '(choice (number :tag "days")
+ (const :tag "never" nil)))
(defcustom gnus-agent-expire-all nil
"If non-nil, also expire unread, ticked and dormant articles.
:type 'boolean
:group 'gnus-agent)
+(defcustom gnus-agent-max-fetch-size 10000000 ;; 10 Mb
+ "Chunk size for `gnus-agent-fetch-session'.
+The function will split its article fetches into chunks smaller than
+this limit."
+ :group 'gnus-agent
+ :type 'integer)
+
;;; Internal variables
(defvar gnus-agent-history-buffers nil)
(defvar gnus-agent-buffer-alist nil)
(defvar gnus-agent-article-alist nil
"An assoc list identifying the articles whose headers have been fetched.
- If successfully fetched, these headers will be stored in the group's overview file.
- The key of each assoc pair is the article ID.
- The value of each assoc pair is a flag indicating
- whether the identified article has been downloaded (gnus-agent-fetch-articles
- sets the value to the day of the download).
- NOTES:
- 1) The last element of this list can not be expired as some
- routines (for example, get-agent-fetch-headers) use the last
- value to track which articles have had their headers retrieved.
- 2) The gnus-agent-regenerate may destructively modify the value.
+If successfully fetched, these headers will be stored in the group's overview
+file. The key of each assoc pair is the article ID, the value of each assoc
+pair is a flag indicating whether the identified article has been downloaded
+\(gnus-agent-fetch-articles sets the value to the day of the download).
+NOTES:
+1) The last element of this list can not be expired as some
+ routines (for example, get-agent-fetch-headers) use the last
+ value to track which articles have had their headers retrieved.
+2) The gnus-agent-regenerate may destructively modify the value.
")
(defvar gnus-agent-group-alist nil)
(defvar gnus-category-alist nil)
(gnus-add-shutdown 'gnus-close-agent 'gnus)
(defun gnus-close-agent ()
- (setq gnus-agent-covered-methods nil
- gnus-category-predicate-cache nil
+ (setq gnus-category-predicate-cache nil
gnus-category-group-cache nil
gnus-agent-spam-hashtb nil)
(gnus-kill-buffer gnus-agent-overview-buffer))
(cadr gnus-command-method))))
(defsubst gnus-agent-directory ()
- "Path of the Gnus agent directory."
+ "The name of the Gnus agent directory."
(nnheader-concat gnus-agent-directory
(nnheader-translate-file-chars (gnus-agent-method)) "/"))
(defun gnus-agent-lib-file (file)
- "The full path of the Gnus agent library FILE."
+ "The full name of the Gnus agent library FILE."
(expand-file-name file
(file-name-as-directory
(expand-file-name "agent.lib" (gnus-agent-directory)))))
(put 'gnus-agent-with-fetch 'lisp-indent-function 0)
(put 'gnus-agent-with-fetch 'edebug-form-spec '(body))
+(defmacro gnus-agent-append-to-list (tail value)
+ `(setq ,tail (setcdr ,tail (cons ,value nil))))
+
;;;
;;; Mode infestation
;;;
(gnus-define-keys gnus-agent-summary-mode-map
"Jj" gnus-agent-toggle-plugged
"Ju" gnus-agent-summary-fetch-group
+ "JS" gnus-agent-fetch-group
"Js" gnus-agent-summary-fetch-series
"J#" gnus-agent-mark-article
"J\M-#" gnus-agent-unmark-article
(gnus-open-agent)
(add-hook 'gnus-setup-news-hook 'gnus-agent-queue-setup)
(unless gnus-agent-send-mail-function
- (setq gnus-agent-send-mail-function (or
- message-send-mail-real-function
- message-send-mail-function)
+ (setq gnus-agent-send-mail-function
+ (or message-send-mail-real-function
+ message-send-mail-function)
message-send-mail-real-function 'gnus-agent-send-mail))
+
(unless gnus-agent-covered-methods
(mapcar
(lambda (server)
(error "Groups can't be fetched when Gnus is unplugged"))
(gnus-group-iterate n 'gnus-agent-fetch-group))
-(defun gnus-agent-fetch-group (group)
+(defun gnus-agent-fetch-group (&optional group)
"Put all new articles in GROUP into the Agent."
(interactive (list (gnus-group-group-name)))
(let ((state gnus-plugged))
(unwind-protect
(progn
+ (setq group (or group gnus-newsgroup-name))
(unless group
(error "No group on the current line"))
(unless state
(gnus-message 1 "Ignoring disappeared server `%s'" m)
(sit-for 1))))
(gnus-agent-read-file
- (nnheader-concat gnus-agent-directory "lib/methods"))))
+ (nnheader-concat gnus-agent-directory "lib/servers"))))
(defun gnus-agent-write-servers ()
"Write the alist of covered servers."
(gnus-agent-mark-article n 'toggle))
(defun gnus-summary-set-agent-mark (article &optional unmark)
- "Mark ARTICLE as downloadable."
- (let ((unmark (if (and (not (null unmark)) (not (eq t unmark)))
- (memq article gnus-newsgroup-downloadable)
- unmark))
- (new-mark gnus-downloadable-mark))
+ "Mark ARTICLE as downloadable. If UNMARK is nil, article is marked.
+When UNMARK is t, the article is unmarked. For any other value, the
+article's mark is toggled."
+ (let ((unmark (cond ((eq nil unmark)
+ nil)
+ ((eq t unmark)
+ t)
+ (t
+ (memq article gnus-newsgroup-downloadable)))))
+ (gnus-summary-update-mark
(if unmark
- (let ((agent-articles gnus-agent-article-alist))
+ (progn
(setq gnus-newsgroup-downloadable
(delq article gnus-newsgroup-downloadable))
- (while (and agent-articles (< (caar agent-articles) article))
- (setq agent-articles (cdr agent-articles)))
- (if (and (eq (caar agent-articles) article)
- (cdar agent-articles))
- (setq new-mark 32)
- (progn (setq new-mark gnus-undownloaded-mark)
- (push article gnus-newsgroup-undownloaded))))
- (setq gnus-newsgroup-undownloaded
- (delq article gnus-newsgroup-undownloaded))
+ (gnus-article-mark article))
+ (progn
(setq gnus-newsgroup-downloadable
- (gnus-add-to-sorted-list gnus-newsgroup-downloadable article)))
- (gnus-summary-update-mark
- new-mark
+ (gnus-add-to-sorted-list gnus-newsgroup-downloadable article))
+ gnus-downloadable-mark)
+ )
'unread)))
-;; Check history - this may make sense if the agent is configured to pre-fetch every article.
(defun gnus-agent-get-undownloaded-list ()
- "Mark all unfetched articles as read."
+ "Construct list of articles that have not been downloaded."
(let ((gnus-command-method (gnus-find-method-for-group gnus-newsgroup-name)))
- (when (and
- (not (gnus-online gnus-command-method))
- (gnus-agent-method-p gnus-command-method))
- (gnus-agent-load-alist gnus-newsgroup-name)
- ;; First mark all undownloaded articles as undownloaded.
- ;; CCC kaig: Maybe change here to consider all headers.
- (let ((articles (delq nil (mapcar (lambda (header) (if (equal (mail-header-from header) "Gnus Agent")
- nil
- (mail-header-number header)))
- gnus-newsgroup-headers)))
- (agent-articles gnus-agent-article-alist)
- candidates article)
- (while (setq article (pop articles))
- (while (and agent-articles
- (< (caar agent-articles) article))
- (setq agent-articles (cdr agent-articles)))
- (when (or (not (cdar agent-articles))
- (not (= (caar agent-articles) article)))
- (push article candidates)))
- (dolist (article candidates)
- (unless (or (memq article gnus-newsgroup-downloadable)
- (memq article gnus-newsgroup-cached))
- (push article gnus-newsgroup-undownloaded))))
- ;; Then mark downloaded downloadable as not-downloadable,
- ;; if you get my drift.
- (dolist (article gnus-newsgroup-downloadable)
- (when (cdr (assq article gnus-agent-article-alist))
- (setq gnus-newsgroup-downloadable
- (delq article gnus-newsgroup-downloadable)))))))
+ (when (set (make-local-variable 'gnus-newsgroup-agentized) (gnus-agent-method-p gnus-command-method))
+ (let* ((alist (gnus-agent-load-alist gnus-newsgroup-name))
+ (headers gnus-newsgroup-headers)
+ (undownloaded (list nil))
+ (tail-undownloaded undownloaded)
+ (unfetched (list nil))
+ (tail-unfetched unfetched))
+ (while (and alist headers)
+ (let ((a (caar alist))
+ (h (mail-header-number (car headers))))
+ (cond ((< a h)
+ ;; Ignore IDs in the alist that are not being
+ ;; displayed in the summary.
+ (pop alist))
+ ((> a h)
+ ;; Headers that are not in the alist should be
+ ;; fictious (see nnagent-retrieve-headers); they
+ ;; imply that this article isn't in the agent.
+ (gnus-agent-append-to-list tail-undownloaded h)
+ (gnus-agent-append-to-list tail-unfetched h)
+ (pop headers))
+ ((cdar alist)
+ (pop alist)
+ (pop headers)
+ nil ; ignore already downloaded
+ )
+ (t
+ (pop alist)
+ (pop headers)
+ (gnus-agent-append-to-list tail-undownloaded a)))))
+
+ (while headers
+ (let ((num (mail-header-number (pop headers))))
+ (gnus-agent-append-to-list tail-undownloaded num)
+ (gnus-agent-append-to-list tail-unfetched num)))
+
+ (setq gnus-newsgroup-undownloaded (cdr undownloaded)
+ gnus-newsgroup-unfetched (cdr unfetched))))))
(defun gnus-agent-catchup ()
- "Mark all undownloaded articles as read."
+ "Mark as read all unhandled articles.
+An article is unhandled if it is neither cached, nor downloaded, nor
+downloadable."
(interactive)
(save-excursion
- (while gnus-newsgroup-undownloaded
- (gnus-summary-mark-article
- (pop gnus-newsgroup-undownloaded) gnus-catchup-mark)))
- (gnus-summary-position-point))
+ (let ((articles gnus-newsgroup-undownloaded))
+ (when (or gnus-newsgroup-downloadable
+ gnus-newsgroup-cached)
+ (setq articles (gnus-sorted-ndifference
+ (gnus-sorted-ndifference
+ (copy-sequence articles)
+ gnus-newsgroup-downloadable)
+ gnus-newsgroup-cached)))
+
+ (while articles
+ (gnus-summary-mark-article
+ (pop articles) gnus-catchup-mark)))
+ (gnus-summary-position-point)))
(defun gnus-agent-summary-fetch-series ()
(interactive)
- (let ((dl gnus-newsgroup-downloadable))
- (while gnus-newsgroup-processable
- (let* ((art (car (last gnus-newsgroup-processable)))
- (gnus-newsgroup-downloadable (list art)))
- (gnus-summary-goto-subject art)
- (sit-for 0)
- (gnus-agent-summary-fetch-group)
- (setq dl (delq art dl))
- (gnus-summary-remove-process-mark art)
- (sit-for 0)))
- (setq gnus-newsgroup-downloadable dl)))
+ (when gnus-newsgroup-processable
+ (setq gnus-newsgroup-downloadable
+ (let* ((dl gnus-newsgroup-downloadable)
+ (gnus-newsgroup-downloadable
+ (sort (copy-sequence gnus-newsgroup-processable) '<))
+ (fetched-articles (gnus-agent-summary-fetch-group)))
+ ;; The preceeding call to (gnus-agent-summary-fetch-group)
+ ;; updated gnus-newsgroup-downloadable to remove each
+ ;; article successfully fetched.
+
+ ;; For each article that I processed, remove its
+ ;; processable mark IF the article is no longer
+ ;; downloadable (i.e. it's already downloaded)
+ (dolist (article gnus-newsgroup-processable)
+ (unless (memq article gnus-newsgroup-downloadable)
+ (gnus-summary-remove-process-mark article)))
+ (gnus-sorted-ndifference dl fetched-articles)))))
(defun gnus-agent-summary-fetch-group (&optional all)
"Fetch the downloadable articles in the group.
(if all gnus-newsgroup-articles
gnus-newsgroup-downloadable))
(gnus-command-method (gnus-find-method-for-group gnus-newsgroup-name))
- (state gnus-plugged))
+ (state gnus-plugged)
+ fetched-articles)
(unwind-protect
(progn
(unless state
(unless articles
(error "No articles to download"))
(gnus-agent-with-fetch
- (gnus-agent-fetch-articles gnus-newsgroup-name articles))
+ (setq gnus-newsgroup-undownloaded
+ (gnus-sorted-ndifference
+ gnus-newsgroup-undownloaded
+ (setq fetched-articles
+ (gnus-agent-fetch-articles
+ gnus-newsgroup-name articles)))))
(save-excursion
- (dolist (article articles)
+
+ (dolist (article articles)
(setq gnus-newsgroup-downloadable
(delq article gnus-newsgroup-downloadable))
- (if gnus-agent-mark-unread-after-downloaded
- (gnus-summary-mark-article article gnus-unread-mark)))))
+ (if gnus-agent-mark-unread-after-downloaded
+ (gnus-summary-mark-article article gnus-unread-mark))
+ (when (gnus-summary-goto-subject article nil t)
+ (gnus-summary-update-download-mark article)))))
(when (and (not state)
gnus-plugged)
- (gnus-agent-toggle-plugged nil)))))
+ (gnus-agent-toggle-plugged nil)))
+ fetched-articles))
(defun gnus-agent-fetch-selected-article ()
"Fetch the current article as it is selected.
`gnus-mark-article-hook'."
(let ((gnus-command-method gnus-current-select-method))
(when (and gnus-plugged (gnus-agent-method-p gnus-command-method))
- (gnus-agent-fetch-articles
- gnus-newsgroup-name
- (list gnus-current-article)))))
+ (when (gnus-agent-fetch-articles
+ gnus-newsgroup-name
+ (list gnus-current-article))
+ (setq gnus-newsgroup-undownloaded
+ (delq gnus-current-article gnus-newsgroup-undownloaded))
+ (gnus-summary-update-article-line
+ gnus-current-article
+ (gnus-summary-article-header gnus-current-article))))))
;;;
;;; Internal functions
(delete-char 1))))))
(defun gnus-agent-group-path (group)
- "Translate GROUP into a path."
+ "Translate GROUP into a file name."
(if nnmail-use-long-file-names
(gnus-group-real-name group)
(nnheader-translate-file-chars
?. ?_)
?. ?/))))
-\f
-
(defun gnus-agent-get-function (method)
(if (gnus-online method)
(car method)
(defun gnus-agent-fetch-articles (group articles)
"Fetch ARTICLES from GROUP and put them into the Agent."
- (gnus-agent-load-alist group)
(when articles
- ;; Prune off articles that we have already fetched.
- (while (and articles
- (cdr (assq (car articles) gnus-agent-article-alist)))
- (pop articles))
- (let ((arts articles))
- (while (cdr arts)
- (if (cdr (assq (cadr arts) gnus-agent-article-alist))
- (setcdr arts (cddr arts))
- (setq arts (cdr arts)))))
- (when articles
- (let ((dir (concat
- (gnus-agent-directory)
- (gnus-agent-group-path group) "/"))
- (date (time-to-days (current-time)))
- (case-fold-search t)
- pos crosses id elem)
- (gnus-make-directory dir)
- (gnus-message 7 "Fetching articles for %s..." group)
- ;; Fetch the articles from the backend.
- (if (gnus-check-backend-function 'retrieve-articles group)
- (setq pos (gnus-retrieve-articles articles group))
- (with-temp-buffer
- (let (article)
- (while (setq article (pop articles))
- (gnus-message 10 "Fetching article %s for %s..."
- article group)
- (when (or
- (gnus-backlog-request-article group article
- nntp-server-buffer)
- (gnus-request-article article group))
- (goto-char (point-max))
- (push (cons article (point)) pos)
- (insert-buffer-substring nntp-server-buffer)))
- (copy-to-buffer nntp-server-buffer (point-min) (point-max))
- (setq pos (nreverse pos)))))
- ;; Then save these articles into the Agent.
- (save-excursion
- (set-buffer nntp-server-buffer)
- (while pos
- (narrow-to-region (cdar pos) (or (cdadr pos) (point-max)))
- (goto-char (point-min))
- (unless (eobp) ;; Don't save empty articles.
- (when (search-forward "\n\n" nil t)
- (when (search-backward "\nXrefs: " nil t)
- ;; Handle cross posting.
- (goto-char (match-end 0)) ; move to end of header name
- (skip-chars-forward "^ ") ; skip server name
- (skip-chars-forward " ")
- (setq crosses nil)
- (while (looking-at "\\([^: \n]+\\):\\([0-9]+\\) *")
- (push (cons (buffer-substring (match-beginning 1)
- (match-end 1))
- (string-to-int (buffer-substring (match-beginning 2)
- (match-end 2))))
- crosses)
- (goto-char (match-end 0)))
- (gnus-agent-crosspost crosses (caar pos) date)))
- (goto-char (point-min))
- (if (not (re-search-forward
- "^Message-ID: *<\\([^>\n]+\\)>" nil t))
- (setq id "No-Message-ID-in-article")
- (setq id (buffer-substring (match-beginning 1) (match-end 1))))
- (let ((coding-system-for-write
- gnus-agent-file-coding-system))
- (write-region (point-min) (point-max)
- (concat dir (number-to-string (caar pos)))
- nil 'silent))
- (when (setq elem (assq (caar pos) gnus-agent-article-alist))
- (setcdr elem date)))
- (widen)
- (pop pos)))
- (gnus-agent-save-alist group)))))
+ (gnus-agent-load-alist group)
+ (let* ((alist gnus-agent-article-alist)
+ (headers (if (< (length articles) 2) nil gnus-newsgroup-headers))
+ (selected-sets (list nil))
+ (current-set-size 0)
+ article
+ header-number)
+ ;; Check each article
+ (while (setq article (pop articles))
+ ;; Skip alist entries preceeding this article
+ (while (> article (or (caar alist) (1+ article)))
+ (setq alist (cdr alist)))
+
+ ;; Prune off articles that we have already fetched.
+ (unless (and (eq article (caar alist))
+ (cdar alist))
+ ;; Skip headers preceeding this article
+ (while (> article
+ (setq header-number
+ (let* ((header (car headers)))
+ (if header
+ (mail-header-number header)
+ (1+ article)))))
+ (setq headers (cdr headers)))
+
+ ;; Add this article to the current set
+ (setcar selected-sets (cons article (car selected-sets)))
+
+ ;; Update the set size, when the set is too large start a
+ ;; new one. I do this after adding the article as I want at
+ ;; least one article in each set.
+ (when (< gnus-agent-max-fetch-size
+ (setq current-set-size
+ (+ current-set-size
+ (if (= header-number article)
+ (mail-header-chars (car headers))
+ 0))))
+ (setcar selected-sets (nreverse (car selected-sets)))
+ (setq selected-sets (cons nil selected-sets)
+ current-set-size 0))))
+
+ (when (or (cdr selected-sets) (car selected-sets))
+ (let* ((fetched-articles (list nil))
+ (tail-fetched-articles fetched-articles)
+ (dir (concat
+ (gnus-agent-directory)
+ (gnus-agent-group-path group) "/"))
+ (date (time-to-days (current-time)))
+ (case-fold-search t)
+ pos crosses id)
+
+ (setcar selected-sets (nreverse (car selected-sets)))
+ (setq selected-sets (nreverse selected-sets))
+
+ (gnus-make-directory dir)
+ (gnus-message 7 "Fetching articles for %s..." group)
+
+ (unwind-protect
+ (while (setq articles (pop selected-sets))
+ ;; Fetch the articles from the backend.
+ (if (gnus-check-backend-function 'retrieve-articles group)
+ (setq pos (gnus-retrieve-articles articles group))
+ (with-temp-buffer
+ (let (article)
+ (while (setq article (pop articles))
+ (gnus-message 10 "Fetching article %s for %s..."
+ article group)
+ (when (or
+ (gnus-backlog-request-article group article
+ nntp-server-buffer)
+ (gnus-request-article article group))
+ (goto-char (point-max))
+ (push (cons article (point)) pos)
+ (insert-buffer-substring nntp-server-buffer)))
+ (copy-to-buffer
+ nntp-server-buffer (point-min) (point-max))
+ (setq pos (nreverse pos)))))
+ ;; Then save these articles into the Agent.
+ (save-excursion
+ (set-buffer nntp-server-buffer)
+ (while pos
+ (narrow-to-region (cdar pos) (or (cdadr pos) (point-max)))
+ (goto-char (point-min))
+ (unless (eobp) ;; Don't save empty articles.
+ (when (search-forward "\n\n" nil t)
+ (when (search-backward "\nXrefs: " nil t)
+ ;; Handle cross posting.
+ (goto-char (match-end 0)) ; move to end of header name
+ (skip-chars-forward "^ ") ; skip server name
+ (skip-chars-forward " ")
+ (setq crosses nil)
+ (while (looking-at "\\([^: \n]+\\):\\([0-9]+\\) *")
+ (push (cons (buffer-substring (match-beginning 1)
+ (match-end 1))
+ (string-to-int
+ (buffer-substring (match-beginning 2)
+ (match-end 2))))
+ crosses)
+ (goto-char (match-end 0)))
+ (gnus-agent-crosspost crosses (caar pos) date)))
+ (goto-char (point-min))
+ (if (not (re-search-forward
+ "^Message-ID: *<\\([^>\n]+\\)>" nil t))
+ (setq id "No-Message-ID-in-article")
+ (setq id (buffer-substring
+ (match-beginning 1) (match-end 1))))
+ (let ((coding-system-for-write
+ gnus-agent-file-coding-system))
+ (write-region (point-min) (point-max)
+ (concat dir (number-to-string (caar pos)))
+ nil 'silent))
+
+ (gnus-agent-append-to-list
+ tail-fetched-articles (caar pos)))
+ (widen)
+ (pop pos))))
+
+ (gnus-agent-save-alist group (cdr fetched-articles) date))
+ (cdr fetched-articles))))))
(defun gnus-agent-crosspost (crosses article &optional date)
(setq date (or date t))
(gnus-agent-check-overview-buffer))
(pop crosses))))
+(defun gnus-agent-backup-overview-buffer ()
+ (when gnus-newsgroup-name
+ (let ((root (gnus-agent-article-name ".overview" gnus-newsgroup-name))
+ (cnt 0)
+ name)
+ (while (file-exists-p
+ (setq name (concat root "~"
+ (int-to-string (setq cnt (1+ cnt))) "~"))))
+ (write-region (point-min) (point-max) name nil 'no-msg)
+ (gnus-message 1 "Created backup copy of overview in %s." name)))
+ t)
+
(defun gnus-agent-check-overview-buffer (&optional buffer)
"Check the overview file given for sanity.
In particular, checks that the file is sorted by article number
and that there are no duplicates."
- (let (prev-num)
+ (let ((prev-num -1)
+ (backed-up nil))
(save-excursion
- (when buffer (set-buffer buffer))
- (save-excursion
- (save-restriction
- (let ((deactivate-mark (if (boundp 'deactivate-mark)
- (symbol-value 'deactivate-mark)
- nil)))
- (widen)
- (goto-char (point-min))
- (setq prev-num (number-at-point))
- (while (and (zerop (forward-line 1))
- (not (eobp)))
- (let ((cur (number-at-point)))
- (cond ((= cur prev-num)
- (gnus-message 10
- "Duplicate overview line for %d" cur)
- (debug nil (format "Duplicate overview line for %d" cur))
- (delete-region (point) (progn (forward-line 1) (point))))
- ((< cur prev-num)
- (gnus-message 10 "Overview buffer not sorted!")
- (debug nil "Overview buffer not sorted!"))))
- (setq prev-num (number-at-point)))))))))
+ (when buffer
+ (set-buffer buffer))
+ (save-restriction
+ (widen)
+ (goto-char (point-min))
+ (while (< (point) (point-max))
+ (let ((p (point))
+ (cur (condition-case nil
+ (read (current-buffer))
+ (error nil))))
+ (cond
+ ((or (not (integerp cur))
+ (not (eq (char-after) ?\t)))
+ (or backed-up
+ (setq backed-up (gnus-agent-backup-overview-buffer)))
+ (gnus-message 1
+ "Overview buffer contains garbage '%s'."
+ (buffer-substring
+ p (gnus-point-at-eol))))
+ ((= cur prev-num)
+ (or backed-up
+ (setq backed-up (gnus-agent-backup-overview-buffer)))
+ (gnus-message 1
+ "Duplicate overview line for %d" cur)
+ (delete-region (point) (progn (forward-line 1) (point))))
+ ((< cur prev-num)
+ (or backed-up
+ (setq backed-up (gnus-agent-backup-overview-buffer)))
+ (gnus-message 1 "Overview buffer not sorted!")
+ (sort-numeric-fields 1 (point-min) (point-max))
+ (goto-char (point-min))
+ (setq prev-num -1))
+ (t
+ (setq prev-num cur)))
+ (forward-line 1)))))))
(defun gnus-agent-flush-cache ()
(save-excursion
nil 'silent))
(pop gnus-agent-buffer-alist))
(while gnus-agent-group-alist
- (with-temp-file (gnus-agent-article-name ".agentview" (caar gnus-agent-group-alist))
+ (with-temp-file (gnus-agent-article-name
+ ".agentview" (caar gnus-agent-group-alist))
(princ (cdar gnus-agent-group-alist))
(insert "\n")
(princ 1 (current-buffer))
(pop gnus-agent-group-alist))))
(defun gnus-agent-fetch-headers (group &optional force)
+ "Fetch interesting headers into the agent. The group's overview
+file will be updated to include the headers while a list of available
+article numbers will be returned."
(let* ((fetch-all (and gnus-agent-consider-all-articles
;; Do not fetch all headers if the predicate
;; implies that we only consider unread articles.
;; interesting marks. (We have to fetch articles with boring marks
;; because otherwise the agent will remove their marks.)
(dolist (arts (gnus-info-marks (gnus-get-info group)))
- (unless (memq (car arts) '(seen recent))
+ (unless (memq (car arts) '(seen recent killed cache))
(setq articles (gnus-range-add articles (cdr arts)))))
(setq articles (sort (gnus-uncompress-sequence articles) '<)))
- ;; At this point, I have the list of articles to consider for fetching.
- ;; This is the list that I'll return to my caller. Some of these articles may have already
- ;; been fetched. That's OK as the fetch article code will filter those out.
- ;; Internally, I'll filter this list to just those articles whose headers need to be fetched.
+ ;; At this point, I have the list of articles to consider for
+ ;; fetching. This is the list that I'll return to my caller. Some
+ ;; of these articles may have already been fetched. That's OK as
+ ;; the fetch article code will filter those out. Internally, I'll
+ ;; filter this list to just those articles whose headers need to
+ ;; be fetched.
(let ((articles articles))
;; Remove known articles.
(when (gnus-agent-load-alist group)
;; Remove articles marked as downloaded.
(if fetch-all
- ;; I want to fetch all headers in the active range.
- ;; Therefore, exclude only those headers that are in the article alist.
- ;; NOTE: This is probably NOT what I want to do after agent expiration in this group.
+ ;; I want to fetch all headers in the active range.
+ ;; Therefore, exclude only those headers that are in the
+ ;; article alist.
+ ;; NOTE: This is probably NOT what I want to do after
+ ;; agent expiration in this group.
(setq articles (gnus-agent-uncached-articles articles group))
- ;; I want to only fetch those headers that have never been fetched.
- ;; Therefore, exclude all headers that are, or WERE, in the article alist.
+ ;; I want to only fetch those headers that have never been
+ ;; fetched. Therefore, exclude all headers that are, or
+ ;; WERE, in the article alist.
(let ((low (1+ (caar (last gnus-agent-article-alist))))
(high (cdr (gnus-active group))))
- ;; Low can be greater than High when the same group is fetched twice
- ;; in the same session {The first fetch will fill the article alist
- ;; such that (last gnus-agent-article-alist) equals (cdr (gnus-active group))}.
- ;; The addition of one(the 1+ above) then forces Low to be greater than High.
- ;; When this happens, gnus-list-range-intersection returns nil which indicates
- ;; that no headers need to be fetched. -- Kevin
+ ;; Low can be greater than High when the same group is
+ ;; fetched twice in the same session {The first fetch will
+ ;; fill the article alist such that (last
+ ;; gnus-agent-article-alist) equals (cdr (gnus-active
+ ;; group))}. The addition of one(the 1+ above) then
+ ;; forces Low to be greater than High. When this happens,
+ ;; gnus-list-range-intersection returns nil which
+ ;; indicates that no headers need to be fetched. -- Kevin
(setq articles (gnus-list-range-intersection
articles (list (cons low high)))))))
- (when articles
- (gnus-message 7 "Fetching headers for %s..." group)
-
- ;; Fetch them.
- (gnus-make-directory (nnheader-translate-file-chars
- (file-name-directory file) t))
-
- (save-excursion
- (set-buffer nntp-server-buffer)
- (unless (eq 'nov (gnus-retrieve-headers articles group))
- (nnvirtual-convert-headers))
- (gnus-agent-check-overview-buffer)
- ;; Move these headers to the overview buffer so that gnus-agent-brand-nov can merge them
- ;; with the contents of FILE.
- (copy-to-buffer gnus-agent-overview-buffer (point-min) (point-max))
- (when (file-exists-p file)
- (gnus-agent-braid-nov group articles file))
- (let ((coding-system-for-write
- gnus-agent-file-coding-system))
- (gnus-agent-check-overview-buffer)
- (write-region (point-min) (point-max) file nil 'silent))
- (gnus-agent-save-alist group articles nil)
- articles)))
+
+ (gnus-message
+ 10 "gnus-agent-fetch-headers: undownloaded articles are '%s'"
+ (gnus-compress-sequence articles t))
+
+ (save-excursion
+ (set-buffer nntp-server-buffer)
+
+ (if articles
+ (progn
+ (gnus-message 7 "Fetching headers for %s..." group)
+
+ ;; Fetch them.
+ (gnus-make-directory (nnheader-translate-file-chars
+ (file-name-directory file) t))
+
+ (unless (eq 'nov (gnus-retrieve-headers articles group))
+ (nnvirtual-convert-headers))
+ (gnus-agent-check-overview-buffer)
+ ;; Move these headers to the overview buffer so that
+ ;; gnus-agent-braid-nov can merge them with the contents
+ ;; of FILE.
+ (copy-to-buffer
+ gnus-agent-overview-buffer (point-min) (point-max))
+ (when (file-exists-p file)
+ (gnus-agent-braid-nov group articles file))
+ (let ((coding-system-for-write
+ gnus-agent-file-coding-system))
+ (gnus-agent-check-overview-buffer)
+ (write-region (point-min) (point-max) file nil 'silent))
+ (gnus-agent-save-alist group articles nil)
+ articles)
+ (ignore-errors
+ (erase-buffer)
+ (nnheader-insert-file-contents file))))
+ )
articles))
(defsubst gnus-agent-copy-nov-line (article)
(insert-buffer-substring gnus-agent-overview-buffer b e))))
(defun gnus-agent-braid-nov (group articles file)
- "Merges the article headers identified by ARTICLES from gnus-agent-overview-buffer with the contents
-of FILE placing the combined headers in nntp-server-buffer."
+ "Merge agent overview data with given file.
+Takes headers for ARTICLES from `gnus-agent-overview-buffer' and the given
+FILE and places the combined headers into `nntp-server-buffer'."
(let (start last)
(set-buffer gnus-agent-overview-buffer)
(goto-char (point-min))
(erase-buffer)
(nnheader-insert-file-contents file)
(goto-char (point-max))
+ (forward-line -1)
+ (unless (looking-at "[0-9]+\t")
+ ;; Remove corrupted lines
+ (gnus-message
+ 1 "Overview %s is corrupted. Removing corrupted lines..." file)
+ (goto-char (point-min))
+ (while (not (eobp))
+ (if (looking-at "[0-9]+\t")
+ (forward-line 1)
+ (delete-region (point) (progn (forward-line 1) (point)))))
+ (forward-line -1))
(unless (or (= (point-min) (point-max))
- (progn
- (forward-line -1)
- (< (setq last (read (current-buffer))) (car articles))))
+ (< (setq last (read (current-buffer))) (car articles)))
;; We do it the hard way.
(when (nnheader-find-nov-line (car articles))
;; Replacing existing NOV entry
t)
((= art (car articles))
(beginning-of-line)
- (delete-region (point) (progn (forward-line 1) (point)))
+ (delete-region
+ (point) (progn (forward-line 1) (point)))
nil)
(t
(beginning-of-line)
(set-buffer nntp-server-buffer))
(insert-buffer-substring gnus-agent-overview-buffer start))))
-(eval-when-compile ; Keeps the compiler from warning about the free variable in gnus-agent-read-agentview
+;; Keeps the compiler from warning about the free variable in
+;; gnus-agent-read-agentview.
+(eval-when-compile
(defvar gnus-agent-read-agentview))
(defun gnus-agent-load-alist (group)
- (let ((gnus-agent-read-agentview group)) ; Binds free variable that's used in gnus-agent-read-agentview
- "Load the article-state alist for GROUP."
+ "Load the article-state alist for GROUP."
+ ;; Bind free variable that's used in `gnus-agent-read-agentview'.
+ (let ((gnus-agent-read-agentview group))
(setq gnus-agent-article-alist
(gnus-cache-file-contents
(gnus-agent-article-name ".agentview" group)
'gnus-agent-file-loading-cache
'gnus-agent-read-agentview))))
-;; Save format may be either 1 or 2. Two is the new, compressed format that is still being tested. Format 1 is uncompressed but known to be reliable.
+;; Save format may be either 1 or 2. Two is the new, compressed
+;; format that is still being tested. Format 1 is uncompressed but
+;; known to be reliable.
(defconst gnus-agent-article-alist-save-format 2)
(defun gnus-agent-read-agentview (file)
(end-of-file 0)))
changed-version)
- (cond ((= version 0)
- (let ((inhibit-quit t)
- entry)
- (gnus-agent-open-history)
- (set-buffer (gnus-agent-history-buffer))
- (goto-char (point-min))
- (while (not (eobp))
- (if (and (looking-at
- "[^\t\n]+\t\\([0-9]+\\)\t\\([^ \n]+\\) \\([0-9]+\\)")
- (string= (match-string 2)
- gnus-agent-read-agentview)
- (setq entry (assoc (string-to-number (match-string 3)) alist)))
- (setcdr entry (string-to-number (match-string 1))))
- (forward-line 1))
- (gnus-agent-close-history)
- (setq changed-version t)))
- ((= version 1)
- (setq changed-version (not (= 1 gnus-agent-article-alist-save-format))))
- ((= version 2)
- (let (uncomp)
- (mapcar (lambda (comp-list)
- (let ((state (car comp-list))
- (sequence (gnus-uncompress-sequence (cdr comp-list))))
- (mapcar (lambda (article-id)
- (setq uncomp (cons (cons article-id state) uncomp))) sequence))) alist)
- (setq alist (sort uncomp (lambda (first second) (< (car first) (car second)))))
- )
- ))
+ (cond
+ ((= version 0)
+ (let ((inhibit-quit t)
+ entry)
+ (gnus-agent-open-history)
+ (set-buffer (gnus-agent-history-buffer))
+ (goto-char (point-min))
+ (while (not (eobp))
+ (if (and (looking-at
+ "[^\t\n]+\t\\([0-9]+\\)\t\\([^ \n]+\\) \\([0-9]+\\)")
+ (string= (match-string 2)
+ gnus-agent-read-agentview)
+ (setq entry (assoc (string-to-number (match-string 3)) alist)))
+ (setcdr entry (string-to-number (match-string 1))))
+ (forward-line 1))
+ (gnus-agent-close-history)
+ (setq changed-version t)))
+ ((= version 1)
+ (setq changed-version (not (= 1 gnus-agent-article-alist-save-format))))
+ ((= version 2)
+ (let (uncomp)
+ (mapcar
+ (lambda (comp-list)
+ (let ((state (car comp-list))
+ (sequence (gnus-uncompress-sequence
+ (cdr comp-list))))
+ (mapcar (lambda (article-id)
+ (setq uncomp (cons (cons article-id state) uncomp)))
+ sequence)))
+ alist)
+ (setq alist (sort uncomp
+ (lambda (first second)
+ (< (car first) (car second))))))))
(when changed-version
(let ((gnus-agent-article-alist alist))
(gnus-agent-save-alist gnus-agent-read-agentview)))
(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))
(day-of-download (cdr pair))
(comp-list (assq day-of-download compressed)))
(if comp-list
- (setcdr comp-list (cons article-id (cdr comp-list)))
- (setq compressed (cons (list day-of-download article-id) compressed)))
+ (setcdr comp-list
+ (cons article-id (cdr comp-list)))
+ (setq compressed
+ (cons (list day-of-download article-id)
+ compressed)))
nil)) gnus-agent-article-alist)
- (mapcar (lambda (comp-list) (setcdr comp-list (gnus-compress-sequence (nreverse (cdr comp-list))))) compressed)
- (princ compressed (current-buffer))
- )
- )
- )
+ (mapcar (lambda (comp-list)
+ (setcdr comp-list
+ (gnus-compress-sequence
+ (nreverse (cdr comp-list)))))
+ compressed)
+ (princ compressed (current-buffer)))))
(insert "\n")
(princ gnus-agent-article-alist-save-format (current-buffer))
(insert "\n"))))
"Fetch GROUP."
(let ((gnus-command-method method)
(gnus-newsgroup-name group)
- gnus-newsgroup-dependencies gnus-newsgroup-headers
- gnus-newsgroup-scored gnus-headers gnus-score
- gnus-use-cache articles arts
- category predicate info marks score-param
+ (gnus-newsgroup-dependencies gnus-newsgroup-dependencies)
+ (gnus-newsgroup-headers gnus-newsgroup-headers)
+ (gnus-newsgroup-scored gnus-newsgroup-scored)
+ (gnus-use-cache gnus-use-cache)
(gnus-summary-expunge-below gnus-summary-expunge-below)
(gnus-summary-mark-below gnus-summary-mark-below)
(gnus-orphan-score gnus-orphan-score)
;; Maybe some other gnus-summary local variables should also
;; be put here.
+
+ gnus-headers
+ gnus-score
+ articles arts
+ category predicate info marks score-param
)
(unless (gnus-check-group group)
(error "Can't open server for %s" group))
+
;; Fetch headers.
- (when (and (or (gnus-active group)
- (gnus-activate-group group))
- (setq articles (gnus-agent-fetch-headers group))
- (let ((nntp-server-buffer gnus-agent-overview-buffer))
- ;; Parse them and see which articles we want to fetch.
- (setq gnus-newsgroup-dependencies
- (make-vector (length articles) 0))
- (setq gnus-newsgroup-headers
- (gnus-get-newsgroup-headers-xover articles nil nil
- group))
- ;; Some articles may not exist, so update `articles'
- ;; from what was actually found. -- kai
- (setq articles
- (mapcar (lambda (x) (aref x 0))
- gnus-newsgroup-headers))
- ;; `gnus-agent-overview-buffer' may be killed for
- ;; timeout reason. If so, recreate it.
- (gnus-agent-create-buffer)))
- (setq category (gnus-group-category group))
- (setq predicate
- (gnus-get-predicate
- (or (gnus-group-find-parameter group 'agent-predicate t)
- (cadr category))))
- (if (memq predicate '(gnus-agent-true gnus-agent-false))
- ;; Simple implementation
- (setq arts (and (eq predicate 'gnus-agent-true) articles))
- (setq arts nil)
- (setq score-param
- (or (gnus-group-get-parameter group 'agent-score t)
- (caddr category)))
- ;; Translate score-param into real one
- (cond
- ((not score-param))
- ((eq score-param 'file)
- (setq score-param (gnus-all-score-files group)))
- ((stringp (car score-param)))
- (t
- (setq score-param (list (list score-param)))))
- (when score-param
- (gnus-score-headers score-param))
-
- ;; Construct arts list with same order as gnus-newsgroup-headers
- (let* ((a (list nil))
- (b a))
- (while (setq gnus-headers (pop gnus-newsgroup-headers))
- (setq gnus-score
- (or (cdr (assq (mail-header-number gnus-headers)
- gnus-newsgroup-scored))
- gnus-summary-default-score))
- (when (funcall predicate)
- (setq a (setcdr a (list (mail-header-number gnus-headers))))))
- (setq arts (cdr b))))
-
- ;; Fetch the articles.
- (when arts
- (gnus-agent-fetch-articles group arts)))
- ;; Perhaps we have some additional articles to fetch.
- (dolist (mark gnus-agent-download-marks)
- (setq arts (assq mark (gnus-info-marks
- (setq info (gnus-get-info group)))))
- (when (cdr arts)
- (gnus-message 8 "Agent is downloading marked articles...")
- (gnus-agent-fetch-articles
- group (gnus-uncompress-range (cdr arts)))
- (when (eq mark 'download)
- (setq marks (delq arts (gnus-info-marks info)))
- (gnus-info-set-marks info marks)
- (gnus-dribble-enter
- (concat "(gnus-group-set-info '"
- (gnus-prin1-to-string info)
- ")")))))))
+ (when (or gnus-newsgroup-active
+ (gnus-active group)
+ (gnus-activate-group group))
+ (let ((marked-articles gnus-newsgroup-downloadable))
+ ;; Identify the articles marked for download
+ (unless gnus-newsgroup-active
+ ;; This needs to be a gnus-summary local variable that is
+ ;; NOT bound to any value above (its global value should
+ ;; default to nil).
+ (dolist (mark gnus-agent-download-marks)
+ (let ((arts (cdr (assq mark (gnus-info-marks
+ (setq info (gnus-get-info group)))))))
+ (when arts
+ (setq marked-articles (nconc (gnus-uncompress-range arts)
+ marked-articles))
+ ))))
+ (setq marked-articles (sort marked-articles '<))
+
+ ;; Fetch any new articles from the server
+ (setq articles (gnus-agent-fetch-headers group))
+
+ ;; Merge new articles with marked
+ (setq articles (sort (append marked-articles articles) '<))
+
+ (when articles
+ ;; Parse them and see which articles we want to fetch.
+ (setq gnus-newsgroup-dependencies
+ (or gnus-newsgroup-dependencies
+ (make-vector (length articles) 0)))
+ (setq gnus-newsgroup-headers
+ (or gnus-newsgroup-headers
+ (gnus-get-newsgroup-headers-xover articles nil nil
+ group)))
+ ;; `gnus-agent-overview-buffer' may be killed for
+ ;; timeout reason. If so, recreate it.
+ (gnus-agent-create-buffer)
+
+ ;; Figure out how to select articles in this group
+ (setq category (gnus-group-category group))
+
+ (setq predicate
+ (gnus-get-predicate
+ (or (gnus-group-find-parameter group 'agent-predicate t)
+ (cadr category))))
+
+ ;; If the selection predicate requires scoring, score each header
+ (unless (memq predicate '(gnus-agent-true gnus-agent-false))
+ (let ((score-param
+ (or (gnus-group-get-parameter group 'agent-score t)
+ (caddr category))))
+ ;; Translate score-param into real one
+ (cond
+ ((not score-param))
+ ((eq score-param 'file)
+ (setq score-param (gnus-all-score-files group)))
+ ((stringp (car score-param)))
+ (t
+ (setq score-param (list (list score-param)))))
+ (when score-param
+ (gnus-score-headers score-param))))
+
+ (unless (and (eq predicate 'gnus-agent-false)
+ (not marked-articles))
+ (let ((arts (list nil)))
+ (let ((arts-tail arts)
+ (alist (gnus-agent-load-alist group))
+ (marked-articles marked-articles)
+ (gnus-newsgroup-headers gnus-newsgroup-headers))
+ (while (setq gnus-headers (pop gnus-newsgroup-headers))
+ (let ((num (mail-header-number gnus-headers)))
+ ;; Determine if this article is already in the cache
+ (while (and alist
+ (> num (caar alist)))
+ (setq alist (cdr alist)))
+
+ (unless (and (eq num (caar alist))
+ (cdar alist))
+
+ ;; Determine if this article was marked for download.
+ (while (and marked-articles
+ (> num (car marked-articles)))
+ (setq marked-articles
+ (cdr marked-articles)))
+
+ ;; When this article is marked, or selected by the
+ ;; predicate, add it to the download list
+ (when (or (eq num (car marked-articles))
+ (let ((gnus-score
+ (or (cdr
+ (assq num gnus-newsgroup-scored))
+ gnus-summary-default-score)))
+ (funcall predicate)))
+ (gnus-agent-append-to-list arts-tail num))))))
+
+ (let (fetched-articles)
+ ;; Fetch all selected articles
+ (setq gnus-newsgroup-undownloaded
+ (gnus-sorted-ndifference
+ gnus-newsgroup-undownloaded
+ (setq fetched-articles
+ (if (cdr arts)
+ (gnus-agent-fetch-articles group (cdr arts))
+ nil))))
+
+ (let ((unfetched-articles
+ (gnus-sorted-ndifference (cdr arts) fetched-articles)))
+ (if gnus-newsgroup-active
+ ;; Update the summary buffer
+ (progn
+ (dolist (article marked-articles)
+ (when (gnus-summary-goto-subject article nil t)
+ (gnus-summary-set-agent-mark article t)))
+ (dolist (article fetched-articles)
+ (if gnus-agent-mark-unread-after-downloaded
+ (gnus-summary-mark-article
+ article gnus-unread-mark))
+ (when (gnus-summary-goto-subject article nil t)
+ (gnus-summary-update-download-mark article)))
+ (dolist (article unfetched-articles)
+ (gnus-summary-mark-article
+ article gnus-canceled-mark)))
+
+ ;; Update the group buffer.
+
+ ;; When some, or all, of the marked articles came
+ ;; from the download mark. Remove that mark. I
+ ;; didn't do this earlier as I only want to remove
+ ;; the marks after the fetch is completed.
+
+ (dolist (mark gnus-agent-download-marks)
+ (when (eq mark 'download)
+ (let ((marked-arts
+ (assq mark (gnus-info-marks
+ (setq info (gnus-get-info group))))))
+ (when (cdr marked-arts)
+ (setq marks
+ (delq marked-arts (gnus-info-marks info)))
+ (gnus-info-set-marks info marks)))))
+ (let ((read (gnus-info-read
+ (or info (setq info (gnus-get-info group))))))
+ (gnus-info-set-read
+ info (gnus-add-to-range read unfetched-articles)))
+
+ (gnus-group-update-group group t)
+ (sit-for 0)
+
+ (gnus-dribble-enter
+ (concat "(gnus-group-set-info '"
+ (gnus-prin1-to-string info)
+ ")"))))))))))))
;;;
;;; Agent Category Mode
(or (gnus-gethash group gnus-category-group-cache)
(assq 'default gnus-category-alist)))
+(defun gnus-agent-expire-2 (expiring-group active articles overview day force
+ dir)
+ (gnus-agent-load-alist expiring-group)
+ (gnus-message 5 "Expiring articles in %s" expiring-group)
+ (let* ((info (gnus-get-info expiring-group))
+ (alist gnus-agent-article-alist)
+ (specials (if alist
+ (list (caar (last alist)))))
+ (unreads ;; Articles that are excluded from the expiration process
+ (cond (gnus-agent-expire-all
+ ;; All articles are marked read by global decree
+ nil)
+ ((eq articles t)
+ ;; All articles are marked read by function parameter
+ nil)
+ ((not articles)
+ ;; Unread articles are marked protected from
+ ;; expiration Don't call gnus-list-of-unread-articles
+ ;; as it returns articles that have not been fetched
+ ;; into the agent.
+ (ignore-errors (gnus-agent-unread-articles expiring-group)))
+ (t
+ ;; All articles EXCEPT those named by the caller are
+ ;; protected from expiration
+ (gnus-sorted-difference
+ (gnus-uncompress-range
+ (cons (caar alist) (caar (last alist))))
+ (sort articles '<)))))
+ (marked ;; More articles that are exluded from the expiration process
+ (cond (gnus-agent-expire-all
+ ;; All articles are unmarked by global decree
+ nil)
+ ((eq articles t)
+ ;; All articles are unmarked by function parameter
+ nil)
+ (articles
+ ;; All articles may as well be unmarked as the
+ ;; unreads list already names the articles we are
+ ;; going to keep
+ nil)
+ (t
+ ;; Ticked and/or dormant articles are excluded from expiration
+ (nconc
+ (gnus-uncompress-range
+ (cdr (assq 'tick (gnus-info-marks info))))
+ (gnus-uncompress-range
+ (cdr (assq 'dormant
+ (gnus-info-marks info))))))))
+ (nov-file (concat dir ".overview"))
+ (cnt 0)
+ (completed -1)
+ dlist
+ type)
+
+ ;; The normal article alist contains
+ ;; elements that look like (article# .
+ ;; fetch_date) I need to combine other
+ ;; information with this list. For
+ ;; example, a flag indicating that a
+ ;; particular article MUST BE KEPT. To
+ ;; do this, I'm going to transform the
+ ;; elements to look like (article#
+ ;; fetch_date keep_flag
+ ;; NOV_entry_marker) Later, I'll reverse
+ ;; the process to generate the expired
+ ;; article alist.
+
+ ;; Convert the alist elements to
+ ;; (article# fetch_date nil nil).
+ (setq dlist (mapcar (lambda (e) (list (car e) (cdr e) nil nil)) alist))
+
+ ;; Convert the keep lists to elements
+ ;; that look like (article# nil
+ ;; keep_flag nil) then append it to the
+ ;; expanded dlist These statements are
+ ;; sorted by ascending precidence of the
+ ;; keep_flag.
+ (setq dlist (nconc dlist (mapcar (lambda (e)
+ (list e nil 'unread nil)) unreads)))
+ (setq dlist (nconc dlist (mapcar (lambda (e)
+ (list e nil 'marked nil)) marked)))
+ (setq dlist (nconc dlist (mapcar (lambda (e)
+ (list e nil 'special nil)) specials)))
+
+ (set-buffer overview)
+ (erase-buffer)
+ (when (file-exists-p nov-file)
+ (gnus-message 7 "gnus-agent-expire: Loading overview...")
+ (nnheader-insert-file-contents nov-file)
+ (goto-char (point-min))
+
+ (let (p)
+ (while (< (setq p (point)) (point-max))
+ (condition-case nil
+ ;; If I successfully read an
+ ;; integer (the plus zero
+ ;; ensures a numeric type),
+ ;; prepend a marker entry to
+ ;; the list
+ (push (list (+ 0 (read (current-buffer))) nil nil
+ (set-marker (make-marker) p)) dlist)
+ (error
+ (gnus-message 1 (concat "gnus-agent-expire: read error occurred "
+ "when reading expression at %s in %s. "
+ "Skipping to next line.")
+ (point) nov-file)))
+ ;; Whether I succeeded, or failed,
+ ;; it doesn't matter. Move to the
+ ;; next line then try again.
+ (forward-line 1)))
+ (gnus-message 7 "gnus-agent-expire: Loading overview... Done"))
+ (set-buffer-modified-p nil)
+
+ ;; At this point, all of the information
+ ;; is in dlist. The only problem is
+ ;; that much of it is spread across
+ ;; multiple entries. Sort then MERGE!!
+ (gnus-message 7 "gnus-agent-expire: Sorting entries... ")
+ ;; If two entries have the same
+ ;; article-number then sort by ascending
+ ;; keep_flag.
+ (let ((special 0)
+ (marked 1)
+ (unread 2))
+ (setq dlist
+ (sort dlist
+ (lambda (a b)
+ (cond ((< (nth 0 a) (nth 0 b))
+ t)
+ ((> (nth 0 a) (nth 0 b))
+ nil)
+ (t
+ (let ((a (or (symbol-value (nth 2 a)) 3))
+ (b (or (symbol-value (nth 2 b)) 3)))
+ (<= a b))))))))
+ (gnus-message 7 "gnus-agent-expire: Sorting entries... Done")
+ (gnus-message 7 "gnus-agent-expire: Merging entries... ")
+ (let ((dlist dlist))
+ (while (cdr dlist) ; I'm not at the end-of-list
+ (if (eq (caar dlist) (caadr dlist))
+ (let ((first (cdr (car dlist)))
+ (secnd (cdr (cadr dlist))))
+ (setcar first (or (car first) (car secnd))) ; fetch_date
+ (setq first (cdr first)
+ secnd (cdr secnd))
+ (setcar first (or (car first) (car secnd))) ; Keep_flag
+ (setq first (cdr first)
+ secnd (cdr secnd))
+ (setcar first (or (car first) (car secnd))) ; NOV_entry_marker
+
+ (setcdr dlist (cddr dlist)))
+ (setq dlist (cdr dlist)))))
+ (gnus-message 7 "gnus-agent-expire: Merging entries... Done")
+
+ (let* ((len (float (length dlist)))
+ (alist (list nil))
+ (tail-alist alist))
+ (while dlist
+ (let ((new-completed (truncate (* 100.0 (/ (setq cnt (1+ cnt)) len)))))
+ (when (> new-completed completed)
+ (setq completed new-completed)
+ (gnus-message 9 "%3d%% completed..." completed)))
+ (let* ((entry (car dlist))
+ (article-number (nth 0 entry))
+ (fetch-date (nth 1 entry))
+ (keep (nth 2 entry))
+ (marker (nth 3 entry)))
+
+ (cond
+ ;; Kept articles are unread, marked, or special.
+ (keep
+ (when fetch-date
+ (unless (file-exists-p (concat dir (number-to-string
+ article-number)))
+ (setf (nth 1 entry) nil)
+ (gnus-message 3 (concat "gnus-agent-expire cleared download "
+ "flag on article %d as the cached "
+ "article file is missing.")
+ (caar dlist)))
+ (unless marker
+ (gnus-message 1 (concat "gnus-agent-expire detected a "
+ "missing NOV entry. Run "
+ "gnus-agent-regenerate-group to "
+ "restore it."))))
+ (gnus-agent-append-to-list tail-alist (cons article-number fetch-date)))
+
+ ;; The following articles are READ, UNMARKED, and ORDINARY.
+ ;; See if they can be EXPIRED!!!
+ ((setq type
+ (cond
+ ((not (integerp fetch-date))
+ 'read) ;; never fetched article (may expire right now)
+ ((not (file-exists-p (concat dir (number-to-string
+ article-number))))
+ (setf (nth 1 entry) nil)
+ 'externally-expired) ;; Can't find the cached
+ ;; article. Handle case as
+ ;; though this article was
+ ;; never fetched.
+
+ ;; We now have the arrival day, so we see
+ ;; whether it's old enough to be expired.
+ ((< fetch-date day)
+ 'expired)
+ (force
+ 'forced)))
+
+ ;; I found some reason to expire this entry.
+
+ (let ((actions nil))
+ (when (memq type '(forced expired))
+ (ignore-errors ; Just being paranoid.
+ (delete-file (concat dir (number-to-string article-number)))
+ (push "expired cached article" actions))
+ (setf (nth 1 entry) nil))
+
+ (when marker
+ (push "NOV entry removed" actions)
+ (goto-char marker)
+ (gnus-delete-line))
+
+ ;; If considering all articles is set, I can only expire
+ ;; article IDs that are no longer in the active range.
+ (if (and gnus-agent-consider-all-articles
+ (>= article-number (car active)))
+ ;; I have to keep this ID in the alist
+ (gnus-agent-append-to-list tail-alist
+ (cons article-number fetch-date))
+ (push (format "Removed %s article number from article alist"
+ type) actions))
+
+ (gnus-message 7 "gnus-agent-expire: Article %d: %s"
+ article-number (mapconcat 'identity
+ actions ", "))))
+ (t
+ (gnus-agent-append-to-list tail-alist (cons article-number fetch-date)))
+ )
+
+ ;; Clean up markers as I want to recycle this buffer over
+ ;; several groups.
+ (when marker
+ (set-marker marker nil))
+
+ (setq dlist (cdr dlist))))
+
+ (setq alist (cdr alist))
+
+ (let ((inhibit-quit t))
+ (unless (equal alist gnus-agent-article-alist)
+ (setq gnus-agent-article-alist alist)
+ (gnus-agent-save-alist expiring-group))
+
+ (when (buffer-modified-p)
+ (let ((coding-system-for-write
+ gnus-agent-file-coding-system))
+ (gnus-make-directory dir)
+ (write-region (point-min) (point-max) nov-file nil 'silent)
+ ;; clear the modified flag as that I'm not confused by its
+ ;; status on the next pass through this routine.
+ (set-buffer-modified-p nil)))
+
+ (when (eq articles t)
+ (gnus-summary-update-info))))))
+
+(defun gnus-agent-expire-1 (&optional articles group force)
+ "Expire all old agent cached articles unconditionally.
+See `gnus-agent-expire'."
+ (let ((methods (if group
+ (list (gnus-find-method-for-group group))
+ gnus-agent-covered-methods))
+ (day (if (numberp gnus-agent-expire-days)
+ (- (time-to-days (current-time)) gnus-agent-expire-days)
+ nil))
+ gnus-command-method sym arts pos
+ history overview file histories elem art nov-file low info
+ unreads marked article orig lowest highest found days)
+ (save-excursion
+ (setq overview (gnus-get-buffer-create " *expire overview*"))
+ (unwind-protect
+ (while (setq gnus-command-method (pop methods))
+ (when (file-exists-p (gnus-agent-lib-file "active"))
+ (with-temp-buffer
+ (nnheader-insert-file-contents
+ (gnus-agent-lib-file "active"))
+ (gnus-active-to-gnus-format
+ gnus-command-method
+ (setq orig (gnus-make-hashtable
+ (count-lines (point-min) (point-max))))))
+ (dolist (expiring-group (gnus-groups-from-server
+ gnus-command-method))
+ (if (or (not group)
+ (equal group expiring-group))
+ (let* ((dir (concat
+ (gnus-agent-directory)
+ (gnus-agent-group-path expiring-group)
+ "/"))
+ (active
+ (gnus-gethash-safe expiring-group orig))
+ (day (if (numberp day)
+ day
+ (let (found
+ (days gnus-agent-expire-days))
+ (catch 'found
+ (while (and (not found) days)
+ (when (eq 0 (string-match
+ (caar days)
+ expiring-group))
+ (throw 'found (- (time-to-days
+ (current-time))
+ (cadar days))))
+ (pop days))
+ ;; No regexp matched so set
+ ;; a limit that will block
+ ;; expiration in this group.
+ 0)))))
+
+ (when active
+ (gnus-agent-expire-2 expiring-group active
+ articles overview day force
+ dir)))))))
+ (kill-buffer overview)))))
+
(defun gnus-agent-expire (&optional articles group force)
- "Expire all old articles.
+ "Expire all old agent cached articles.
If you want to force expiring of certain articles, this function can
take ARTICLES, GROUP and FORCE parameters as well.
Setting GROUP will limit expiration to that group.
FORCE is equivalent to setting gnus-agent-expire-days to zero(0)."
(interactive)
-
- (if force (setq force 'forced))
-
- (if (or (not (eq articles t))
- (yes-or-no-p (concat "Are you sure that you want to expire all articles in " (if group group "every agentized group") ".")))
- (let ((methods (if group
- (list (gnus-find-method-for-group group))
- gnus-agent-covered-methods))
- (day (if (numberp gnus-agent-expire-days)
- (- (time-to-days (current-time)) gnus-agent-expire-days)
- nil))
- gnus-command-method sym arts pos
- history overview file histories elem art nov-file low info
- unreads marked article orig lowest highest found days)
- (save-excursion
- (setq overview (gnus-get-buffer-create " *expire overview*"))
- (while (setq gnus-command-method (pop methods))
- (when (file-exists-p (gnus-agent-lib-file "active"))
- (with-temp-buffer
- (nnheader-insert-file-contents (gnus-agent-lib-file "active"))
- (gnus-active-to-gnus-format
- gnus-command-method
- (setq orig (gnus-make-hashtable
- (count-lines (point-min) (point-max))))))
- (dolist (expiring-group (gnus-groups-from-server gnus-command-method))
- (if (or (not group)
- (equal group expiring-group))
- (let* ((dir (concat
- (gnus-agent-directory)
- (gnus-agent-group-path expiring-group) "/")))
- (cond ((gnus-gethash-safe expiring-group; KJG (gnus-group-real-name expiring-group)
- orig)
- (gnus-agent-load-alist expiring-group)
- (gnus-message 5 "Expiring articles in %s" expiring-group)
- (let* ((info (gnus-get-info expiring-group))
- (alist gnus-agent-article-alist)
- changed-alist
- (specials (if alist
- (list (caar (last alist)))))
- (unreads;; Articles that are excluded from the expiration process
- (cond (gnus-agent-expire-all
- ;; All articles are marked read by global decree
- nil)
- ((eq articles t)
- ;; All articles are marked read by function parameter
- nil)
- ((not articles)
- ;; Unread articles are marked protected from expiration
- (ignore-errors (gnus-list-of-unread-articles expiring-group)))
- (t
- ;; All articles EXCEPT those named by the caller are protected from expiration
- (gnus-sorted-difference (gnus-uncompress-range (cons (caar alist) (caar (last alist)))) (sort articles '<)))))
- (marked;; More articles that are exluded from the expiration process
- (cond (gnus-agent-expire-all
- ;; All articles are unmarked by global decree
- nil)
- ((eq articles t)
- ;; All articles are unmarked by function parameter
- nil)
- (articles
- ;; All articles may as well be unmarked as the unreads list already names the articles we are going to keep
- nil)
- (t
- ;; Ticked and/or dormant articles are excluded from expiration
- (nconc
- (gnus-uncompress-range
- (cdr (assq 'tick (gnus-info-marks info))))
- (gnus-uncompress-range
- (cdr (assq 'dormant
- (gnus-info-marks info))))))
- ))
- (keep (sort (nconc specials unreads marked) '<))
- (nov-file (concat dir ".overview"))
- (len (length alist))
- (cnt 0)
- type)
- (when (file-exists-p nov-file)
- (set-buffer overview)
- (erase-buffer)
- (nnheader-insert-file-contents nov-file)
- (goto-char (point-min))
- (set-buffer-modified-p nil))
- (while alist
- (let ((art (caar alist)))
- (gnus-message 9 "Processing %d of %d" (setq cnt (1+ cnt)) len)
- (while (< (or (car keep) (1+ art)) art)
- (ignore-errors
- (while (let ((nov-art (read (current-buffer))))
- (cond ((< nov-art (car keep))
- (gnus-delete-line)
- t)
- ((= nov-art (car keep))
- (forward-line 1)
- nil)
- (t
- (beginning-of-line)
- nil)))))
- (setq keep (cdr keep)))
-
- (cond ((eq art (car keep))
- (if (and (cdar alist)
- (not (file-exists-p (concat dir (number-to-string art)))))
- (progn (setcdr (car alist) nil)
- (gnus-message 7 "Article %d: cleared download flag as local file missing" (caar alist))
- (setq changed-alist t)))
- (setq alist (cdr alist)
- keep (cdr keep))
- (condition-case nil
- (while (let ((nov-art (read (current-buffer))))
- (cond ((< nov-art art)
- (gnus-message 7 "Article %d: NOV line removed" nov-art)
- (gnus-delete-line)
- t)
- ((= nov-art art)
- (forward-line 1)
- nil)
- (t
- (beginning-of-line)
- nil))))
- (error (forward-line 1))))
- ((setq type (let ((fetch-date (cdar alist)))
- (or
- ;; if read but not downloaded
- (if (and (numberp fetch-date)
- (file-exists-p (concat dir (number-to-string art))))
- nil
- 'read)
- ;; We now have the arrival day, so we see
- ;; whether it's old enough to be expired.
- (if (< fetch-date
- (if (numberp day)
- day
- (let (found
- (days gnus-agent-expire-days))
- (while (and (not found)
- days)
- (when (eq 0 (string-match (caar days) expiring-group))
- (setq found (cadar days)))
- (pop days))
- found)))
- 'expired)
- force)))
-
- (if gnus-agent-consider-all-articles
- (setq alist (cdr alist)) ;; Iterate forward
- (gnus-message 7 "Article %d: Removed %s article from alist" art type)
- (setcar alist (cadr alist))
- (setcdr alist (cddr alist))
- (setq changed-alist t))
-
- (if (memq type '(forced expired))
- (ignore-errors
- (delete-file (concat dir (number-to-string art)))
- (gnus-message 7 "Article %d: Expired local copy" art)))
- (ignore-errors
- (let (nov-art)
- (while (<= (setq nov-art (read (current-buffer))) art)
- (gnus-message 7 "Article %d: NOV line removed" nov-art)
- (gnus-delete-line)))
- (beginning-of-line))
- )
- (t
- (setq alist (cdr alist)))
- )
- )
- )
-
- (let ((inhibit-quit t))
- (if changed-alist
- (gnus-agent-save-alist expiring-group))
- (if (buffer-modified-p)
- (let ((coding-system-for-write
- gnus-agent-file-coding-system))
- (gnus-make-directory dir)
- (write-region (point-min) (point-max) nov-file nil 'silent)
- ;; clear the modified flag as that I'm not confused by its status on the next pass through this routine.
- (set-buffer-modified-p nil))
- )
- (if (eq articles t)
- (gnus-summary-update-info))
- ))))))))))))
- (gnus-message 4 "Expiry...done"))
+ (if (and (not gnus-agent-expire-days)
+ (or (not (eq articles t))
+ (yes-or-no-p (concat "Are you sure that you want to expire all "
+ "articles in " (if group group
+ "every agentized group")
+ "."))))
+ (gnus-agent-expire-1 articles group force)
+ (gnus-message 4 "Expiry...done")))
;;;###autoload
(defun gnus-agent-batch ()
(gnus-group-send-queue)
(gnus-agent-fetch-session)))
+(defun gnus-agent-unread-articles (group)
+ (let* ((read (gnus-info-read (gnus-get-info group)))
+ (known (gnus-agent-load-alist group))
+ (unread (list nil))
+ (tail-unread unread))
+ (while (and known read)
+ (let ((candidate (car (pop known))))
+ (while (let* ((range (car read))
+ (min (if (numberp range) range (car range)))
+ (max (if (numberp range) range (cdr range))))
+ (cond ((or (not min)
+ (< candidate min))
+ (gnus-agent-append-to-list tail-unread candidate)
+ nil)
+ ((> candidate max)
+ (pop read)))))))
+ (while known
+ (gnus-agent-append-to-list tail-unread (car (pop known))))
+ (cdr unread)))
+
(defun gnus-agent-uncached-articles (articles group &optional cached-header)
- "Constructs sublist of ARTICLES that excludes those articles ids in GROUP that have already been fetched.
- If CACHED-HEADER is nil, articles are only excluded if the article itself has been fetched."
+ "Restrict ARTICLES to numbers already fetched.
+Returns a sublist of ARTICLES that excludes thos article ids in GROUP
+that have already been fetched.
+If CACHED-HEADER is nil, articles are only excluded if the article itself
+has been fetched."
+
+ ;; Logically equivalent to: (gnus-sorted-difference articles (mapcar
+ ;; 'car gnus-agent-article-alist))
-;; Logically equivalent to: (gnus-sorted-difference articles (mapcar 'car gnus-agent-article-alist))
-;; Functionally, I don't need to construct a temp list using mapcar.
+ ;; Functionally, I don't need to construct a temp list using mapcar.
(if (gnus-agent-load-alist group)
(let* ((ref gnus-agent-article-alist)
(arts articles)
(uncached (list nil))
- (tail uncached))
+ (tail-uncached uncached))
(while (and ref arts)
(let ((v1 (car arts))
(v2 (caar ref)))
(cond ((< v1 v2) ; the article (v1) does not appear in the reference list
- (setq tail (setcdr tail (list v1)))
+ (gnus-agent-append-to-list tail-uncached v1)
(pop arts))
((= v1 v2)
(unless (or cached-header (cdar ref)) ; the article (v1) is already cached
- (setq tail (setcdr tail (list v1))))
+ (gnus-agent-append-to-list tail-uncached v1))
(pop arts)
(pop ref))
(t ; the reference article (v2) preceeds the list being filtered
(pop ref)))))
(while arts
- (setq tail (setcdr tail (list (pop arts)))))
+ (gnus-agent-append-to-list tail-uncached (pop arts)))
(cdr uncached))
;; if gnus-agent-load-alist fails, no articles are cached.
articles))
uncached-articles group fetch-old)))
(nnvirtual-convert-headers))
((eq 'nntp (car gnus-current-select-method))
- ;; The author of gnus-get-newsgroup-headers-xover reports that the XOVER command
- ;; is commonly unreliable. The problem is that recently posted articles may not
- ;; be entered into the NOV database in time to respond to my XOVER query.
+ ;; The author of gnus-get-newsgroup-headers-xover
+ ;; reports that the XOVER command is commonly
+ ;; unreliable. The problem is that recently
+ ;; posted articles may not be entered into the
+ ;; NOV database in time to respond to my XOVER
+ ;; query.
;;
- ;; I'm going to use his assumption that the NOV database is updated in order
- ;; of ascending article ID. Therefore, a response containing article ID N
- ;; implies that all articles from 1 to N-1 are up-to-date. Therefore,
- ;; missing articles in that range have expired.
+ ;; I'm going to use his assumption that the NOV
+ ;; database is updated in order of ascending
+ ;; article ID. Therefore, a response containing
+ ;; article ID N implies that all articles from 1
+ ;; to N-1 are up-to-date. Therefore, missing
+ ;; articles in that range have expired.
(set-buffer nntp-server-buffer)
(let* ((fetched-articles (list nil))
- (tail fetched-articles)
+ (tail-fetched-articles fetched-articles)
(min (cond ((numberp fetch-old)
(max 1 (- (car articles) fetch-old)))
(fetch-old
;; Get the list of articles that were fetched
(goto-char (point-min))
- (ignore-errors
- (while t
- (setq tail (setcdr tail (cons (read (current-buffer)) nil)))
+ (let ((pm (point-max)))
+ (while (< (point) pm)
+ (when (looking-at "[0-9]+\t")
+ (gnus-agent-append-to-list tail-fetched-articles (read (current-buffer))))
(forward-line 1)))
- ;; Clip this list to the headers that will actually be returned
+ ;; Clip this list to the headers that will
+ ;; actually be returned
(setq fetched-articles (gnus-list-range-intersection
(cdr fetched-articles)
(cons min max)))
- ;; Clip the uncached articles list to exclude IDs after the last FETCHED header.
- ;; The excluded IDs may be fetchable using HEAD.
- (if (car tail)
+ ;; Clip the uncached articles list to exclude
+ ;; IDs after the last FETCHED header. The
+ ;; excluded IDs may be fetchable using HEAD.
+ (if (car tail-fetched-articles)
(setq uncached-articles (gnus-list-range-intersection
uncached-articles
- (cons (car uncached-articles) (car tail)))))
+ (cons (car uncached-articles) (car tail-fetched-articles)))))
- ;; Create the list of articles that were "successfully" fetched. Success, in
- ;; this case, means that the ID should not be fetched again. In the case of
- ;; an expired article, the header will not be fetched.
+ ;; Create the list of articles that were
+ ;; "successfully" fetched. Success, in this
+ ;; case, means that the ID should not be
+ ;; fetched again. In the case of an expired
+ ;; article, the header will not be fetched.
(setq uncached-articles (gnus-sorted-nunion fetched-articles uncached-articles))
))))
(set-buffer nntp-server-buffer)
(copy-to-buffer gnus-agent-overview-buffer (point-min) (point-max))
- ;; Merge the temp buffer with the known headers (found on disk in FILE) into the nntp-server-buffer
+ ;; Merge the temp buffer with the known headers (found on
+ ;; disk in FILE) into the nntp-server-buffer
(when (and uncached-articles (file-exists-p file))
(gnus-agent-braid-nov group uncached-articles file))
(gnus-agent-check-overview-buffer)
(write-region (point-min) (point-max) file nil 'silent))
- ;; Update the group's article alist to include the newly fetched articles.
+ ;; Update the group's article alist to include the newly
+ ;; fetched articles.
(gnus-agent-load-alist group)
(gnus-agent-save-alist group uncached-articles nil)
)
t)))
(defun gnus-agent-regenerate-group (group &optional reread)
- "Regenerate GROUP. If REREAD is t, all articles in the .overview are marked as unread. If REREAD is not nil, downloaded articles are marked as unread."
+ "Regenerate GROUP.
+If REREAD is t, all articles in the .overview are marked as unread.
+If REREAD is not nil, downloaded articles are marked as unread."
+ (interactive (list (let ((def (or (gnus-group-group-name)
+ gnus-newsgroup-name)))
+ (let ((select (read-string (if def (concat "Group Name (" def "): ")
+ "Group Name: "))))
+ (if (and (equal "" select)
+ def)
+ def
+ select)))
+ (intern-soft (read-string "Reread (nil)? (t=>all, nil=>none, some=>all downloaded): "))))
(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)))
+ 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)
(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.
+ ;; 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 ((looking-at "[0-9]+\\b")
+ (cond ((looking-at "[0-9]+\t")
(push (read (current-buffer)) nov-arts)
(forward-line 1)
(let ((l1 (car nov-arts))
(cond ((not l2)
nil)
((< l1 l2)
- ;; Don't sort now as I haven't verified that every line begins with a number
+ (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 entries contained duplicate of article %s. Duplicate deleted." l1)
(gnus-delete-line)
(pop 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 (sort-numeric-fields 1 (point-min) (point-max))
+ (progn
+ (gnus-message 5 "gnus-agent-regenerate-group: Sorting NOV entries into ascending order.")
+ (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.
+ ;; 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 6 "Regenerating NOV %s %d..." group (car downloaded))
+ (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)
(push (cons (car nov-arts) nil) alist)
(pop 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.
+ ;; 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))
((< (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)
(setq gnus-agent-article-alist alist)
(when regenerated
- (gnus-agent-save-alist group))
+ (gnus-agent-save-alist group)))
+ )
(when (and reread gnus-agent-article-alist)
(gnus-make-ascending-articles-unread
(if (eq status 'offline) 'offline 'online)
(if (eq status 'offline) 'online 'offline))))
+(defun gnus-agent-group-covered-p (group)
+ (member (gnus-group-method group)
+ gnus-agent-covered-methods))
+
(provide 'gnus-agent)
;;; gnus-agent.el ends here