* nnimap.el (nnimap-expiry-target): Don't search for which
[gnus] / lisp / gnus-agent.el
index 6c32ed6..9f203f6 100644 (file)
@@ -29,6 +29,7 @@
 (require 'nnvirtual)
 (require 'gnus-sum)
 (require 'gnus-score)
+(require 'gnus-srvr)
 (eval-when-compile
   (if (featurep 'xemacs)
       (require 'itimer)
@@ -127,6 +128,18 @@ If this is `ask' the hook will query the user."
                 (const :tag "Ask" ask))
   :group 'gnus-agent)
 
+(defcustom gnus-agent-mark-unread-after-downloaded t
+  "Indicate whether to mark articles unread after downloaded."
+  :version "21.1"
+  :type 'boolean
+  :group 'gnus-agent)
+
+(defcustom gnus-agent-download-marks '(download)
+  "Marks for downloading."
+  :version "21.1"
+  :type '(repeat (symbol :tag "Mark"))
+  :group 'gnus-agent)
+
 ;;; Internal variables
 
 (defvar gnus-agent-history-buffers nil)
@@ -142,6 +155,12 @@ If this is `ask' the hook will query the user."
 (defvar gnus-agent-file-name nil)
 (defvar gnus-agent-send-mail-function nil)
 (defvar gnus-agent-file-coding-system 'raw-text)
+(defvar gnus-agent-file-loading-cache nil)
+
+(defvar gnus-agent-auto-agentize-methods '(nntp nnimap)
+  "Initially, all servers from these methods are agentized.
+The user may remove or add servers using the Server buffer.  See Info
+node `(gnus)Server Buffer'.")
 
 ;; Dynamic variables
 (defvar gnus-headers)
@@ -277,7 +296,8 @@ If this is `ask' the hook will query the user."
   "JY" gnus-agent-synchronize-flags
   "JS" gnus-group-send-queue
   "Ja" gnus-agent-add-group
-  "Jr" gnus-agent-remove-group)
+  "Jr" gnus-agent-remove-group
+  "Jo" gnus-agent-toggle-group-plugged)
 
 (defun gnus-agent-group-make-menu-bar ()
   (unless (boundp 'gnus-agent-group-menu)
@@ -285,6 +305,7 @@ If this is `ask' the hook will query the user."
      gnus-agent-group-menu gnus-agent-group-mode-map ""
      '("Agent"
        ["Toggle plugged" gnus-agent-toggle-plugged t]
+       ["Toggle group plugged" gnus-agent-toggle-group-plugged t]
        ["List categories" gnus-enter-category-buffer t]
        ["Send queue" gnus-group-send-queue gnus-plugged]
        ("Fetch"
@@ -309,7 +330,7 @@ If this is `ask' the hook will query the user."
        ["Mark as downloadable" gnus-agent-mark-article t]
        ["Unmark as downloadable" gnus-agent-unmark-article t]
        ["Toggle mark" gnus-agent-toggle-mark t]
-       ["Fetch downloadable" gnus-aget-summary-fetch-group t]
+       ["Fetch downloadable" gnus-agent-summary-fetch-group t]
        ["Catchup undownloaded" gnus-agent-catchup t]))))
 
 (defvar gnus-agent-server-mode-map (make-sparse-keymap))
@@ -341,7 +362,7 @@ If this is `ask' the hook will query the user."
       (progn
        (setq gnus-plugged plugged)
        (gnus-run-hooks 'gnus-agent-plugged-hook)
-       (setcar (cdr gnus-agent-mode-status) 
+       (setcar (cdr gnus-agent-mode-status)
                (gnus-agent-make-mode-line-string " Plugged"
                                                  'mouse-2
                                                  'gnus-agent-toggle-plugged))
@@ -350,7 +371,7 @@ If this is `ask' the hook will query the user."
     (gnus-agent-close-connections)
     (setq gnus-plugged plugged)
     (gnus-run-hooks 'gnus-agent-unplugged-hook)
-    (setcar (cdr gnus-agent-mode-status) 
+    (setcar (cdr gnus-agent-mode-status)
            (gnus-agent-make-mode-line-string " Unplugged"
                                              'mouse-2
                                              'gnus-agent-toggle-plugged)))
@@ -376,6 +397,13 @@ If this is `ask' the hook will query the user."
   (setq gnus-plugged t)
   (gnus))
 
+;;;###autoload
+(defun gnus-slave-unplugged (&optional arg)
+  "Read news as a slave unplugged."
+  (interactive "P")
+  (setq gnus-plugged nil)
+  (gnus arg nil 'slave))
+
 ;;;###autoload
 (defun gnus-agentize ()
   "Allow Gnus to be an offline newsreader.
@@ -396,7 +424,14 @@ minor mode in all Gnus buffers."
                                         message-send-mail-function)
          message-send-mail-real-function 'gnus-agent-send-mail))
   (unless gnus-agent-covered-methods
-    (setq gnus-agent-covered-methods (list gnus-select-method))))
+    (mapcar
+     (lambda (server)
+       (if (memq (car (gnus-server-to-method server)) 
+                gnus-agent-auto-agentize-methods)
+          (setq gnus-agent-covered-methods 
+                (cons (gnus-server-to-method server)
+                      gnus-agent-covered-methods ))))
+     (append (list gnus-select-method) gnus-secondary-select-methods))))
 
 (defun gnus-agent-queue-setup ()
   "Make sure the queue group exists."
@@ -453,6 +488,7 @@ be a select method."
              methods (cdr methods)))
       covered)))
 
+;;;###autoload
 (defun gnus-agent-possibly-save-gcc ()
   "Save GCC if Gnus is unplugged."
   (when (and (not gnus-plugged) (gnus-agent-any-covered-gcc))
@@ -605,8 +641,12 @@ be a select method."
 (defun gnus-agent-read-servers ()
   "Read the alist of covered servers."
   (setq gnus-agent-covered-methods
-       (gnus-agent-read-file
-        (nnheader-concat gnus-agent-directory "lib/servers"))))
+       (mapcar (lambda (m)
+                 (gnus-server-get-method
+                  nil
+                  (or m "native")))
+               (gnus-agent-read-file
+                (nnheader-concat gnus-agent-directory "lib/servers")))))
 
 (defun gnus-agent-write-servers ()
   "Write the alist of covered servers."
@@ -614,7 +654,8 @@ be a select method."
   (let ((coding-system-for-write nnheader-file-coding-system)
        (file-name-coding-system nnmail-pathname-coding-system))
     (with-temp-file (nnheader-concat gnus-agent-directory "lib/servers")
-      (prin1 gnus-agent-covered-methods (current-buffer)))))
+      (prin1 (mapcar 'gnus-method-simplify gnus-agent-covered-methods)
+            (current-buffer)))))
 
 ;;;
 ;;; Summary commands
@@ -667,7 +708,8 @@ the actual number of articles toggled is returned."
          (push article gnus-newsgroup-undownloaded))
       (setq gnus-newsgroup-undownloaded
            (delq article gnus-newsgroup-undownloaded))
-      (push article gnus-newsgroup-downloadable))
+      (setq gnus-newsgroup-downloadable
+           (gnus-add-to-sorted-list gnus-newsgroup-downloadable article)))
     (gnus-summary-update-mark
      (if unmark gnus-undownloaded-mark gnus-downloadable-mark)
      'unread)))
@@ -679,12 +721,21 @@ the actual number of articles toggled is returned."
               (gnus-agent-method-p gnus-command-method))
       (gnus-agent-load-alist gnus-newsgroup-name)
       ;; First mark all undownloaded articles as undownloaded.
-      (dolist (article (mapcar (lambda (header) (mail-header-number header))
-                              gnus-newsgroup-headers))
-       (unless (or (cdr (assq article gnus-agent-article-alist))
-                   (memq article gnus-newsgroup-downloadable)
-                   (memq article gnus-newsgroup-cached))
-         (push article gnus-newsgroup-undownloaded)))
+      (let ((articles (mapcar (lambda (header) (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)
@@ -719,7 +770,8 @@ the actual number of articles toggled is returned."
            (dolist (article articles)
              (setq gnus-newsgroup-downloadable
                    (delq article gnus-newsgroup-downloadable))
-             (gnus-summary-mark-article article gnus-unread-mark))))
+             (if gnus-agent-mark-unread-after-downloaded
+                 (gnus-summary-mark-article article gnus-unread-mark)))))
       (when (and (not state)
                 gnus-plugged)
        (gnus-agent-toggle-plugged nil)))))
@@ -787,7 +839,7 @@ the actual number of articles toggled is returned."
        (goto-char (point-min))
        (when (re-search-forward
               (concat "^" (regexp-quote group) " ") nil t)
-          (save-excursion
+         (save-excursion
            (read (current-buffer))                      ;; max
            (setq oactive-min (read (current-buffer))))  ;; min
          (gnus-delete-line))
@@ -931,33 +983,35 @@ the actual number of articles toggled is returned."
          (while pos
            (narrow-to-region (cdar pos) (or (cdadr pos) (point-max)))
            (goto-char (point-min))
-           (when (search-forward "\n\n" nil t)
-             (when (search-backward "\nXrefs: " nil t)
-               ;; Handle crossposting.
-               (skip-chars-forward "^ ")
-               (skip-chars-forward " ")
-               (setq crosses nil)
-               (while (looking-at "\\([^: \n]+\\):\\([0-9]+\\) +")
-                 (push (cons (buffer-substring (match-beginning 1)
-                                               (match-end 1))
-                             (buffer-substring (match-beginning 2)
-                                               (match-end 2)))
-                       crosses)
-                 (goto-char (match-end 0)))
-               (gnus-agent-crosspost crosses (caar pos))))
-           (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 t))
-           (gnus-agent-enter-history
-            id (or crosses (list (cons group (caar pos)))) date)
+           (unless (eobp)  ;; Don't save empty articles.
+             (when (search-forward "\n\n" nil t)
+               (when (search-backward "\nXrefs: " nil t)
+                 ;; Handle cross posting.
+                 (skip-chars-forward "^ ")
+                 (skip-chars-forward " ")
+                 (setq crosses nil)
+                 (while (looking-at "\\([^: \n]+\\):\\([0-9]+\\) +")
+                   (push (cons (buffer-substring (match-beginning 1)
+                                                 (match-end 1))
+                               (buffer-substring (match-beginning 2)
+                                                 (match-end 2)))
+                         crosses)
+                   (goto-char (match-end 0)))
+                 (gnus-agent-crosspost crosses (caar pos))))
+             (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 t))
+             (gnus-agent-enter-history
+              id (or crosses (list (cons group (caar pos)))) date))
            (widen)
            (pop pos)))
        (gnus-agent-save-alist group)))))
@@ -1013,13 +1067,14 @@ the actual number of articles toggled is returned."
        gnus-agent-cache)
     ;; Add article with marks to list of article headers we want to fetch.
     (dolist (arts (gnus-info-marks (gnus-get-info group)))
-      (setq articles (gnus-range-add articles (cdr arts))))
+      (unless (memq (car arts) '(seen recent))
+       (setq articles (gnus-range-add articles (cdr arts)))))
     (setq articles (sort (gnus-uncompress-sequence articles) '<))
     ;; Remove known articles.
     (when (gnus-agent-load-alist group)
-      (setq articles (gnus-sorted-intersection
+      (setq articles (gnus-list-range-intersection
                      articles
-                     (gnus-uncompress-range
+                     (list
                       (cons (1+ (caar (last gnus-agent-article-alist)))
                             (cdr (gnus-active group)))))))
     ;; Fetch them.
@@ -1028,11 +1083,11 @@ the actual number of articles toggled is returned."
     (when articles
       (gnus-message 7 "Fetching headers for %s..." group)
       (save-excursion
-       (set-buffer nntp-server-buffer)
-       (unless (eq 'nov (gnus-retrieve-headers articles group))
-         (nnvirtual-convert-headers))
-       ;; Save these headers for later processing.
-       (copy-to-buffer gnus-agent-overview-buffer (point-min) (point-max))
+       (set-buffer nntp-server-buffer)
+       (unless (eq 'nov (gnus-retrieve-headers articles group))
+         (nnvirtual-convert-headers))
+       ;; Save these headers for later processing.
+       (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
@@ -1046,78 +1101,87 @@ the actual number of articles toggled is returned."
        articles))))
 
 (defsubst gnus-agent-copy-nov-line (article)
-  (let (b e)
+  (let (art b e)
     (set-buffer gnus-agent-overview-buffer)
-    (unless (eobp)
+    (while (and (not (eobp))
+               (< (setq art (read (current-buffer))) article))
+      (forward-line 1))
+    (beginning-of-line)
+    (if (or (eobp)
+           (not (eq article art)))
+       (set-buffer nntp-server-buffer)
       (setq b (point))
-      (if (eq article (read (current-buffer)))
-         (setq e (progn (forward-line 1) (point)))
-       (progn
-         (beginning-of-line)
-         (setq e b)))
+      (setq e (progn (forward-line 1) (point)))
       (set-buffer nntp-server-buffer)
       (insert-buffer-substring gnus-agent-overview-buffer b e))))
 
 (defun gnus-agent-braid-nov (group articles file)
-  (set-buffer gnus-agent-overview-buffer)
-  (goto-char (point-min))
-  (set-buffer nntp-server-buffer)
-  (erase-buffer)
-  (nnheader-insert-file-contents file)
-  (goto-char (point-max))
-  (if (or (= (point-min) (point-max))
-         (progn
-           (forward-line -1)
-           (< (read (current-buffer)) (car articles))))
-      ;; We have only headers that are after the older headers,
-      ;; so we just append them.
-      (progn
-       (goto-char (point-max))
-       (insert-buffer-substring gnus-agent-overview-buffer))
-    ;; We do it the hard way.
-    (nnheader-find-nov-line (car articles))
-    (gnus-agent-copy-nov-line (car articles))
-    (pop articles)
-    (while (and articles
-               (not (eobp)))
-      (while (and (not (eobp))
-                 (< (read (current-buffer)) (car articles)))
-       (forward-line 1))
-      (beginning-of-line)
-      (unless (eobp)
-       (gnus-agent-copy-nov-line (car articles))
-       (setq articles (cdr articles))))
+  (let (start last)
+    (set-buffer gnus-agent-overview-buffer)
+    (goto-char (point-min))
+    (set-buffer nntp-server-buffer)
+    (erase-buffer)
+    (nnheader-insert-file-contents file)
+    (goto-char (point-max))
+    (unless (or (= (point-min) (point-max))
+               (progn
+                 (forward-line -1)
+                 (< (setq last (read (current-buffer))) (car articles))))
+      ;; We do it the hard way.
+      (nnheader-find-nov-line (car articles))
+      (gnus-agent-copy-nov-line (pop articles))
+      (while (and articles
+                 (not (eobp)))
+       (while (and (not (eobp))
+                   (< (read (current-buffer)) (car articles)))
+         (forward-line 1))
+       (beginning-of-line)
+       (unless (eobp)
+         (gnus-agent-copy-nov-line (pop articles)))))
+    ;; Copy the rest lines
     (set-buffer nntp-server-buffer)
+    (goto-char (point-max))
     (when articles
-      (let (b e)
+      (when last
        (set-buffer gnus-agent-overview-buffer)
-       (setq b (point)
-             e (point-max))
        (while (and (not (eobp))
-                   (<= (read (current-buffer)) (car articles)))
-         (forward-line 1)
-         (setq b (point)))
-       (set-buffer nntp-server-buffer)
-       (insert-buffer-substring gnus-agent-overview-buffer b e)))))
+                   (<= (read (current-buffer)) last))
+         (forward-line 1))
+       (beginning-of-line)
+       (setq start (point))
+       (set-buffer nntp-server-buffer))
+      (insert-buffer-substring gnus-agent-overview-buffer start))))
 
 (defun gnus-agent-load-alist (group &optional dir)
   "Load the article-state alist for GROUP."
+  (let ((file ))
   (setq gnus-agent-article-alist
-       (gnus-agent-read-file
+       (gnus-cache-file-contents
         (if dir
-            (expand-file-name ".agentview" dir)
-          (gnus-agent-article-name ".agentview" group)))))
+                 (expand-file-name ".agentview" dir)
+               (gnus-agent-article-name ".agentview" group))
+        'gnus-agent-file-loading-cache
+        'gnus-agent-read-file))))
 
 (defun gnus-agent-save-alist (group &optional articles state dir)
   "Save the article-state alist for GROUP."
-  (let ((file-name-coding-system nnmail-pathname-coding-system)
-       print-level print-length item)
-    (dolist (art articles)
-       (if (setq item (memq art gnus-agent-article-alist))
-           (setcdr item state)
-         (push  (cons art state) gnus-agent-article-alist)))
-    (setq gnus-agent-article-alist 
-         (sort gnus-agent-article-alist 'car-less-than-car))
+  (let* ((file-name-coding-system nnmail-pathname-coding-system)
+        (prev (cons nil gnus-agent-article-alist))
+        (all prev)
+        print-level print-length item article)
+    (while (setq article (pop articles))
+      (while (and (cdr prev)
+                (< (caadr prev) article))
+       (setq prev (cdr prev)))
+      (cond
+       ((not (cdr prev))
+       (setcdr prev (list (cons article state))))
+       ((> (caadr prev) article)
+       (setcdr prev (cons (cons article state) (cdr prev))))
+       ((= (caadr prev) article)
+       (setcdr (cadr prev) state)))
+      (setq prev (cdr prev)))
+    (setq gnus-agent-article-alist (cdr all))
     (with-temp-file (if dir
                        (expand-file-name ".agentview" dir)
                      (gnus-agent-article-name ".agentview" group))
@@ -1168,12 +1232,12 @@ the actual number of articles toggled is returned."
                      (gnus-agent-fetch-group-1 group gnus-command-method))))))
          (error
           (unless (funcall gnus-agent-confirmation-function
-                           (format "Error (%s).  Continue? " err))
+                           (format "Error (%s).  Continue? " (cadr err)))
             (error "Cannot fetch articles into the Gnus agent")))
          (quit
           (unless (funcall gnus-agent-confirmation-function
                            (format "Quit fetching session (%s).  Continue? "
-                                   err))
+                                   (cadr err)))
             (signal 'quit "Cannot fetch articles into the Gnus agent"))))
        (pop methods))
       (run-hooks 'gnus-agent-fetch-hook)
@@ -1243,18 +1307,20 @@ the actual number of articles toggled is returned."
       (when arts
        (gnus-agent-fetch-articles group arts)))
     ;; Perhaps we have some additional articles to fetch.
-    (setq arts (assq 'download (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)))
-      (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)
-              ")")))))
+    (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)
+                  ")")))))))
 
 ;;;
 ;;; Agent Category Mode
@@ -1406,7 +1472,7 @@ The following commands are available:
     (gnus-category-position-point)))
 
 (defun gnus-category-name ()
-  (or (get-text-property (gnus-point-at-bol) 'gnus-category)
+  (or (intern (get-text-property (gnus-point-at-bol) 'gnus-category))
       (error "No category on the current line")))
 
 (defun gnus-category-read ()
@@ -1578,9 +1644,11 @@ The following commands are available:
 (defun gnus-get-predicate (predicate)
   "Return the predicate for CATEGORY."
   (or (cdr (assoc predicate gnus-category-predicate-cache))
-      (cdar (push (cons predicate
-                       (gnus-category-make-function predicate))
-                 gnus-category-predicate-cache))))
+      (let ((func (gnus-category-make-function predicate)))
+       (setq gnus-category-predicate-cache
+             (nconc gnus-category-predicate-cache
+                    (list (cons predicate func))))
+       func)))
 
 (defun gnus-group-category (group)
   "Return the category GROUP belongs to."
@@ -1595,15 +1663,20 @@ The following commands are available:
   (or (gnus-gethash group gnus-category-group-cache)
       (assq 'default gnus-category-alist)))
 
-(defun gnus-agent-expire ()
-  "Expire all old articles."
+(defun gnus-agent-expire (&optional articles group force)
+  "Expire all old articles.
+If you want to force expiring of certain articles, this function can
+take ARTICLES, GROUP and FORCE parameters as well.  Setting ARTICLES
+and GROUP without FORCE is not supported."
   (interactive)
-  (let ((methods gnus-agent-covered-methods)
+  (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))
        (current-day (time-to-days (current-time)))
-       gnus-command-method sym group articles
+       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
@@ -1622,181 +1695,207 @@ The following commands are available:
             (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))))
+           (if (and articles group force) ;; point usless without art+group
+               (while (setq article (pop articles))
+                 ;; try to find history entries for articles
+                 (goto-char (point-min))
+                 (if (re-search-forward 
+                      (concat "^[^\t]*\t[^\t]*\t\(.* ?\)"
+                              (format "%S" (gnus-group-prefixed-name
+                                            group gnus-command-method))
+                              " "
+                              (number-to-string article)
+                              " $")
+                      nil t)
+                     (setq pos (point))
+                   (setq pos nil))
+                 (setq sym (let ((obarray expiry-hashtb) s)
+                             (intern group)))
+                 (if (boundp sym)
+                     (set sym (cons (cons article pos)
+                                    (symbol-value sym)))
+                   (set sym (list (cons article pos)))))
+             ;; go through history file to find eligble articles
+             (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)
+                    arts (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 arts))
+                (setq article (car elem))
+                (when (or (null low)
+                          (< article low)
+                          gnus-agent-expire-all
+                          (and (not (memq article unreads))
+                               (not (memq article marked)))
+                          force)
+                  ;; 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
-                          (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")))))))
+                          (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.
+                  (if (cdr elem)
+                      (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-agent-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-agent-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"))))))
 
 ;;;###autoload
 (defun gnus-agent-batch ()
+  "Start Gnus, send queue and fetch session."
   (interactive)
   (let ((init-file-user "")
        (gnus-always-read-dribble-file t))
     (gnus))
-  (gnus-group-send-queue)
-  (gnus-agent-fetch-session))
+  (let ((gnus-agent-confirmation-function 'gnus-agent-batch-confirmation))
+    (gnus-group-send-queue)
+    (gnus-agent-fetch-session)))
 
 (defun gnus-agent-retrieve-headers (articles group &optional fetch-old)
   (save-excursion
@@ -1811,21 +1910,21 @@ The following commands are available:
          (erase-buffer)
          (let ((nnheader-file-coding-system
                 gnus-agent-file-coding-system))
-           (nnheader-insert-file-contents file))
-         (goto-char (point-min)) 
+           (nnheader-insert-nov-file file (car articles)))
+         (nnheader-find-nov-line (car articles))
          (while (not (eobp))
            (when (looking-at "[0-9]")
              (push (read (current-buffer)) cached-articles))
            (forward-line 1))
-         (setq cached-articles (sort cached-articles '<))))
-      (if (setq uncached-articles 
-                 (gnus-set-difference articles cached-articles))
+         (setq cached-articles (nreverse cached-articles))))
+      (if (setq uncached-articles
+               (gnus-sorted-difference articles cached-articles))
          (progn
            (set-buffer nntp-server-buffer)
            (erase-buffer)
            (let (gnus-agent-cache)
-             (unless (eq 'nov 
-                         (gnus-retrieve-headers 
+             (unless (eq 'nov
+                         (gnus-retrieve-headers
                           uncached-articles group fetch-old))
                (nnvirtual-convert-headers)))
            (set-buffer gnus-agent-overview-buffer)
@@ -1867,8 +1966,9 @@ The following commands are available:
                  (gnus-agent-directory)
                  (gnus-agent-group-path group) "/"
                  (number-to-string article)))
-       (buffer-read-only nil))
-    (when (file-exists-p file)
+        (buffer-read-only nil))
+    (when (and (file-exists-p file)
+              (> (nth 7 (file-attributes file)) 0))
       (erase-buffer)
       (gnus-kill-all-overlays)
       (let ((coding-system-for-read gnus-cache-coding-system))
@@ -1893,7 +1993,7 @@ The following commands are available:
          (let ((nnheader-file-coding-system
                 gnus-agent-file-coding-system))
            (nnheader-insert-file-contents file)))
-      (goto-char (point-min)) 
+      (goto-char (point-min))
       (while (not (eobp))
        (while (not (or (eobp) (looking-at "[0-9]")))
          (setq point (point))
@@ -1906,7 +2006,7 @@ The following commands are available:
            (while (and arts (> n (car arts)))
              (message "Regenerating NOV %s %d..." group (car arts))
              (mm-with-unibyte-buffer
-               (nnheader-insert-file-contents 
+               (nnheader-insert-file-contents
                 (concat dir (number-to-string (car arts))))
                (goto-char (point-min))
                (if (search-forward "\n\n" nil t)
@@ -1931,15 +2031,15 @@ The following commands are available:
     (unless clean
       (gnus-agent-load-alist group))
     (setq alist (sort alist 'car-less-than-car))
-    (setq gnus-agent-article-alist (sort gnus-agent-article-alist 
+    (setq gnus-agent-article-alist (sort gnus-agent-article-alist
                                         'car-less-than-car))
     (while (and alist gnus-agent-article-alist)
-      (cond 
+      (cond
        ((< (caar alist) (caar gnus-agent-article-alist))
        (push (pop alist) new-alist))
        ((> (caar alist) (caar gnus-agent-article-alist))
        (push (list (car (pop gnus-agent-article-alist))) new-alist))
-       (t 
+       (t
        (pop gnus-agent-article-alist)
        (while (and gnus-agent-article-alist
                    (= (caar alist) (caar gnus-agent-article-alist)))
@@ -1963,8 +2063,8 @@ The following commands are available:
       (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))))
-      (gnus-agent-enter-history 
-       id (list (cons group article)) 
+      (gnus-agent-enter-history
+       id (list (cons group article))
        (time-to-days (nth 5 (file-attributes file)))))))
 
 ;;;###autoload
@@ -1975,7 +2075,7 @@ If CLEAN, don't read existing active and agentview files."
   (message "Regenerating Gnus agent files...")
   (dolist (gnus-command-method gnus-agent-covered-methods)
     (let ((active-file (gnus-agent-lib-file "active"))
-         history-hashtb active-hashtb active-changed 
+         history-hashtb active-hashtb active-changed
          history-changed point)
       (gnus-make-directory (file-name-directory active-file))
       (if clean
@@ -1997,12 +2097,12 @@ If CLEAN, don't read existing active and agentview files."
        (goto-char (point-min))
        (forward-line 1)
        (while (not (eobp))
-         (if (looking-at 
+         (if (looking-at
               "\\([^\t\n]+\\)\t[0-9]+\t\\([^ \n]+\\) \\([0-9]+\\)")
              (progn
-               (unless (string= (match-string 1) 
+               (unless (string= (match-string 1)
                                 "last-header-fetched-for-session")
-                 (gnus-sethash (match-string 2) 
+                 (gnus-sethash (match-string 2)
                                (cons (string-to-number (match-string 3))
                                      (gnus-gethash-safe (match-string 2)
                                                         history-hashtb))
@@ -2033,14 +2133,14 @@ If CLEAN, don't read existing active and agentview files."
              n)
          (gnus-sethash group arts history-hashtb)
          (while (and arts gnus-agent-article-alist)
-           (cond 
+           (cond
             ((> (car arts) (caar gnus-agent-article-alist))
              (when (cdar gnus-agent-article-alist)
-               (gnus-agent-regenerate-history 
+               (gnus-agent-regenerate-history
                 group (caar gnus-agent-article-alist))
                (setq history-changed t))
              (setq n (car (pop gnus-agent-article-alist)))
-             (while (and gnus-agent-article-alist 
+             (while (and gnus-agent-article-alist
                          (= n (caar gnus-agent-article-alist)))
                (pop gnus-agent-article-alist)))
             ((< (car arts) (caar gnus-agent-article-alist))
@@ -2049,7 +2149,7 @@ If CLEAN, don't read existing active and agentview files."
                (pop arts)))
             (t
              (setq n (car (pop gnus-agent-article-alist)))
-             (while (and gnus-agent-article-alist 
+             (while (and gnus-agent-article-alist
                          (= n (caar gnus-agent-article-alist)))
                (pop gnus-agent-article-alist))
              (setq n (pop arts))
@@ -2057,18 +2157,18 @@ If CLEAN, don't read existing active and agentview files."
                (pop arts)))))
          (while gnus-agent-article-alist
            (when (cdar gnus-agent-article-alist)
-             (gnus-agent-regenerate-history 
+             (gnus-agent-regenerate-history
               group (caar gnus-agent-article-alist))
              (setq history-changed t))
            (pop gnus-agent-article-alist))))
       (when history-changed
-       (message "Regenerate the history file of %s:%s" 
+       (message "Regenerate the history file of %s:%s"
                 (car gnus-command-method)
                 (cadr gnus-command-method))
        (gnus-agent-save-history))
       (gnus-agent-close-history)
       (when active-changed
-       (message "Regenerate %s" active-file) 
+       (message "Regenerate %s" active-file)
        (let ((nnmail-active-file-coding-system gnus-agent-file-coding-system))
          (gnus-write-active-file active-file active-hashtb)))))
   (message "Regenerating Gnus agent files...done"))
@@ -2078,13 +2178,26 @@ If CLEAN, don't read existing active and agentview files."
   (interactive (list t))
   (dolist (server gnus-opened-servers)
     (when (eq (nth 1 server) 'offline)
-      (if (if (eq force 'ask) 
-             (gnus-y-or-n-p 
+      (if (if (eq force 'ask)
+             (gnus-y-or-n-p
               (format "Switch %s:%s into online status? "
                       (caar server) (cadar server)))
            force)
          (setcar (nthcdr 1 server) 'close)))))
 
+(defun gnus-agent-toggle-group-plugged (group)
+  "Toggle the status of the server of the current group."
+  (interactive (list (gnus-group-group-name)))
+  (let* ((method (gnus-find-method-for-group group))
+        (status (cadr (assoc method gnus-opened-servers))))
+    (if (eq status 'offline)
+       (gnus-server-set-status method 'closed)
+      (gnus-close-server method)
+      (gnus-server-set-status method 'offline))
+    (message "Turn %s:%s from %s to %s." (car method) (cadr method)
+            (if (eq status 'offline) 'offline 'online)
+            (if (eq status 'offline) 'online 'offline))))
+
 (provide 'gnus-agent)
 
 ;;; gnus-agent.el ends here