- (let ((methods gnus-agent-covered-methods)
- (day (if (numberp gnus-agent-expire-days)
- (- (time-to-days (current-time)) gnus-agent-expire-days)
- nil))
- (current-day (time-to-days (current-time)))
- gnus-command-method sym group articles
- 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))))))
- (let ((expiry-hashtb (gnus-make-hashtable 1023)))
- (gnus-agent-open-history)
- (set-buffer
- (setq gnus-agent-current-history
- (setq history (gnus-agent-history-buffer))))
- (goto-char (point-min))
- (when (> (buffer-size) 1)
- (goto-char (point-min))
- (while (not (eobp))
- (skip-chars-forward "^\t")
- (if (let ((fetch-date (read (current-buffer))))
- (if (numberp fetch-date)
- ;; We now have the arrival day, so we see
- ;; whether it's old enough to be expired.
- (if (numberp day)
- (> fetch-date day)
- (skip-chars-forward "\t")
- (setq found nil
- days gnus-agent-expire-days)
- (while (and (not found)
- days)
- (when (looking-at (caar days))
- (setq found (cadar days)))
- (pop days))
- (> fetch-date (- current-day found)))
- ;; History file is corrupted.
- (gnus-message
- 5
- (format "File %s is corrupted!"
- (gnus-agent-lib-file "history")))
- (sit-for 1)
- ;; Ignore it
- t))
- ;; New article; we don't expire it.
- (forward-line 1)
- ;; Old article. Schedule it for possible nuking.
- (while (not (eolp))
- (setq sym (let ((obarray expiry-hashtb) s)
- (setq s (read (current-buffer)))
- (if (stringp s) (intern s) s)))
- (if (boundp sym)
- (set sym (cons (cons (read (current-buffer)) (point))
- (symbol-value sym)))
- (set sym (list (cons (read (current-buffer)) (point)))))
- (skip-chars-forward " "))
- (forward-line 1)))
- ;; We now have all articles that can possibly be expired.
- (mapatoms
- (lambda (sym)
- (setq group (symbol-name sym)
- articles (sort (symbol-value sym) 'car-less-than-car)
- low (car (gnus-active group))
- info (gnus-get-info group)
- unreads (ignore-errors
- (gnus-list-of-unread-articles group))
- marked (nconc
- (gnus-uncompress-range
- (cdr (assq 'tick (gnus-info-marks info))))
- (gnus-uncompress-range
- (cdr (assq 'dormant
- (gnus-info-marks info)))))
- nov-file (gnus-agent-article-name ".overview" group)
- lowest nil
- highest nil)
- (gnus-agent-load-alist group)
- (gnus-message 5 "Expiring articles in %s" group)
- (set-buffer overview)
- (erase-buffer)
- (when (file-exists-p nov-file)
- (nnheader-insert-file-contents nov-file))
- (goto-char (point-min))
- (setq article 0)
- (while (setq elem (pop articles))
- (setq article (car elem))
- (when (or (null low)
- (< article low)
- gnus-agent-expire-all
- (and (not (memq article unreads))
- (not (memq article marked))))
- ;; Find and nuke the NOV line.
- (while (and (not (eobp))
- (or (not (numberp
- (setq art (read (current-buffer)))))
- (< art article)))
- (if (and (numberp art)
- (file-exists-p
- (gnus-agent-article-name
- (number-to-string art) group)))
- (progn
- (unless lowest
- (setq lowest art))
- (setq highest art)
- (forward-line 1))
- ;; Remove old NOV lines that have no articles.
- (gnus-delete-line)))
- (if (or (eobp)
- (/= art article))
- (beginning-of-line)
- (gnus-delete-line))
- ;; Nuke the article.
- (when (file-exists-p
- (setq file (gnus-agent-article-name
- (number-to-string article)
- group)))
- (delete-file file))
- ;; Schedule the history line for nuking.
- (push (cdr elem) histories)))
- (gnus-make-directory (file-name-directory nov-file))
- (let ((coding-system-for-write
- gnus-agent-file-coding-system))
- (write-region (point-min) (point-max) nov-file nil 'silent))
- ;; Delete the unwanted entries in the alist.
- (setq gnus-agent-article-alist
- (sort gnus-agent-article-alist 'car-less-than-car))
- (let* ((alist gnus-agent-article-alist)
- (prev (cons nil alist))
- (first prev)
- expired)
- (while (and alist
- (<= (caar alist) article))
- (if (or (not (cdar alist))
- (not (file-exists-p
- (gnus-agent-article-name
- (number-to-string
- (caar alist))
- group))))
- (progn
- (push (caar alist) expired)
- (setcdr prev (setq alist (cdr alist))))
- (setq prev alist
- alist (cdr alist))))
- (setq gnus-agent-article-alist (cdr first))
- (gnus-agent-save-alist group)
- ;; Mark all articles up to the first article
- ;; in `gnus-article-alist' as read.
- (when (and info (caar gnus-agent-article-alist))
- (setcar (nthcdr 2 info)
- (gnus-range-add
- (nth 2 info)
- (cons 1 (- (caar gnus-agent-article-alist) 1)))))
- ;; Maybe everything has been expired from
- ;; `gnus-article-alist' and so the above marking as
- ;; read could not be conducted, or there are
- ;; expired article within the range of the alist.
- (when (and info
- expired
- (or (not (caar gnus-agent-article-alist))
- (> (car expired)
- (caar gnus-agent-article-alist))))
- (setcar (nthcdr 2 info)
- (gnus-add-to-range
- (nth 2 info)
- (nreverse expired))))
- (gnus-dribble-enter
- (concat "(gnus-group-set-info '"
- (gnus-prin1-to-string info)
- ")")))
- (when lowest
- (if (gnus-gethash group orig)
- (setcar (gnus-gethash group orig) lowest)
- (gnus-sethash group (cons lowest highest) orig))))
- expiry-hashtb)
- (set-buffer history)
- (setq histories (nreverse (sort histories '<)))
- (while histories
- (goto-char (pop histories))
- (gnus-delete-line))
- (gnus-agent-save-history)
- (gnus-agent-close-history)
- (gnus-write-active-file
- (gnus-agent-lib-file "active") orig))
- (gnus-message 4 "Expiry...done")))))))
+
+ (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)))
+ (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 (* 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
+ (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
+ '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" article)
+ (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 ", "))))
+ )
+
+ ;; 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)))))))))))
+ (kill-buffer overview)))))
+ (gnus-message 4 "Expiry...done"))