Go back to grave quoting in Gnus
[gnus] / lisp / gnus-int.el
index df7f979..4f8f17f 100644 (file)
@@ -1,7 +1,6 @@
 ;;; gnus-int.el --- backend interface functions for Gnus
 
-;; Copyright (C) 1996, 1997, 1998, 1999, 2000, 2001, 2002, 2003, 2004
-;;   2005, 2006, 2007, 2008, 2009, 2010 Free Software Foundation, Inc.
+;; Copyright (C) 1996-2015 Free Software Foundation, Inc.
 
 ;; Author: Lars Magne Ingebrigtsen <larsi@gnus.org>
 ;; Keywords: news
@@ -31,6 +30,7 @@
 (require 'message)
 (require 'gnus-range)
 
+(autoload 'gnus-run-hook-with-args "gnus-util")
 (autoload 'gnus-agent-expire "gnus-agent")
 (autoload 'gnus-agent-regenerate-group "gnus-agent")
 (autoload 'gnus-agent-read-servers-validate-native "gnus-agent")
   :group 'gnus-start
   :type 'hook)
 
+(defcustom gnus-after-set-mark-hook nil
+  "Hook called just after marks are set in a group."
+  :version "24.1"
+  :group 'gnus-start
+  :type 'hook)
+
+(defcustom gnus-before-update-mark-hook nil
+  "Hook called just before marks are updated in a group."
+  :version "24.1"
+  :group 'gnus-start
+  :type 'hook)
+
 (defcustom gnus-server-unopen-status nil
   "The default status if the server is not able to open.
 If the server is covered by Gnus agent, the possible values are
@@ -53,6 +65,13 @@ server denied."
                 (const :tag "Deny server" denied)
                 (const :tag "Unplug Agent" offline)))
 
+(defcustom gnus-nntp-server nil
+  "The name of the host running the NNTP server."
+  :group 'gnus-server
+  :type '(choice (const :tag "disable" nil)
+                string))
+(make-obsolete-variable 'gnus-nntp-server 'gnus-select-method "24.1")
+
 (defvar gnus-internal-registry-spool-current-method nil
   "The current method, for the registry.")
 
@@ -89,16 +108,14 @@ If CONFIRM is non-nil, the user will be asked for an NNTP server."
        ;; Stream is already opened.
        nil
       ;; Open NNTP server.
-      (unless gnus-nntp-service
-       (setq gnus-nntp-server nil))
       (when confirm
        ;; Read server name with completion.
        (setq gnus-nntp-server
-             (completing-read "NNTP server: "
-                              (mapcar 'list
-                                      (cons (list gnus-nntp-server)
-                                            gnus-secondary-servers))
-                              nil nil gnus-nntp-server)))
+             (gnus-completing-read "NNTP server"
+                                    (cons gnus-nntp-server
+                                         (if (boundp 'gnus-secondary-servers)
+                                             gnus-secondary-servers))
+                                    nil gnus-nntp-server)))
 
       (when (and gnus-nntp-server
                 (stringp gnus-nntp-server)
@@ -147,8 +164,8 @@ If CONFIRM is non-nil, the user will be asked for an NNTP server."
        (gnus-open-server gnus-select-method)
        gnus-batch-mode
        (gnus-y-or-n-p
-       (format
-        "%s (%s) open error: '%s'.  Continue? "
+       (gnus-format-message
+        "%s (%s) open error: `%s'.  Continue? "
         (car gnus-select-method) (cadr gnus-select-method)
         (gnus-status-message gnus-select-method)))
        (gnus-error 1 "Couldn't open server on %s"
@@ -181,10 +198,15 @@ If it is down, start it up (again)."
       (prog1
          (setq result (gnus-open-server method))
        (unless silent
-         (gnus-message 5 "Opening %s server%s...%s" (car method)
-                       (if (equal (nth 1 method) "") ""
-                         (format " on %s" (nth 1 method)))
-                       (if result "done" "failed")))))))
+         (gnus-message
+          (if result 5 3)
+          "Opening %s server%s...%s" (car method)
+          (if (equal (nth 1 method) "") ""
+            (format " on %s" (nth 1 method)))
+          (if result
+              "done"
+            (format "failed: %s"
+                    (nnheader-get-report-string (car method))))))))))
 
 (defun gnus-get-function (method function &optional noerror)
   "Return a function symbol based on METHOD and FUNCTION."
@@ -226,75 +248,83 @@ If it is down, start it up (again)."
   (eq (nth 1 (assoc method gnus-opened-servers))
       'denied))
 
-(defvar gnus-backend-trace t)
+(defvar gnus-backend-trace nil)
+(defvar gnus-backend-trace-elapsed nil)
 
-(defun gnus-open-server (gnus-command-method)
-  "Open a connection to GNUS-COMMAND-METHOD."
-  (when (stringp gnus-command-method)
-    (setq gnus-command-method (gnus-server-to-method gnus-command-method)))
+(defun gnus-backend-trace (type form)
   (when gnus-backend-trace
     (with-current-buffer (get-buffer-create "*gnus trace*")
       (buffer-disable-undo)
       (goto-char (point-max))
       (insert (format-time-string "%H:%M:%S")
-             (format " %S\n" gnus-command-method))))
+             (format " %.2fs %s %S\n"
+                     (if (numberp gnus-backend-trace-elapsed)
+                         (- (float-time) gnus-backend-trace-elapsed)
+                       0)
+                     type form))
+      (setq gnus-backend-trace-elapsed (float-time)))))
+
+(defun gnus-open-server (gnus-command-method)
+  "Open a connection to GNUS-COMMAND-METHOD."
+  (when (stringp gnus-command-method)
+    (setq gnus-command-method (gnus-server-to-method gnus-command-method)))
+  (gnus-backend-trace :opening gnus-command-method)
   (let ((elem (assoc gnus-command-method gnus-opened-servers))
        (server (gnus-method-to-server-name gnus-command-method)))
     ;; If this method was previously denied, we just return nil.
     (if (eq (nth 1 elem) 'denied)
        (progn
-         (gnus-message 1 "Denied server %s" server)
+         (gnus-message
+          1 "Server %s previously determined to be down; not retrying" server)
          nil)
       ;; Open the server.
-      (let* ((open-server-function (gnus-get-function gnus-command-method 'open-server))
+      (let* ((open-server-function
+             (gnus-get-function gnus-command-method 'open-server))
              (result
-             (condition-case err
-                 (funcall open-server-function
-                          (nth 1 gnus-command-method)
-                          (nthcdr 2 gnus-command-method))
-               (error
-                (gnus-message 1 "Unable to open server %s due to: %s"
-                             server (error-message-string err))
-                nil)
-               (quit
-                (gnus-message 1 "Quit trying to open server %s" server)
-                nil)))
-            open-offline)
+             (condition-case err
+                 (funcall open-server-function
+                          (nth 1 gnus-command-method)
+                          (nthcdr 2 gnus-command-method))
+               (error
+                (gnus-message 1 "Unable to open server %s due to: %s"
+                              server (error-message-string err))
+                nil)
+               (quit
+                (if debug-on-quit
+                    (debug "Quit")
+                  (gnus-message 1 "Quit trying to open server %s" server))
+                nil)))
+            open-offline)
        ;; If this hasn't been opened before, we add it to the list.
        (unless elem
          (setq elem (list gnus-command-method nil)
                gnus-opened-servers (cons elem gnus-opened-servers)))
        ;; Set the status of this server.
-        (setcar (cdr elem)
-                (cond (result
-                       (if (eq open-server-function #'nnagent-open-server)
-                           ;; The agent's backend has a "special" status
-                           'offline
-                         'ok))
-                      ((and gnus-agent
-                            (gnus-agent-method-p gnus-command-method))
-                       (cond (gnus-server-unopen-status
-                              ;; Set the server's status to the unopen
-                              ;; status.  If that status is offline,
-                              ;; recurse to open the agent's backend.
-                              (setq open-offline (eq gnus-server-unopen-status 'offline))
-                              gnus-server-unopen-status)
-                             ((and
-                              (not gnus-batch-mode)
-                              (gnus-y-or-n-p
-                               (format
-                                "Unable to open server %s (%s), go offline? "
-                                server
-                                (nnheader-get-report
-                                 (car gnus-command-method)))))
-                              (setq open-offline t)
-                              'offline)
-                             (t
-                              ;; This agentized server was still denied
-                              'denied)))
-                      (t
-                       ;; This unagentized server must be denied
-                       'denied)))
+        (setcar
+        (cdr elem)
+        (cond (result
+               (if (eq open-server-function 'nnagent-open-server)
+                   ;; The agent's backend has a "special" status
+                   'offline
+                 'ok))
+              ((and gnus-agent
+                    (gnus-agent-method-p gnus-command-method))
+               (cond
+                (gnus-server-unopen-status
+                 ;; Set the server's status to the unopen
+                 ;; status.  If that status is offline,
+                 ;; recurse to open the agent's backend.
+                 (setq open-offline (eq gnus-server-unopen-status 'offline))
+                 gnus-server-unopen-status)
+                ((not gnus-batch-mode)
+                 (setq open-offline t)
+                 'offline)
+                (t
+                 ;; This agentized server was still denied
+                 'denied)))
+              (t
+               ;; This unagentized server must be denied
+               'denied)))
 
         ;; NOTE: I MUST set the server's status to offline before this
         ;; recursive call as this status will drive the
@@ -313,6 +343,7 @@ If it is down, start it up (again)."
            (save-excursion
              (gnus-agent-possibly-synchronize-flags-server
               gnus-command-method)))
+         (gnus-backend-trace :opened gnus-command-method)
           result)))))
 
 (defun gnus-close-server (gnus-command-method)
@@ -333,12 +364,16 @@ If it is down, start it up (again)."
   "Read and update infos from GNUS-COMMAND-METHOD."
   (when (stringp gnus-command-method)
     (setq gnus-command-method (gnus-server-to-method gnus-command-method)))
-  (funcall (gnus-get-function gnus-command-method 'finish-retrieve-group-infos)
-          (nth 1 gnus-command-method)
-          infos data))
+  (gnus-backend-trace :finishing gnus-command-method)
+  (prog1
+      (funcall (gnus-get-function gnus-command-method
+                                 'finish-retrieve-group-infos)
+              (nth 1 gnus-command-method)
+              infos data)
+    (gnus-backend-trace :finished gnus-command-method)))
 
 (defun gnus-retrieve-group-data-early (gnus-command-method infos)
-  "Start early async retrival of data from GNUS-COMMAND-METHOD."
+  "Start early async retrieval of data from GNUS-COMMAND-METHOD."
   (when (stringp gnus-command-method)
     (setq gnus-command-method (gnus-server-to-method gnus-command-method)))
   (funcall (gnus-get-function gnus-command-method 'retrieve-group-data-early)
@@ -378,7 +413,7 @@ If it is down, start it up (again)."
     result))
 
 (defun gnus-request-compact (gnus-command-method)
-  "Request groups compaction  from GNUS-COMMAND-METHOD."
+  "Request groups compaction from GNUS-COMMAND-METHOD."
   (when (stringp gnus-command-method)
     (setq gnus-command-method (gnus-server-to-method gnus-command-method)))
   (funcall (gnus-get-function gnus-command-method 'request-compact)
@@ -396,14 +431,6 @@ If it is down, start it up (again)."
             dont-check
             info)))
 
-(defun gnus-list-active-group (group)
-  "Request active information on GROUP."
-  (let ((gnus-command-method (gnus-find-method-for-group group))
-       (func 'list-active-group))
-    (when (gnus-check-backend-function func group)
-      (funcall (gnus-get-function gnus-command-method func)
-              (gnus-group-real-name group) (nth 1 gnus-command-method)))))
-
 (defun gnus-request-group-description (group)
   "Request a description of GROUP."
   (let ((gnus-command-method (gnus-find-method-for-group group))
@@ -412,13 +439,13 @@ If it is down, start it up (again)."
       (funcall (gnus-get-function gnus-command-method func)
               (gnus-group-real-name group) (nth 1 gnus-command-method)))))
 
-(defun gnus-request-group-articles (group)
-  "Request a list of existing articles in GROUP."
+(defun gnus-request-group-scan (group info)
+  "Request that GROUP get a complete rescan."
   (let ((gnus-command-method (gnus-find-method-for-group group))
-       (func 'request-group-articles))
+       (func 'request-group-scan))
     (when (gnus-check-backend-function func group)
       (funcall (gnus-get-function gnus-command-method func)
-              (gnus-group-real-name group) (nth 1 gnus-command-method)))))
+              (gnus-group-real-name group) (nth 1 gnus-command-method) info))))
 
 (defun gnus-close-group (group)
   "Request the GROUP be closed."
@@ -464,6 +491,18 @@ If FETCH-OLD, retrieve all headers (or some subset thereof) in the group."
       (funcall (gnus-get-function gnus-command-method 'request-type)
               (gnus-group-real-name group) article))))
 
+(defun gnus-request-update-group-status (group status)
+  "Change the status of a group.
+Valid statuses include `subscribe' and `unsubscribe'."
+  (let ((gnus-command-method (gnus-find-method-for-group group)))
+    (if (not (gnus-check-backend-function
+             'request-update-group-status (car gnus-command-method)))
+       nil
+      (funcall
+       (gnus-get-function gnus-command-method 'request-update-group-status)
+       (gnus-group-real-name group) status
+       (nth 1 gnus-command-method)))))
+
 (defun gnus-request-set-mark (group action)
   "Set marks on articles in the back end."
   (let ((gnus-command-method (gnus-find-method-for-group group)))
@@ -472,7 +511,8 @@ If FETCH-OLD, retrieve all headers (or some subset thereof) in the group."
        action
       (funcall (gnus-get-function gnus-command-method 'request-set-mark)
               (gnus-group-real-name group) action
-              (nth 1 gnus-command-method)))))
+              (nth 1 gnus-command-method))
+      (gnus-run-hook-with-args gnus-after-set-mark-hook group action))))
 
 (defun gnus-request-update-mark (group article mark)
   "Allow the back end to change the mark the user tries to put on an article."
@@ -480,6 +520,7 @@ If FETCH-OLD, retrieve all headers (or some subset thereof) in the group."
     (if (not (gnus-check-backend-function
              'request-update-mark (car gnus-command-method)))
        mark
+      (gnus-run-hook-with-args gnus-before-update-mark-hook group article mark)
       (funcall (gnus-get-function gnus-command-method 'request-update-mark)
               (gnus-group-real-name group) article mark))))
 
@@ -492,6 +533,77 @@ If BUFFER, insert the article in that group."
             article (gnus-group-real-name group)
             (nth 1 gnus-command-method) buffer)))
 
+(defun gnus-request-thread (header group)
+  "Request the headers in the thread containing the article specified by HEADER."
+  (let ((gnus-command-method (gnus-find-method-for-group group)))
+    (funcall (gnus-get-function gnus-command-method 'request-thread)
+            header
+            (gnus-group-real-name group))))
+
+(defun gnus-select-group-with-message-id (group message-id)
+  "Activate and select GROUP with the given MESSAGE-ID selected.
+Returns the article number of the message.
+
+If GROUP is not already selected, the message will be the only one in
+the group's summary.
+"
+  ;; TODO: is there a way to know at this point whether the group will
+  ;; be newly-selected?  If so we could clean up the logic at the end
+  ;;
+  ;; save the new group's display parameter, if any, so we
+  ;; can replace it temporarily with zero.
+  (let ((saved-display
+         (gnus-group-get-parameter group 'display :allow-list)))
+
+    ;; Tell gnus we really don't want any articles
+    (gnus-group-set-parameter group 'display 0)
+
+    (unwind-protect
+        (gnus-summary-read-group-1
+         group (not :show-all) :no-article (not :kill-buffer)
+         ;; The combination of no-display and this dummy list of
+         ;; articles to select somehow makes it possible to open a
+         ;; group with no articles in it.  Black magic.
+         :no-display '(-1); select-articles
+         )
+      ;; Restore the new group's display parameter
+      (gnus-group-set-parameter group 'display saved-display)))
+
+  ;; The summary buffer was suppressed by :no-display above.
+  ;; Create it now and insert the message
+  (let ((group-is-new (gnus-summary-setup-buffer group)))
+    (condition-case err
+        (let ((article-number
+               (gnus-summary-insert-subject message-id)))
+          (unless article-number
+            (signal 'error "message-id not in group"))
+          (gnus-summary-select-article nil nil nil article-number)
+          article-number)
+      ;; Clean up the new summary and propagate the error
+      (error (when group-is-new (gnus-summary-exit))
+             (apply 'signal err)))))
+
+(defun gnus-simplify-group-name (group)
+  "Return the simplest representation of the name of GROUP.
+This is the string that Gnus uses to identify the group."
+  (gnus-group-prefixed-name
+   (gnus-group-real-name group)
+   (gnus-group-method group)))
+
+(defun gnus-warp-to-article ()
+  "Look up the current article in the group where it originated.
+This command only makes sense for groups shows articles gathered
+from other groups -- for instance, search results and the like."
+  (interactive)
+  (let ((gnus-command-method
+         (gnus-find-method-for-group gnus-newsgroup-name)))
+    (or
+     (when (gnus-check-backend-function
+            'warp-to-article (car gnus-command-method))
+       (funcall (gnus-get-function gnus-command-method 'warp-to-article)))
+     (and (bound-and-true-p gnus-registry-enabled)
+          (gnus-try-warping-via-registry)))))
+
 (defun gnus-request-head (article group)
   "Request the head of ARTICLE in GROUP."
   (let* ((gnus-command-method (gnus-find-method-for-group group))
@@ -510,7 +622,8 @@ If BUFFER, insert the article in that group."
            clean-up t))
      ;; Use `head' function.
      ((fboundp head)
-      (setq res (funcall head article (gnus-group-real-name group)
+      (setq res (funcall head article
+                         (and (not gnus-override-method) (gnus-group-real-name group))
                         (nth 1 gnus-command-method))))
      ;; Use `article' function.
      (t
@@ -584,15 +697,23 @@ If GROUP is nil, all groups on GNUS-COMMAND-METHOD are scanned."
               (and group (gnus-group-real-name group))
               (nth 1 gnus-command-method)))))
 
-(defsubst gnus-request-update-info (info gnus-command-method)
+(defun gnus-request-update-info (info gnus-command-method)
+  (when (gnus-check-backend-function
+        'request-update-info (car gnus-command-method))
+    (when (stringp gnus-command-method)
+      (setq gnus-command-method (gnus-server-to-method gnus-command-method)))
+    (funcall (gnus-get-function gnus-command-method 'request-update-info)
+            (gnus-group-real-name (gnus-info-group info)) info
+            (nth 1 gnus-command-method))))
+
+(defsubst gnus-request-marks (info gnus-command-method)
   "Request that GNUS-COMMAND-METHOD update INFO."
   (when (stringp gnus-command-method)
     (setq gnus-command-method (gnus-server-to-method gnus-command-method)))
   (when (gnus-check-backend-function
-        'request-update-info (car gnus-command-method))
+        'request-marks (car gnus-command-method))
     (let ((group (gnus-info-group info)))
-      (and (funcall (gnus-get-function gnus-command-method
-                                      'request-update-info)
+      (and (funcall (gnus-get-function gnus-command-method 'request-marks)
                    (gnus-group-real-name group)
                    info (nth 1 gnus-command-method))
           ;; If the minimum article number is greater than 1, then all
@@ -609,6 +730,10 @@ If GROUP is nil, all groups on GNUS-COMMAND-METHOD are scanned."
 
 (defun gnus-request-expire-articles (articles group &optional force)
   (let* ((gnus-command-method (gnus-find-method-for-group group))
+         ;; Filter out any negative article numbers; they can't be
+         ;; expired here.
+         (articles
+          (delq nil (mapcar (lambda (n) (and (>= n 0) n)) articles)))
         (gnus-inhibit-demon t)
         (not-deleted
          (funcall
@@ -628,7 +753,8 @@ If GROUP is nil, all groups on GNUS-COMMAND-METHOD are scanned."
         (result (funcall (gnus-get-function gnus-command-method
                                             'request-move-article)
                          article (gnus-group-real-name group)
-                         (nth 1 gnus-command-method) accept-function last move-is-internal)))
+                         (nth 1 gnus-command-method) accept-function
+                         last move-is-internal)))
     (when (and result gnus-agent
               (gnus-agent-method-p gnus-command-method))
       (gnus-agent-unfetch-articles group (list article)))
@@ -636,7 +762,6 @@ If GROUP is nil, all groups on GNUS-COMMAND-METHOD are scanned."
 
 (defun gnus-request-accept-article (group &optional gnus-command-method last
                                          no-encode)
-  ;; Make sure there's a newline at the end of the article.
   (when (stringp gnus-command-method)
     (setq gnus-command-method (gnus-server-to-method gnus-command-method)))
   (when (and (not gnus-command-method)
@@ -644,6 +769,7 @@ If GROUP is nil, all groups on GNUS-COMMAND-METHOD are scanned."
     (setq gnus-command-method (or (gnus-find-method-for-group group)
                                   (gnus-group-name-to-method group))))
   (goto-char (point-max))
+  ;; Make sure there's a newline at the end of the article.
   (unless (bolp)
     (insert "\n"))
   (unless no-encode
@@ -662,7 +788,9 @@ If GROUP is nil, all groups on GNUS-COMMAND-METHOD are scanned."
          (if (stringp group) (gnus-group-real-name group) group)
          (cadr gnus-command-method)
          last)))
-    (when (and gnus-agent (gnus-agent-method-p gnus-command-method))
+    (when (and gnus-agent
+              (gnus-agent-method-p gnus-command-method)
+              (cdr result))
       (gnus-agent-regenerate-group group (list (cdr result))))
     result))
 
@@ -682,11 +810,6 @@ If GROUP is nil, all groups on GNUS-COMMAND-METHOD are scanned."
       (gnus-agent-regenerate-group group (list article)))
     result))
 
-(defun gnus-request-associate-buffer (group)
-  (let ((gnus-command-method (gnus-find-method-for-group group)))
-    (funcall (gnus-get-function gnus-command-method 'request-associate-buffer)
-            (gnus-group-real-name group))))
-
 (defun gnus-request-restore-buffer (article group)
   "Request a new buffer restored to the state of ARTICLE."
   (let ((gnus-command-method (gnus-find-method-for-group group)))