When braiding heads, don't use the same article number for all the cached articles.
[gnus] / lisp / nnimap.el
index 16eb594..7f88685 100644 (file)
@@ -51,7 +51,7 @@ it will default to `imap'.")
 
 (defvoo nnimap-stream 'ssl
   "How nnimap will talk to the IMAP server.
-Values are `ssl' and `network'.")
+Values are `ssl', `network', `starttls' or `shell'.")
 
 (defvoo nnimap-shell-program (if (boundp 'imap-shell-program)
                                 (if (listp imap-shell-program)
@@ -90,8 +90,12 @@ not done by default on servers that doesn't support that command.")
 (defvar nnimap-split-download-body-default nil
   "Internal variable with default value for `nnimap-split-download-body'.")
 
+(defvar nnimap-keepalive-timer nil)
+(defvar nnimap-process-buffers nil)
+
 (defstruct nnimap
-  group process commands capabilities select-result newlinep server)
+  group process commands capabilities select-result newlinep server
+  last-command-time)
 
 (defvar nnimap-object nil)
 
@@ -135,19 +139,26 @@ not done by default on servers that doesn't support that command.")
        (nnimap-transform-headers))
       (insert-buffer-substring
        (nnimap-find-process-buffer (current-buffer))))
-    t))
+    'headers))
 
 (defun nnimap-transform-headers ()
   (goto-char (point-min))
-  (let (article bytes lines size)
+  (let (article bytes lines size string)
     (block nil
       (while (not (eobp))
        (while (not (looking-at "^\\* [0-9]+ FETCH.*UID \\([0-9]+\\)"))
          (delete-region (point) (progn (forward-line 1) (point)))
          (when (eobp)
            (return)))
-       (setq article (match-string 1)
-             bytes (nnimap-get-length)
+       (setq article (match-string 1))
+       ;; Unfold quoted {number} strings.
+       (while (re-search-forward "[^]] {\\([0-9]+\\)}\r\n"
+                                 (1+ (line-end-position)) t)
+         (setq size (string-to-number (match-string 1)))
+         (delete-region (+ (match-beginning 0) 2) (point))
+         (setq string (delete-region (point) (+ (point) size)))
+         (insert (format "%S" string)))
+       (setq bytes (nnimap-get-length)
              lines nil)
        (beginning-of-line)
        (setq size
@@ -157,7 +168,8 @@ not done by default on servers that doesn't support that command.")
                   (match-string 1)))
        (beginning-of-line)
        (when (search-forward "BODYSTRUCTURE" (line-end-position) t)
-         (let ((structure (ignore-errors (read (current-buffer)))))
+         (let ((structure (ignore-errors
+                            (read (current-buffer)))))
            (while (and (consp structure)
                        (not (stringp (car structure))))
              (setq structure (car structure)))
@@ -215,6 +227,7 @@ not done by default on servers that doesn't support that command.")
     (set (make-local-variable 'nnimap-object)
         (make-nnimap :server (nnoo-current-server 'nnimap)))
     (push (list buffer (current-buffer)) nnimap-connection-alist)
+    (push (current-buffer) nnimap-process-buffers)
     (current-buffer)))
 
 (defun nnimap-open-shell-stream (name buffer host port)
@@ -238,7 +251,25 @@ not done by default on servers that doesn't support that command.")
             '("login" "password") address port nil (null ports))))
     credentials))
 
+(defun nnimap-keepalive ()
+  (let ((now (current-time)))
+    (dolist (buffer nnimap-process-buffers)
+      (when (buffer-name buffer)
+       (with-current-buffer buffer
+         (when (and nnimap-object
+                    (nnimap-last-command-time nnimap-object)
+                    (> (time-to-seconds
+                        (time-subtract
+                         now
+                         (nnimap-last-command-time nnimap-object)))
+                       ;; More than five minutes since the last command.
+                       (* 5 60)))
+           (nnimap-send-command "NOOP")))))))
+
 (defun nnimap-open-connection (buffer)
+  (unless nnimap-keepalive-timer
+    (setq nnimap-keepalive-timer (run-at-time (* 60 15) (* 60 15)
+                                             'nnimap-keepalive)))
   (with-current-buffer (nnimap-make-process-buffer buffer)
     (let* ((coding-system-for-read 'binary)
           (coding-system-for-write 'binary)
@@ -257,6 +288,11 @@ not done by default on servers that doesn't support that command.")
               "*nnimap*" (current-buffer) nnimap-address
               (or nnimap-server-port "imap"))
              '("imap"))
+            ((eq nnimap-stream 'starttls)
+             (starttls-open-stream
+              "*nnimap*" (current-buffer) nnimap-address
+              (or nnimap-server-port "imap"))
+             '("imap"))
             ((eq nnimap-stream 'ssl)
              (open-tls-stream
               "*nnimap*" (current-buffer) nnimap-address
@@ -273,6 +309,9 @@ not done by default on servers that doesn't support that command.")
                       '(open run)))
        (gnus-set-process-query-on-exit-flag (nnimap-process nnimap-object) nil)
        (when (setq connection-result (nnimap-wait-for-connection))
+         (when (eq nnimap-stream 'starttls)
+           (nnimap-send-command "STARTTLS")
+           (starttls-negotiate (nnimap-process nnimap-object)))
          (unless (equal connection-result "PREAUTH")
            (if (not (setq credentials
                           (if (eq nnimap-authenticator 'anonymous)
@@ -419,14 +458,11 @@ not done by default on servers that doesn't support that command.")
              (when info
                (nnimap-update-infos marks (list info)))
              (goto-char (point-max))
-             (cond
-              (marks
-               (let ((uidnext (nth 5 (car marks))))
-                 (setq high (or (nth 3 (car marks)) (1- uidnext))
-                       low (or (nth 4 (car marks)) uidnext))))
-              ((re-search-backward "UIDNEXT \\([0-9]+\\)" nil t)
-               (setq high (1- (string-to-number (match-string 1)))
-                     low 1)))))
+             (let ((uidnext (nth 5 (car marks))))
+               (setq high (if uidnext
+                              (1- uidnext)
+                            (nth 3 (car marks)))
+                     low (or (nth 4 (car marks)) uidnext)))))
          (erase-buffer)
          (insert
           (format
@@ -782,17 +818,19 @@ not done by default on servers that doesn't support that command.")
       (let ((group (gnus-info-group info))
            (completep (and start-article
                            (= start-article 1))))
+       (when uidnext
+         (setq high (1- uidnext)))
        ;; First set the active ranges based on high/low.
        (if (or completep
                (not (gnus-active group)))
            (gnus-set-active group
-                            (if high
+                            (if (and low high)
                                 (cons low high)
                               ;; No articles in this group.
                               (cons uidnext (1- uidnext))))
          (setcdr (gnus-active group) (or high (1- uidnext))))
        (unless high
-         (setq high (or high (1- uidnext))))
+         (setq high (1- uidnext)))
        ;; Then update the list of read articles.
        (let* ((unread
                (gnus-compress-sequence
@@ -825,6 +863,7 @@ not done by default on servers that doesn't support that command.")
                  (new-marks
                   (gnus-compress-sequence
                    (cdr (or (assoc (caddr type) flags)     ; %Flagged
+                            (assoc (intern (cadr type) obarray) flags)
                             (assoc (cadr type) flags)))))) ; "\Flagged"
              (setq marks (delq old-marks marks))
              (pop old-marks)
@@ -970,6 +1009,7 @@ not done by default on servers that doesn't support that command.")
 
 (defun nnimap-command (&rest args)
   (erase-buffer)
+  (setf (nnimap-last-command-time nnimap-object) (current-time))
   (let* ((sequence (apply #'nnimap-send-command args))
         (response (nnimap-get-response sequence)))
     (if (equal (caar response) "OK")
@@ -1138,8 +1178,8 @@ not done by default on servers that doesn't support that command.")
              ;; And then mark the successful copy actions as deleted,
              ;; and possibly expunge them.
              (nnimap-mark-and-expunge-incoming
-              (nnimap-parse-copied-articles sequences))
-             (nnimap-mark-and-expunge-incoming junk-articles))))))))
+              (nnimap-parse-copied-articles sequences)))
+            (nnimap-mark-and-expunge-incoming junk-articles)))))))
 
 (defun nnimap-mark-and-expunge-incoming (range)
   (when range