(nnimap-update-info): Rely more on the current active than the param
[gnus] / lisp / nnimap.el
index c11e30a..775b1bc 100644 (file)
@@ -295,7 +295,9 @@ textual parts.")
             (port nil)
             (ports
              (cond
-              ((eq nnimap-stream 'network)
+              ((or (eq nnimap-stream 'network)
+                   (and (eq nnimap-stream 'starttls)
+                        (fboundp 'open-gnutls-stream)))
                (open-network-stream
                 "*nnimap*" (current-buffer) nnimap-address
                 (setq port
@@ -357,8 +359,16 @@ textual parts.")
              (push (format "%s" nnimap-server-port) ports))
            ;; If this is a STARTTLS-capable server, then sever the
            ;; connection and start a STARTTLS connection instead.
-           (when (and (eq nnimap-stream 'network)
-                      (member "STARTTLS" (nnimap-capabilities nnimap-object)))
+           (cond
+            ((and (or (and (eq nnimap-stream 'network)
+                           (member "STARTTLS"
+                                   (nnimap-capabilities nnimap-object)))
+                      (eq nnimap-stream 'starttls))
+                  (fboundp 'open-gnutls-stream))
+             (nnimap-command "STARTTLS")
+             (gnutls-negotiate (nnimap-process nnimap-object) nil))
+            ((and (eq nnimap-stream 'network)
+                  (member "STARTTLS" (nnimap-capabilities nnimap-object)))
              (let ((nnimap-stream 'starttls))
                (let ((tls-process
                       (nnimap-open-connection buffer)))
@@ -369,7 +379,7 @@ textual parts.")
                  (when (memq (process-status tls-process) '(open run))
                    (delete-process (nnimap-process nnimap-object))
                    (kill-buffer (current-buffer))
-                   (return tls-process)))))
+                   (return tls-process))))))
            (unless (equal connection-result "PREAUTH")
              (if (not (setq credentials
                             (if (eq nnimap-authenticator 'anonymous)
@@ -949,7 +959,7 @@ textual parts.")
       (erase-buffer)
       (setf (nnimap-group nnimap-object) nil)
       ;; QRESYNC handling isn't implemented.
-      (let ((qresyncp (member "notQRESYNC" (nnimap-capabilities nnimap-object)))
+      (let ((qresyncp (member "QRESYNC" (nnimap-capabilities nnimap-object)))
            params groups sequences active uidvalidity modseq group)
        ;; Go through the infos and gather the data needed to know
        ;; what and how to request the data.
@@ -963,9 +973,9 @@ textual parts.")
                   uidvalidity
                   modseq)
              (push
-              (list 'qresync
-                    (nnimap-send-command "EXAMINE %S (QRESYNC (%s %s))"
+              (list (nnimap-send-command "EXAMINE %S (QRESYNC (%s %s))"
                                          group uidvalidity modseq)
+                    'qresync
                     nil group 'qresync)
               sequences)
            (let ((start
@@ -998,7 +1008,10 @@ textual parts.")
             (nnimap-possibly-change-group nil server))
     (with-current-buffer (nnimap-buffer)
       ;; Wait for the final data to trickle in.
-      (when (nnimap-wait-for-response (cadar sequences) t)
+      (when (nnimap-wait-for-response (if (eq (cadar sequences) 'qresync)
+                                         (caar sequences)
+                                       (cadar sequences))
+                                     t)
        ;; Now we should have most of the data we need, no matter
        ;; whether we're QRESYNCING, fetching all the flags from
        ;; scratch, or just fetching the last 100 flags per group.
@@ -1028,13 +1041,16 @@ textual parts.")
 
 (defun nnimap-update-info (info marks)
   (destructuring-bind (existing flags high low uidnext start-article
-                               permanent-flags uidvalidity) marks
+                               permanent-flags uidvalidity
+                               vanished highestmodseq) marks
     (cond
      ;; Ignore groups with no UIDNEXT/marks.  This happens for
      ;; completely empty groups.
      ((and (not existing)
           (not uidnext))
-      )
+      (let ((active (cdr (assq 'active (gnus-info-params info)))))
+       (when active
+         (gnus-set-active (gnus-info-group info) active))))
      ;; We have a mismatch between the old and new UIDVALIDITY
      ;; identifiers, so we have to re-request the group info (the next
      ;; time).  This virtually never happens.
@@ -1043,12 +1059,15 @@ textual parts.")
        (and old-uidvalidity
             (not (equal old-uidvalidity uidvalidity))
             (> start-article 1)))
-      (gnus-group-remove-parameter info 'uidvalidity))
+      (gnus-group-remove-parameter info 'uidvalidity)
+      (gnus-group-remove-parameter info 'modseq))
      ;; We have the data needed to update.
      (t
-      (let ((group (gnus-info-group info))
-           (completep (and start-article
-                           (= start-article 1))))
+      (let* ((group (gnus-info-group info))
+            (completep (and start-article
+                            (= start-article 1)))
+            (active (or (gnus-active group)
+                        (cdr (assq 'active (gnus-info-params info))))))
        (when uidnext
          (setq high (1- uidnext)))
        ;; First set the active ranges based on high/low.
@@ -1061,6 +1080,8 @@ textual parts.")
                              (uidnext
                               ;; No articles in this group.
                               (cons uidnext (1- uidnext)))
+                             (active
+                              active)
                              (start-article
                               (cons start-article (1- start-article)))
                              (t
@@ -1068,7 +1089,7 @@ textual parts.")
                               nil)))
          (gnus-set-active
           group
-          (cons (car (gnus-active group))
+          (cons (car active)
                 (or high (1- uidnext)))))
        ;; See whether this is a read-only group.
        (unless (eq permanent-flags 'not-scanned)
@@ -1077,54 +1098,101 @@ textual parts.")
           (if (memq '%* permanent-flags)
               t
             nil)))
-       ;; Then update marks and read articles if this isn't a
+       ;; Update marks and read articles if this isn't a
        ;; read-only IMAP group.
        (when (cdr (assq 'permanent-flags (gnus-info-params info)))
-         ;; Update the list of read articles.
-         (let* ((unread
-                 (gnus-compress-sequence
-                  (gnus-set-difference
-                   (gnus-set-difference
-                    existing
-                    (cdr (assoc '%Seen flags)))
-                   (cdr (assoc '%Flagged flags)))))
-                (read (gnus-range-difference
-                       (cons start-article high) unread)))
-           (when (> start-article 1)
-             (setq read
-                   (gnus-range-nconcat
-                    (if (> start-article 1)
-                        (gnus-sorted-range-intersection
-                         (cons 1 (1- start-article))
-                         (gnus-info-read info))
-                      (gnus-info-read info))
-                    read)))
-           (gnus-info-set-read info read)
-           ;; Update the marks.
-           (setq marks (gnus-info-marks info))
-           (dolist (type (cdr nnimap-mark-alist))
-             (let ((old-marks (assoc (car type) marks))
-                   (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)
-               (when (and old-marks
-                          (> start-article 1))
-                 (setq old-marks (gnus-range-difference
-                                  old-marks
-                                  (cons start-article high)))
-                 (setq new-marks (gnus-range-nconcat old-marks new-marks)))
-               (when new-marks
-                 (push (cons (car type) new-marks) marks)))
-             (gnus-info-set-marks info marks t))))
+         (if (and highestmodseq
+                  (not start-article))
+             ;; We've gotten the data by QRESYNCing.
+             (nnimap-update-qresync-info
+              info existing (nnimap-imap-ranges-to-gnus-ranges vanished) flags)
+           ;; Do normal non-QRESYNC flag updates.
+           ;; Update the list of read articles.
+           (let* ((unread
+                   (gnus-compress-sequence
+                    (gnus-set-difference
+                     (gnus-set-difference
+                      existing
+                      (cdr (assoc '%Seen flags)))
+                     (cdr (assoc '%Flagged flags)))))
+                  (read (gnus-range-difference
+                         (cons start-article high) unread)))
+             (when (> start-article 1)
+               (setq read
+                     (gnus-range-nconcat
+                      (if (> start-article 1)
+                          (gnus-sorted-range-intersection
+                           (cons 1 (1- start-article))
+                           (gnus-info-read info))
+                        (gnus-info-read info))
+                      read)))
+             (gnus-info-set-read info read)
+             ;; Update the marks.
+             (setq marks (gnus-info-marks info))
+             (dolist (type (cdr nnimap-mark-alist))
+               (let ((old-marks (assoc (car type) marks))
+                     (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)
+                 (when (and old-marks
+                            (> start-article 1))
+                   (setq old-marks (gnus-range-difference
+                                    old-marks
+                                    (cons start-article high)))
+                   (setq new-marks (gnus-range-nconcat old-marks new-marks)))
+                 (when new-marks
+                   (push (cons (car type) new-marks) marks)))
+               (gnus-info-set-marks info marks t)))))
        ;; Note the active level for the next run-through.
        (gnus-group-set-parameter info 'active (gnus-active group))
        (gnus-group-set-parameter info 'uidvalidity uidvalidity)
+       (gnus-group-set-parameter info 'modseq highestmodseq)
        (nnimap-store-info info (gnus-active group)))))))
 
+(defun nnimap-update-qresync-info (info existing vanished flags)
+  ;; Add all the vanished articles to the list of read articles.
+  (gnus-info-set-read
+   info
+   (gnus-range-add (gnus-info-read info)
+                  vanished))
+  (let ((marks (gnus-info-marks info)))
+    (dolist (type (cdr nnimap-mark-alist))
+      (let ((ticks (assoc (car type) marks))
+           (new-marks
+            (cdr (or (assoc (caddr type) flags) ; %Flagged
+                     (assoc (intern (cadr type) obarray) flags)
+                     (assoc (cadr type) flags))))) ; "\Flagged"
+       (setq marks (delq ticks marks))
+       (pop ticks)
+       ;; Add the new marks we got.
+       (setq ticks (gnus-add-to-range ticks new-marks))
+       ;; Remove the marks from messages that don't have them.
+       (setq ticks (gnus-remove-from-range
+                    ticks
+                    (gnus-compress-sequence
+                     (gnus-sorted-complement existing new-marks))))
+       (when ticks
+         (push (cons (car type) ticks) marks)))
+      (gnus-info-set-marks info marks t))))
+
+(defun nnimap-imap-ranges-to-gnus-ranges (irange)
+  (if (zerop (length irange))
+      nil
+    (let ((result nil))
+      (dolist (elem (split-string irange ","))
+       (push
+        (if (string-match ":" elem)
+            (let ((numbers (split-string elem ":")))
+              (cons (string-to-number (car numbers))
+                    (string-to-number (cadr numbers))))
+          (string-to-number elem))
+        result))
+      (nreverse result))))
+
 (defun nnimap-store-info (info active)
   (let* ((group (gnus-group-real-name (gnus-info-group info)))
         (entry (assoc group nnimap-current-infos)))
@@ -1134,14 +1202,16 @@ textual parts.")
 
 (defun nnimap-flags-to-marks (groups)
   (let (data group totalp uidnext articles start-article mark permanent-flags
-            uidvalidity)
+            uidvalidity vanished highestmodseq)
     (dolist (elem groups)
       (setq group (car elem)
            uidnext (nth 1 elem)
            start-article (nth 2 elem)
            permanent-flags (nth 3 elem)
            uidvalidity (nth 4 elem)
-           articles (nthcdr 5 elem))
+           vanished (nth 5 elem)
+           highestmodseq (nth 6 elem)
+           articles (nthcdr 7 elem))
       (let ((high (caar articles))
            marks low existing)
        (dolist (article articles)
@@ -1153,7 +1223,7 @@ textual parts.")
                (push (list flag (car article)) marks)
              (setcdr mark (cons (car article) (cdr mark))))))
        (push (list group existing marks high low uidnext start-article
-                   permanent-flags uidvalidity)
+                   permanent-flags uidvalidity vanished highestmodseq)
              data)))
     data))
 
@@ -1163,13 +1233,13 @@ textual parts.")
   (subst-char-in-region (point-min) (point-max)
                        ?\\ ?% t)
   (let (start end articles groups uidnext elems permanent-flags
-             uidvalidity)
+             uidvalidity vanished highestmodseq)
     (dolist (elem sequences)
       (destructuring-bind (group-sequence flag-sequence totalp group command)
          elem
        (setq start (point))
-       ;; The EXAMINE was successful.
        (when (and
+              ;; The EXAMINE was successful.
               (search-forward (format "\n%d OK " group-sequence) nil t)
               (progn
                 (forward-line 1)
@@ -1189,24 +1259,42 @@ textual parts.")
                 (goto-char start)
                 (setq uidvalidity
                       (and (re-search-forward "UIDVALIDITY \\([0-9]+\\)"
-                                           (or end (point-min)) t)
+                                              (or end (point-min)) t)
                            ;; Store UIDVALIDITY as a string, as it's
                            ;; too big for 32-bit Emacsen, usually.
                            (match-string 1)))
+                (goto-char start)
+                (setq vanished
+                      (and (eq flag-sequence 'qresync)
+                           (re-search-forward "VANISHED.* \\([0-9:,]+\\)"
+                                              (or end (point-min)) t)
+                           (match-string 1)))
+                (goto-char start)
+                (setq highestmodseq
+                      (and (search-forward "HIGHESTMODSEQ "
+                                           (or end (point-min)) t)
+                           (read (current-buffer))))
                 (goto-char end)
                 (forward-line -1))
               ;; The UID FETCH FLAGS was successful.
-              (search-forward (format "\n%d OK " flag-sequence) nil t))
-         (setq start (point))
-         (goto-char end)
+              (or (eq flag-sequence 'qresync)
+                  (search-forward (format "\n%d OK " flag-sequence) nil t)))
+         (if (eq flag-sequence 'qresync)
+             (progn
+               (goto-char start)
+               (setq start end))
+           (setq start (point))
+           (goto-char end))
          (while (search-forward " FETCH " start t)
            (setq elems (read (current-buffer)))
            (push (cons (cadr (memq 'UID elems))
                        (cadr (memq 'FLAGS elems)))
                  articles))
-         (push (nconc (list group uidnext totalp permanent-flags uidvalidity)
+         (push (nconc (list group uidnext totalp permanent-flags uidvalidity
+                            vanished highestmodseq)
                       articles)
                groups)
+         (goto-char end)
          (setq articles nil))))
     groups))
 
@@ -1301,20 +1389,28 @@ textual parts.")
 (defun nnimap-wait-for-response (sequence &optional messagep)
   (let ((process (get-buffer-process (current-buffer)))
        openp)
-    (goto-char (point-max))
-    (while (and (setq openp (memq (process-status process)
-                                 '(open run)))
-               (not (re-search-backward
-                     (format "^%d .*\n" sequence)
-                     (if nnimap-streaming
-                         (max (point-min) (- (point) 500))
-                       (point-min))
-                     t)))
-      (when messagep
-       (message "nnimap read %dk" (/ (buffer-size) 1000)))
-      (nnheader-accept-process-output process)
-      (goto-char (point-max)))
-    openp))
+    (condition-case nil
+        (progn
+         (goto-char (point-max))
+         (while (and (setq openp (memq (process-status process)
+                                       '(open run)))
+                     (not (re-search-backward
+                           (format "^%d .*\n" sequence)
+                           (if nnimap-streaming
+                               (max (point-min) (- (point) 500))
+                             (point-min))
+                           t)))
+           (when messagep
+             (message "nnimap read %dk" (/ (buffer-size) 1000)))
+           (nnheader-accept-process-output process)
+           (goto-char (point-max)))
+          openp)
+      (quit
+       ;; The user hit C-g while we were waiting: kill the process, in case
+       ;; it's a gnutls-cli process that's stuck (tends to happen a lot behind
+       ;; NAT routers).
+       (delete-process process)
+       nil))))
 
 (defun nnimap-parse-response ()
   (let ((lines (split-string (nnimap-last-response-string) "\r\n" t))