;;; nntp.el --- nntp access for Gnus
-;; Copyright (C) 1987-1990, 1992-1998, 2000-2011
-;; Free Software Foundation, Inc.
+;; Copyright (C) 1987-1990, 1992-1998, 2000-2014 Free Software
+;; Foundation, Inc.
;; Author: Lars Magne Ingebrigtsen <larsi@gnus.org>
;; Keywords: news
server there that you can connect to. See also
`nntp-open-connection-function'")
-(defvoo nntp-coding-system-for-read 'binary
- "*Coding system to read from NNTP.")
-
-(defvoo nntp-coding-system-for-write 'binary
- "*Coding system to write to NNTP.")
-
-;; Marks
-(defvoo nntp-marks-is-evil nil
- "*If non-nil, Gnus will never generate and use marks file for nntp groups.
-See `nnml-marks-is-evil' for more information.")
-
-(defvoo nntp-marks-file-name ".marks")
-(defvoo nntp-marks nil)
-(defvar nntp-marks-modtime (gnus-make-hashtable))
-
-(defcustom nntp-marks-directory
- (nnheader-concat gnus-directory "marks/")
- "*The directory where marks for nntp groups will be stored."
- :group 'nntp
- :type 'directory)
-
(defcustom nntp-authinfo-file "~/.authinfo"
".netrc-like file that holds nntp authinfo passwords."
:group 'nntp
;;; Internal variables.
+(defvoo nntp-retrieval-in-progress nil)
(defvar nntp-record-commands nil
"*If non-nil, nntp will record all commands in the \"*nntp-log*\" buffer.")
(insert (format-time-string "%Y%m%dT%H%M%S.%3N")
" " nntp-address " " string "\n")))
+(defvar nntp--report-1 nil)
+
(defun nntp-report (&rest args)
"Report an error from the nntp backend. The first string in ARGS
can be a format string. For some commands, the failed command may be
retried once before actually displaying the error report."
+ (if nntp--report-1
+ (progn
+ ;; Throw out to nntp-with-open-group-error so that the connection may
+ ;; be restored and the command retried."
+ (when nntp-record-commands
+ (nntp-record-command "*** CONNECTION LOST ***"))
+ (throw 'nntp-with-open-group-error t))
- (when nntp-record-commands
- (nntp-record-command "*** CALLED nntp-report ***"))
-
- (nnheader-report 'nntp args)
-
- (apply 'error args))
+ (when nntp-record-commands
+ (nntp-record-command "*** CALLED nntp-report ***"))
-(defun nntp-report-1 (&rest args)
- "Throws out to nntp-with-open-group-error so that the connection may
-be restored and the command retried."
+ (nnheader-report 'nntp args)
- (when nntp-record-commands
- (nntp-record-command "*** CONNECTION LOST ***"))
-
- (throw 'nntp-with-open-group-error t))
+ (apply 'error args)))
(defmacro nntp-copy-to-buffer (buffer start end)
"Copy string from unibyte current buffer to multibyte buffer."
(t
nil)))
-(eval-when-compile
- (defvar nntp-with-open-group-internal nil)
- (defvar nntp-report-n nil))
-
(defun nntp-with-open-group-function (-group -server -connectionless -bodyfun)
"Protect against servers that don't like clients that keep idle connections opens.
The problem being that these servers may either close a connection or
`nntp-connection-timeout' has expired. When these occur
`nntp-with-open-group', opens a new connection then re-issues the NNTP
command whose response triggered the error."
- (letf ((nntp-report-n (symbol-function 'nntp-report))
- ((symbol-function 'nntp-report) (symbol-function 'nntp-report-1))
- (nntp-with-open-group-internal nil))
+ (let ((nntp-report-n nntp--report-1)
+ (nntp--report-1 t)
+ (nntp-with-open-group-internal nil))
(while (catch 'nntp-with-open-group-error
;; Open the connection to the server
;; NOTE: Existing connections are NOT tested.
(process-buffer -process))))
;; When I an able to identify the
;; connection to the server AND I've
- ;; received NO reponse for
+ ;; received NO response for
;; nntp-connection-timeout seconds.
(when (and -buffer (eq 0 (buffer-size -buffer)))
;; Close the connection. Take no
(when -timer
(nnheader-cancel-timer -timer)))
nil))
- (setf (symbol-function 'nntp-report) nntp-report-n))
+ (setq nntp--report-1 nntp-report-n))
nntp-with-open-group-internal))
(defmacro nntp-with-open-group (group server &optional connectionless &rest forms)
(deffoo nntp-retrieve-group-data-early (server infos)
"Retrieve group info on INFOS."
(nntp-with-open-group nil server
- (when (nntp-find-connection-buffer nntp-server-buffer)
- ;; The first time this is run, this variable is `try'. So we
- ;; try.
- (when (eq nntp-server-list-active-group 'try)
- (nntp-try-list-active
- (gnus-group-real-name (gnus-info-group (car infos)))))
- (with-current-buffer (nntp-find-connection-buffer nntp-server-buffer)
- (erase-buffer)
- (let ((nntp-inhibit-erase t)
- (command (if nntp-server-list-active-group
- "LIST ACTIVE" "GROUP")))
- (dolist (info infos)
- (nntp-send-command
- nil command (gnus-group-real-name (gnus-info-group info)))))
- (length infos)))))
+ (let ((buffer (nntp-find-connection-buffer nntp-server-buffer)))
+ (unless infos
+ (with-current-buffer buffer
+ (setq nntp-retrieval-in-progress nil)))
+ (when (and buffer
+ infos
+ (with-current-buffer buffer
+ (not nntp-retrieval-in-progress)))
+ ;; The first time this is run, this variable is `try'. So we
+ ;; try.
+ (when (eq nntp-server-list-active-group 'try)
+ (nntp-try-list-active
+ (gnus-group-real-name (gnus-info-group (car infos)))))
+ (with-current-buffer buffer
+ (erase-buffer)
+ ;; Mark this buffer as "in use" in case we try to issue two
+ ;; retrievals from the same server. This shouldn't happen,
+ ;; so this is mostly a sanity check.
+ (setq nntp-retrieval-in-progress t)
+ (let ((nntp-inhibit-erase t)
+ (command (if nntp-server-list-active-group
+ "LIST ACTIVE" "GROUP")))
+ (dolist (info infos)
+ (nntp-send-command
+ nil command (gnus-group-real-name (gnus-info-group info)))))
+ (length infos))))))
(deffoo nntp-finish-retrieve-group-infos (server infos count)
(nntp-with-open-group nil server
(car infos)))
(received 0)
(last-point 1))
+ (with-current-buffer buf
+ (setq nntp-retrieval-in-progress nil))
(when (and buf
count)
(with-current-buffer buf
(progn
(nntp-copy-to-buffer nntp-server-buffer
(point-min) (point-max))
- (gnus-groups-to-gnus-format method gnus-active-hashtb t))
+ (with-current-buffer nntp-server-buffer
+ (gnus-groups-to-gnus-format method gnus-active-hashtb t)))
;; We have read active entries, so we just delete the
;; superfluous gunk.
(goto-char (point-min))
"Retrieve group info on GROUPS."
(nntp-with-open-group
nil server
- (when (nntp-find-connection-buffer nntp-server-buffer)
+ (when (and (nntp-find-connection-buffer nntp-server-buffer)
+ (with-current-buffer
+ (nntp-find-connection-buffer nntp-server-buffer)
+ (if (not nntp-retrieval-in-progress)
+ t
+ (message "Warning: Refusing to do retrieval from %s because a retrieval is already happening"
+ server)
+ nil)))
(catch 'done
(save-excursion
;; Erase nntp-server-buffer before nntp-inhibit-erase.
(deffoo nntp-asynchronous-p ()
t)
-(deffoo nntp-request-set-mark (group actions &optional server)
- (when (and (not nntp-marks-is-evil)
- nntp-marks-file-name)
- (nntp-possibly-create-directory group server)
- (nntp-open-marks group server)
- (setq nntp-marks (nnheader-update-marks-actions nntp-marks actions))
- (nntp-save-marks group server))
- nil)
-
-(deffoo nntp-request-marks (group info &optional server)
- (when (and (not nntp-marks-is-evil)
- nntp-marks-file-name)
- (nntp-possibly-create-directory group server)
- (when (nntp-marks-changed-p group server)
- (nnheader-message 8 "Updating marks for %s..." group)
- (nntp-open-marks group server)
- ;; Update info using `nntp-marks'.
- (mapc (lambda (pred)
- (unless (memq (cdr pred) gnus-article-unpropagated-mark-lists)
- (gnus-info-set-marks
- info
- (gnus-update-alist-soft
- (cdr pred)
- (cdr (assq (cdr pred) nntp-marks))
- (gnus-info-marks info))
- t)))
- gnus-article-mark-lists)
- (let ((seen (cdr (assq 'read nntp-marks))))
- (gnus-info-set-read info
- (if (and (integerp (car seen))
- (null (cdr seen)))
- (list (cons (car seen) (car seen)))
- seen)))
- (nnheader-message 8 "Updating marks for %s...done" group)))
- nil)
-
-
;;; Hooky functions.
(let* ((list (netrc-parse nntp-authinfo-file))
(alist (netrc-machine list nntp-address "nntp"))
(auth-info
- (nth 0 (auth-source-search :max 1
- ;; TODO: allow the virtual server name too
- :host nntp-address
- :port '("119" "nntp"))))
+ (nth 0 (auth-source-search
+ :max 1
+ :host (list nntp-address (nnoo-current-server 'nntp))
+ :port `("119" "nntp" ,(format "%s" nntp-port-number)
+ "563" "nntps" "snews"))))
(auth-user (plist-get auth-info :user))
(auth-force (plist-get auth-info :force))
(auth-passwd (plist-get auth-info :secret))
(read-passwd (format "NNTP (%s@%s) password: "
user nntp-address))))))))))
-(defun nntp-send-nosy-authinfo ()
- "Send the AUTHINFO to the nntp server."
- (let ((user (read-string (format "NNTP (%s) user name: " nntp-address))))
- (unless (member user '(nil ""))
- (nntp-send-command "^3.*\r?\n" "AUTHINFO USER" user)
- (when t ;???Should check if AUTHINFO succeeded
- (nntp-send-command "^2.*\r?\n" "AUTHINFO PASS"
- (read-passwd (format "NNTP (%s@%s) password: "
- user nntp-address)))))))
-
-(defun nntp-send-authinfo-from-file ()
- "Send the AUTHINFO to the nntp server.
-
-The authinfo login name is taken from the user's login name and the
-password contained in '~/.nntp-authinfo'."
- (when (file-exists-p "~/.nntp-authinfo")
- (with-temp-buffer
- (insert-file-contents "~/.nntp-authinfo")
- (goto-char (point-min))
- (nntp-send-command "^3.*\r?\n" "AUTHINFO USER" (user-login-name))
- (nntp-send-command
- "^2.*\r?\n" "AUTHINFO PASS"
- (buffer-substring (point) (point-at-eol))))))
-
;;; Internal functions.
(defun nntp-handle-authinfo (process)
(set (make-local-variable 'nntp-process-to-buffer) nil)
(set (make-local-variable 'nntp-process-start-point) nil)
(set (make-local-variable 'nntp-process-decode) nil)
+ (set (make-local-variable 'nntp-retrieval-in-progress) nil)
(current-buffer)))
(defun nntp-open-connection (buffer)
(nntp-kill-buffer ,pbuffer)))))
(process
(condition-case err
- (let ((coding-system-for-read nntp-coding-system-for-read)
- (coding-system-for-write nntp-coding-system-for-write)
+ (let ((coding-system-for-read 'binary)
+ (coding-system-for-write 'binary)
(map '((nntp-open-network-stream network)
(network-only plain) ; compat
(nntp-open-plain-stream plain)
"nntpd" pbuffer nntp-address nntp-port-number
:type (cadr (assoc nntp-open-connection-function map))
:end-of-command "^\\([2345]\\|[.]\\).*\n"
- :capability-command "CAPABILITIES\r\n"
+ :capability-command "HELP\r\n"
:success "^3"
:starttls-function
(lambda (capabilities)
(nnheader-cancel-timer timer))
(when (and process
(not (memq (process-status process) '(open run))))
+ (with-current-buffer pbuffer
+ (goto-char (point-min))
+ (nnheader-report 'nntp "Error when connecting: %s"
+ (buffer-substring (point) (line-end-position))))
(setq process nil))
(unless process
(nntp-kill-buffer pbuffer))
nntp-process-start-point (point-max))
(setq after-change-functions '(nntp-after-change-function))))
-(defun nntp-async-timer-handler ()
- (mapcar
- (lambda (proc)
- (if (memq (process-status proc) '(open run))
- (nntp-async-trigger proc)
- (nntp-async-stop proc)))
- nntp-async-process-list))
-
(defun nntp-async-stop (proc)
(setq nntp-async-process-list (delq proc nntp-async-process-list))
(when (and nntp-async-timer (not nntp-async-process-list))
;; for the first available article. Obviously, a client can
;; use that entry to avoid making unnecessary requests. The
;; only problem is for a client that assumes that the response
- ;; will always be within the requested ranage. For such a
+ ;; will always be within the requested range. For such a
;; client, we can get N copies of the same entry (one for each
;; XOVER command sent to the server).
(delete-region (point) (point-max)))
proc)))
-;; Marks handling
-
-(defun nntp-marks-directory (server)
- (expand-file-name server nntp-marks-directory))
-
-(defvar nntp-server-to-method-cache nil
- "Alist of servers and select methods.")
-
-(defun nntp-group-pathname (server group &optional file)
- "Return an absolute file name of FILE for GROUP on SERVER."
- (let ((method (cdr (assoc server nntp-server-to-method-cache))))
- (unless method
- (push (cons server (setq method (or (gnus-server-to-method server)
- (gnus-find-method-for-group group))))
- nntp-server-to-method-cache))
- (nnmail-group-pathname
- (mm-decode-coding-string group
- (inline (gnus-group-name-charset method group)))
- (nntp-marks-directory server)
- file)))
-
-(defun nntp-possibly-create-directory (group server)
- (let ((dir (nntp-group-pathname server group))
- (file-name-coding-system nnmail-pathname-coding-system))
- (unless (file-exists-p dir)
- (make-directory (directory-file-name dir) t)
- (nnheader-message 5 "Creating nntp marks directory %s" dir))))
-
-(autoload 'time-less-p "time-date")
-
-(defun nntp-marks-changed-p (group server)
- (let ((file (nntp-group-pathname server group nntp-marks-file-name))
- (file-name-coding-system nnmail-pathname-coding-system))
- (if (null (gnus-gethash file nntp-marks-modtime))
- t ;; never looked at marks file, assume it has changed
- (time-less-p (gnus-gethash file nntp-marks-modtime)
- (nth 5 (file-attributes file))))))
-
-(defun nntp-save-marks (group server)
- (let ((file-name-coding-system nnmail-pathname-coding-system)
- (file (nntp-group-pathname server group nntp-marks-file-name)))
- (condition-case err
- (progn
- (nntp-possibly-create-directory group server)
- (with-temp-file file
- (erase-buffer)
- (gnus-prin1 nntp-marks)
- (insert "\n"))
- (gnus-sethash file
- (nth 5 (file-attributes file))
- nntp-marks-modtime))
- (error (or (gnus-yes-or-no-p
- (format "Could not write to %s (%s). Continue? " file err))
- (error "Cannot write to %s (%s)" file err))))))
-
-(defun nntp-open-marks (group server)
- (let ((file (nntp-group-pathname server group nntp-marks-file-name))
- (file-name-coding-system nnmail-pathname-coding-system))
- (if (file-exists-p file)
- (condition-case err
- (with-temp-buffer
- (gnus-sethash file (nth 5 (file-attributes file))
- nntp-marks-modtime)
- (nnheader-insert-file-contents file)
- (setq nntp-marks (read (current-buffer)))
- (dolist (el gnus-article-unpropagated-mark-lists)
- (setq nntp-marks (gnus-remassoc el nntp-marks))))
- (error (or (gnus-yes-or-no-p
- (format "Error reading nntp marks file %s (%s). Continuing will use marks from .newsrc.eld. Continue? " file err))
- (error "Cannot read nntp marks file %s (%s)" file err))))
- ;; User didn't have a .marks file. Probably first time
- ;; user of the .marks stuff. Bootstrap it from .newsrc.eld.
- (let ((info (gnus-get-info
- (gnus-group-prefixed-name
- group
- (gnus-server-to-method (format "nntp:%s" server)))))
- (decoded-name (mm-decode-coding-string
- group
- (gnus-group-name-charset
- (gnus-server-to-method server) group))))
- (nnheader-message 7 "Bootstrapping marks for %s..." decoded-name)
- (setq nntp-marks (gnus-info-marks info))
- (push (cons 'read (gnus-info-read info)) nntp-marks)
- (dolist (el gnus-article-unpropagated-mark-lists)
- (setq nntp-marks (gnus-remassoc el nntp-marks)))
- (nntp-save-marks group server)
- (nnheader-message 7 "Bootstrapping marks for %s...done"
- decoded-name)))))
-
(provide 'nntp)
;;; nntp.el ends here