;;; gnus-agent.el --- unplugged support for Semi-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>
(defcustom gnus-agent-expire-days 7
"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."
:group 'gnus-agent
- :type 'integer)
+ :type '(choice (number :tag "days")
+ (sexp :tag "List" nil)))
(defcustom gnus-agent-expire-all nil
"If non-nil, also expire unread, ticked and dormant articles.
:group 'gnus-agent)
(defcustom gnus-agent-max-fetch-size 10000000 ;; 10 Mb
- "gnus-agent-fetch-session is required to split its article fetches into chunks smaller than this limit."
+ "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)
(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)
(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)))))
["Toggle plugged" gnus-agent-toggle-plugged t]
["Toggle group plugged" gnus-agent-toggle-group-plugged t]
["List categories" gnus-enter-category-buffer t]
+ ["Add (current) group to category" gnus-agent-add-group t]
+ ["Remove (current) group from category" gnus-agent-remove-group t]
["Send queue" gnus-group-send-queue gnus-plugged]
("Fetch"
["All" gnus-agent-fetch-session gnus-plugged]
- ["Group" gnus-agent-fetch-group gnus-plugged])))))
+ ["Group" gnus-agent-fetch-group gnus-plugged])
+ ["Synchronize flags" gnus-agent-synchronize-flags t]
+ ))))
(defvar gnus-agent-summary-mode-map (make-sparse-keymap))
(gnus-define-keys gnus-agent-summary-mode-map
(make-mode-line-mouse-map mouse-button mouse-func))
string))
-(defun gnus-agent-toggle-plugged (plugged)
+(defun gnus-agent-toggle-plugged (set-to)
"Toggle whether Gnus is unplugged or not."
(interactive (list (not gnus-plugged)))
- (if plugged
- (progn
- (setq gnus-plugged plugged)
- (gnus-run-hooks 'gnus-agent-plugged-hook)
- (setcar (cdr gnus-agent-mode-status)
- (gnus-agent-make-mode-line-string " Plugged"
- 'mouse-2
- 'gnus-agent-toggle-plugged))
- (gnus-agent-go-online gnus-agent-go-online)
- (gnus-agent-possibly-synchronize-flags))
- (gnus-agent-close-connections)
- (setq gnus-plugged plugged)
- (gnus-run-hooks 'gnus-agent-unplugged-hook)
- (setcar (cdr gnus-agent-mode-status)
- (gnus-agent-make-mode-line-string " Unplugged"
- 'mouse-2
- 'gnus-agent-toggle-plugged)))
+ (cond ((eq set-to gnus-plugged)
+ nil)
+ (set-to
+ (setq gnus-plugged set-to)
+ (gnus-run-hooks 'gnus-agent-plugged-hook)
+ (setcar (cdr gnus-agent-mode-status)
+ (gnus-agent-make-mode-line-string " Plugged"
+ 'mouse-2
+ 'gnus-agent-toggle-plugged))
+ (gnus-agent-go-online gnus-agent-go-online)
+ (gnus-agent-possibly-synchronize-flags))
+ (t
+ (gnus-agent-close-connections)
+ (setq gnus-plugged set-to)
+ (gnus-run-hooks 'gnus-agent-unplugged-hook)
+ (setcar (cdr gnus-agent-mode-status)
+ (gnus-agent-make-mode-line-string " Unplugged"
+ 'mouse-2
+ 'gnus-agent-toggle-plugged))))
(force-mode-line-update)
(set-buffer-modified-p t))
+(defmacro gnus-agent-while-plugged (&rest body)
+ `(let ((original-gnus-plugged gnus-plugged))
+ (unwind-protect
+ (progn (gnus-agent-toggle-plugged t)
+ ,@body)
+ (gnus-agent-toggle-plugged original-gnus-plugged))))
+
+(put 'gnus-agent-while-plugged 'lisp-indent-function 0)
+(put 'gnus-agent-while-plugged 'edebug-form-spec '(body))
+
(defun gnus-agent-close-connections ()
"Close all methods covered by the Gnus agent."
(let ((methods gnus-agent-covered-methods))
(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
+ (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)
(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-agent-toggle-plugged gnus-plugged))
- (let ((gnus-command-method (gnus-find-method-for-group group)))
- (gnus-agent-with-fetch
- (gnus-agent-fetch-group-1 group gnus-command-method)
- (gnus-message 5 "Fetching %s...done" group))))
- (when (and (not state)
- gnus-plugged)
- (gnus-agent-toggle-plugged gnus-plugged)))))
+ (setq group (or group gnus-newsgroup-name))
+ (unless group
+ (error "No group on the current line"))
+
+ (gnus-agent-while-plugged
+ (let ((gnus-command-method (gnus-find-method-for-group group)))
+ (gnus-agent-with-fetch
+ (gnus-agent-fetch-group-1 group gnus-command-method)
+ (gnus-message 5 "Fetching %s...done" group)))))
(defun gnus-agent-add-group (category arg)
"Add the current group to an agent category."
t)
(t
(memq article gnus-newsgroup-downloadable)))))
- (gnus-summary-update-mark
- (if unmark
- (progn
- (setq gnus-newsgroup-downloadable
- (delq article gnus-newsgroup-downloadable))
- (gnus-article-mark article))
- (progn
- (setq gnus-newsgroup-downloadable
- (gnus-add-to-sorted-list gnus-newsgroup-downloadable article))
- gnus-downloadable-mark)
- )
- 'unread)))
+ (when (gnus-summary-goto-subject article nil t)
+ (gnus-summary-update-mark
+ (if unmark
+ (progn
+ (setq gnus-newsgroup-downloadable
+ (delq article gnus-newsgroup-downloadable))
+ (gnus-article-mark article))
+ (progn
+ (setq gnus-newsgroup-downloadable
+ (gnus-add-to-sorted-list gnus-newsgroup-downloadable article))
+ gnus-downloadable-mark)
+ )
+ 'unread))))
(defun gnus-agent-get-undownloaded-list ()
"Construct list of articles that have not been downloaded."
(let ((gnus-command-method (gnus-find-method-for-group gnus-newsgroup-name)))
(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))
+ (headers (sort (mapcar (lambda (h) (mail-header-number h)) 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))))
+ (h (car headers)))
(cond ((< a h)
- (pop alist)) ; ignore IDs in the alist that are not being displayed in the summary
+ ;; 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
+ ;; 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 h)
+ (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
+ nil ; ignore already downloaded
)
(t
(pop alist)
(pop headers)
- (gnus-agent-append-to-list tail a)))))
+ (gnus-agent-append-to-list tail-undownloaded a)))))
(while headers
- (gnus-agent-append-to-list tail (mail-header-number (pop headers))))
- (setq gnus-newsgroup-undownloaded (cdr undownloaded))))))
+ (let ((num (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 articles as read that are neither cached, downloaded, nor downloadable."
+ "Mark as read all unhandled articles.
+An article is unhandled if it is neither cached, nor downloaded, nor
+downloadable."
(interactive)
(save-excursion
(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)))
+ (setq articles (gnus-sorted-ndifference
+ (gnus-sorted-ndifference
+ (copy-sequence articles)
+ gnus-newsgroup-downloadable)
+ gnus-newsgroup-cached)))
(while articles
(gnus-summary-mark-article
(when gnus-newsgroup-processable
(setq gnus-newsgroup-downloadable
(let* ((dl gnus-newsgroup-downloadable)
- (gnus-newsgroup-downloadable (sort (copy-sequence gnus-newsgroup-processable) '<))
+ (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
(if all gnus-newsgroup-articles
gnus-newsgroup-downloadable))
(gnus-command-method (gnus-find-method-for-group gnus-newsgroup-name))
- (state gnus-plugged)
fetched-articles)
- (unwind-protect
- (progn
- (unless state
- (gnus-agent-toggle-plugged t))
- (unless articles
- (error "No articles to download"))
- (gnus-agent-with-fetch
- (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)
- (setq gnus-newsgroup-downloadable
- (delq article gnus-newsgroup-downloadable))
- (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-while-plugged
+ (unless articles
+ (error "No articles to download"))
+ (gnus-agent-with-fetch
+ (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)
+ (let ((was-marked-downloadable
+ (memq article gnus-newsgroup-downloadable)))
+ (cond (gnus-agent-mark-unread-after-downloaded
+ (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)))
+ (when (gnus-summary-goto-subject article nil t)
+ (gnus-summary-update-download-mark article))))))
fetched-articles))
(defun gnus-agent-fetch-selected-article ()
(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
;; 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))))
+ (setq current-set-size
+ (+ current-set-size
+ (if (= header-number article)
+ (let ((char-size (mail-header-chars (car headers))))
+ (if (<= char-size 0)
+ (max (* 65 (mail-header-lines (car headers)))
+ 1000)
+ char-size))
+ 0))))
(setcar selected-sets (nreverse (car selected-sets)))
(setq selected-sets (cons nil selected-sets)
current-set-size 0))))
(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))
+ (copy-to-buffer
+ nntp-server-buffer (point-min) (point-max))
(setq pos (nreverse pos)))))
;; Then save these articles into the Agent.
(save-excursion
(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))))
+ (string-to-int
+ (buffer-substring (match-beginning 2)
+ (match-end 2))))
crosses)
(goto-char (match-end 0)))
(gnus-agent-crosspost crosses (caar pos) date)))
(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))))
+ (setq id (buffer-substring
+ (match-beginning 1) (match-end 1))))
(write-region-as-coding-system
gnus-agent-file-coding-system (point-min) (point-max)
(concat dir (number-to-string (caar pos))) nil 'silent)
- (gnus-agent-append-to-list tail-fetched-articles (caar pos)))
+ (gnus-agent-append-to-list
+ tail-fetched-articles (caar pos)))
(widen)
(pop pos))))
- (gnus-agent-save-alist group (cdr fetched-articles) date))
+ (gnus-agent-save-alist group (cdr fetched-articles) date)
+ (gnus-message 7 nil))
(cdr fetched-articles))))))
(defun gnus-agent-crosspost (crosses article &optional date)
(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))) "~"))))
+ (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)
- )
- )
+ (gnus-message 1 "Created backup copy of overview in %s." name)))
t)
(defun gnus-agent-check-overview-buffer (&optional buffer)
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))
(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)))))))
- (gnus-message 10 "gnus-agent-fetch-headers: undownloaded articles are '%s'" (gnus-compress-sequence articles t))
+ (gnus-message
+ 10 "gnus-agent-fetch-headers: undownloaded articles are '%s'"
+ (gnus-compress-sequence articles 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-braid-nov can merge them
- ;; with the contents of FILE.
- (copy-to-buffer gnus-agent-overview-buffer (point-min) (point-max))
+ ;; 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))
(gnus-agent-check-overview-buffer)
(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))
(forward-line -1)
(unless (looking-at "[0-9]+\t")
;; Remove corrupted lines
- (gnus-message 1 "Overview %s is corrupted. Removing corrupted lines..." file)
+ (gnus-message
+ 1 "Overview %s is corrupted. Removing corrupted lines..." file)
(goto-char (point-min))
(while (not (eobp))
(if (looking-at "[0-9]+\t")
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)))
(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"))))
(defun gnus-agent-article-name (article group)
- (expand-file-name (if (stringp article) article (string-to-number article))
+ (expand-file-name article
(file-name-as-directory
(expand-file-name (gnus-agent-group-path group)
(gnus-agent-directory)))))
(save-excursion
(while methods
(condition-case err
- (progn
- (setq gnus-command-method (car methods))
- (when (and (or (gnus-server-opened gnus-command-method)
- (gnus-open-server gnus-command-method))
- (gnus-online gnus-command-method))
- (setq groups (gnus-groups-from-server (car methods)))
- (gnus-agent-with-fetch
- (while (setq group (pop groups))
- (when (<= (gnus-group-level group) gnus-agent-handle-level)
- (gnus-agent-fetch-group-1 group gnus-command-method))))))
+ (progn
+ (setq gnus-command-method (car methods))
+ (when (and (or (gnus-server-opened gnus-command-method)
+ (gnus-open-server gnus-command-method))
+ (gnus-online gnus-command-method))
+ (setq groups (gnus-groups-from-server (car methods)))
+ (gnus-agent-with-fetch
+ (while (setq group (pop groups))
+ (when (<= (gnus-group-level group) gnus-agent-handle-level)
+ (gnus-agent-fetch-group-1 group gnus-command-method))))))
(error
- (unless (funcall gnus-agent-confirmation-function
- (format "Error %s. Continue? " (cdr err)))
- (error "Cannot fetch articles into the Gnus agent")))
+ (unless (funcall gnus-agent-confirmation-function
+ (format "Error %s. Continue? " (cdr err)))
+ (error "Cannot fetch articles into the Gnus agent")))
(quit
- (unless (funcall gnus-agent-confirmation-function
- (format "Quit fetching session %s. Continue? "
- (cdr err)))
+ (unless (funcall gnus-agent-confirmation-function
+ (format "Quit fetching session %s. Continue? "
+ (cdr err)))
(signal 'quit "Cannot fetch articles into the Gnus agent"))))
(pop methods))
(run-hooks 'gnus-agent-fetch-hook)
(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 (It's global
- ;; value should default to nil).
+ (unless gnus-newsgroup-active
+ ;; The variable gnus-newsgroup-active was selected as I need
+ ;; a gnus-summary local variable that is NOT bound to any
+ ;; value (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)))))))
;; predicate, add it to the download list
(when (or (eq num (car marked-articles))
(let ((gnus-score
- (or (cdr (assq num gnus-newsgroup-scored))
+ (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)))
+ (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)))
+ (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))
+ (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)))
+ (gnus-summary-mark-article
+ article gnus-canceled-mark)))
;; Update the group buffer.
(dolist (mark gnus-agent-download-marks)
(when (eq mark 'download)
- (let ((marked-arts (assq mark (gnus-info-marks
- (setq info (gnus-get-info group))))))
+ (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)))
+ (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)))
+ (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)
(defun gnus-category-read ()
"Read the category alist."
- (setq gnus-category-alist
- (or (gnus-agent-read-file
- (nnheader-concat gnus-agent-directory "lib/categories"))
+ (setq gnus-category-alist
+ (or (gnus-agent-read-file
+ (nnheader-concat gnus-agent-directory "lib/categories"))
(list (list 'default 'short nil nil)))))
(defun gnus-category-write ()
(error "Unknown category type: %s" cat))))
(defun gnus-get-predicate (predicate)
- "Return the predicate for CATEGORY."
+ "Return the function implementing PREDICATE."
(or (cdr (assoc predicate gnus-category-predicate-cache))
(let ((func (gnus-category-make-function predicate)))
(setq gnus-category-predicate-cache
(or (gnus-gethash group gnus-category-group-cache)
(assq 'default gnus-category-alist)))
+(defun gnus-agent-expire-group (group &optional articles force)
+ "Expire all old articles in GROUP.
+If you want to force expiring of certain articles, this function can
+take ARTICLES, and FORCE parameters as well.
+
+The articles on which the expiration process runs are selected as follows:
+ if ARTICLES is null, all read and unmarked articles.
+ if ARTICLES is t, all articles.
+ if ARTICLES is a list, just those articles.
+FORCE is equivalent to setting the expiration predicates to true."
+ (interactive)
+
+ (if (not group)
+ (gnus-agent-expire articles group force)
+ (if (or (not (eq articles t))
+ (yes-or-no-p
+ (concat "Are you sure that you want to "
+ "expire all articles in " group ".")))
+ (let ((gnus-command-method (gnus-find-method-for-group group))
+ (overview (gnus-get-buffer-create " *expire overview*"))
+ orig)
+ (unwind-protect
+ (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))))))
+ (save-excursion
+ (gnus-agent-expire-group-1
+ group overview (gnus-gethash-safe group orig)
+ articles force)))
+ (kill-buffer overview))))
+ (gnus-message 4 "Expiry...done")))
+
+(defun gnus-agent-expire-group-1 (group overview active articles force)
+ ;; Internal function - requires caller to have set
+ ;; gnus-command-method, initialized overview buffer, and to have
+ ;; provided a non-nil active
+ (interactive)
+
+ (gnus-message 5 "Expiring articles in %s" group)
+ (gnus-agent-load-alist group)
+ (let* ((info (gnus-get-info group))
+ (alist gnus-agent-article-alist)
+ (dir (concat
+ (gnus-agent-directory)
+ (gnus-agent-group-path group)
+ "/"))
+ (day (if (numberp gnus-agent-expire-days)
+ (- (time-to-days (current-time)) gnus-agent-expire-days)
+ (let ((days gnus-agent-expire-days))
+ (catch 'found
+ (while days
+ (when (eq 0 (string-match
+ (caar days)
+ 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))))
+ (specials (if (and alist
+ (not force))
+ ;; This could be a bit of a problem. I need to
+ ;; keep the last article to avoid refetching
+ ;; headers when using nntp in the backend. At
+ ;; the same time, if someone uses a backend
+ ;; that supports article moving then I may have
+ ;; to remove the last article to complete the
+ ;; move. Right now, I'm going to assume that
+ ;; FORCE overrides specials.
+ (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 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 "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
+ (gnus-message 10 "gnus-agent-expire: Article %d: Kept %s article." article-number keep)
+ (when fetch-date
+ (unless (file-exists-p
+ (concat dir (number-to-string
+ article-number)))
+ (setf (nth 1 entry) nil)
+ (gnus-message 3 "gnus-agent-expire cleared \
+download flag on article %d as the cached article file is missing."
+ (caar dlist)))
+ (unless marker
+ (gnus-message 1 "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-message 10 "gnus-agent-expire: Article %d: Article kept as expiration tests failed." article-number)
+ (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 group))
+
+ (when (buffer-modified-p)
+ (gnus-make-directory dir)
+ (write-region-as-coding-system gnus-agent-file-coding-system
+ (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 (&optional articles group force)
"Expire all old articles.
If you want to force expiring of certain articles, this function can
if ARTICLES is t, all articles.
if ARTICLES is a list, just those articles.
Setting GROUP will limit expiration to that group.
-FORCE is equivalent to setting gnus-agent-expire-days to zero(0)."
+FORCE is equivalent to setting the expiration predicates to true."
(interactive)
-
- (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*"))
- (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))
- (debug)
- (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)))))
+
+ (if group
+ (gnus-agent-expire-group group articles force)
+ (if (or (not (eq articles t))
+ (yes-or-no-p "Are you sure that you want to expire all \
+articles in every agentized group."))
+ (let ((methods gnus-agent-covered-methods)
+ gnus-command-method overview orig)
+ (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))
+ (let* ((active
+ (gnus-gethash-safe expiring-group orig)))
- (when active
- (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 "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... ")
- (let ((special 0) ; If two entries have the same article-number then sort by ascending keep_flag.
- (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 "gnus-agent-expire cleared download flag on article %d as the cached article file is missing." (caar dlist)))
- (unless marker
- (gnus-message 1 "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)
- (gnus-make-directory dir)
- (write-region-as-coding-system
- gnus-agent-file-coding-system
- (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)))))))))))
- (kill-buffer overview)))))
- (gnus-message 4 "Expiry...done"))
+ (when active
+ (save-excursion
+ (gnus-agent-expire-group-1
+ expiring-group overview active articles force)))))))
+ (kill-buffer overview))
+ (gnus-message 4 "Expiry...done")))))
;;;###autoload
(defun gnus-agent-batch ()
(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))
-;; Functionally, I don't need to construct a temp list using mapcar.
+ ;; 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.
(if (gnus-agent-load-alist group)
(let* ((ref gnus-agent-article-alist)
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))
(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.
+ ;; 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-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-file-coding-system
(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)))
(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]+\t")
+ (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))
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
+ ;; Don't sort now as I haven't verified
+ ;; that every line begins with a number
(setq load t))
((= l1 l2)
(forward-line -1)
(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)
(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))
;; 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 n)
- (when o
- (push (cons (caar o) nil) alist)))
+ (cond ((not o)
+ nil)
+ ((not n)
+ (push (cons (caar o) nil) alist))
((< (caar n) (caar o))
(setcdr n (list (car o)))))))
(sit-for 0))
)
+ (gnus-message 5 nil)
regenerated))
;;;###autoload