Subject: bug#21057: closed (Re: bug#21057: [PATCH] nnimap.el: add support for IMAP namespaces)

From: [email-masked] (GNU bug Tracking System)
Date: Sat, 11 Aug 2018 07:50:02 +0000

Your bug report

#21057: [PATCH] nnimap.el: add support for IMAP namespaces

which was filed against the emacs,gnus package, has been closed.

The explanation is attached below, along with your original report.
If you require more details, please reply to [email-masked].

-- 
21057: http://debbugs.gnu.org/cgi/bugreport.cgi?bug=21057
GNU Bug Tracking System
Contact [email-masked] with problems

Subject: Re: bug#21057: [PATCH] nnimap.el: add support for IMAP namespaces
From: Eli Zaretskii <[email-masked]>
To: Nikolaus Rath <[email-masked]>
Cc: [email-masked], [email-masked], [email-masked], [email-masked]
Date: Sat, 11 Aug 2018 10:49:16 +0300
List-Id: <debbugs-submit.debbugs.gnu.org>

> From: Nikolaus Rath <[email-masked]>
> Date: Sun, 05 Aug 2018 10:54:27 +0100
> Cc: Eric Abrahamsen <[email-masked]>, Andreas Schwab <[email-masked]>,
> 	[email-masked]
> 
> Friendly ping.. is there anything that blocks applying this?

Pushed to the master branch.

In the future, please observe 2 spaces between sentences in
documentation and comments, and also mention the bug number in the
commit log message.

Subject: [PATCH] nnimap.el: add support for IMAP namespaces
From: Nikolaus Rath <[email-masked]>
To: [email-masked]
Date: Sun, 12 Jul 2015 18:52:40 -0700
List-Id: <debbugs-submit.debbugs.gnu.org>

Package: gnus
Tags: patch

Hello,

The attached patch adds basic support for IMAP namespaces to nnimap. If
the new server variable nnimap-use-namespaces is t, Gnus will attempt to
determine the user's personal namespace (e.g. "INBOX."). When reading
the list of mail folders, this prefix will be stripped. If there are
folders outside the users personal namespace, they will be prefixed with
the breakout character (#).

This is useful for servers where all personal mailboxes appear is
subfolders of the user's INBOX. For example, before the patch one would
have:

nnimap+fm:INBOX
nnimap+fm:INBOX.Sent Items
nnimap+fm:INBOX.Drafts
nnimap+fm:INBOX.Archive
nnimap+fm:Shared.Bob.INBOX

and afterwards:

nnimap+fm:INBOX
nnimap+fm:Sent Items
nnimap+fm:Drafts
nnimap+fm:Archive
nnimap+fm:#Shared.Bob.INBOX

I have only tested this on my personal account, so feedback is
appreciated. 

Thanks for considering,
-Nikolaus

-- 
GPG encrypted emails preferred. Key id: 0xD113FCAC3C4E599F
Fingerprint: ED31 791B 2C5C 1613 AF38 8B8A D113 FCAC 3C4E 599F

             »Time flies like an arrow, fruit flies like a Banana.«
>From cd74921ce8e867dfe056d6aa94431269460822e2 Mon Sep 17 00:00:00 2001
From: Nikolaus Rath <[email-masked]>
Date: Sun, 12 Jul 2015 11:10:28 -0700
Subject: [PATCH 1/2] nnimap.el: factor out nnimap-group-to-imap

* lisp/nnimap.el (nnimap-request-group-scan)
(nnimap-request-create-group, nnimap-request-delete-group)
(nnimap-request-rename-group, nnimap-request-move-article)
(nnimap-process-expiry-targets)
(nnimap-request-update-group-status)
(nnimap-request-accept-article, nnimap-request-list)
(nnimap-retrieve-group-data-early, nnimap-change-group)
(nnimap-split-incoming-mail): use nnimap-group-to-imap.
(nnimap-group-to-imap): new function to map Gnus group names to
IMAP folder names.
---
 lisp/ChangeLog | 13 +++++++++++++
 lisp/nnimap.el | 32 ++++++++++++++++++--------------
 2 files changed, 31 insertions(+), 14 deletions(-)

diff --git a/lisp/ChangeLog b/lisp/ChangeLog
index 556be98..be5d3fb 100644
--- a/lisp/ChangeLog
+++ b/lisp/ChangeLog
@@ -1,5 +1,18 @@
 2015-07-12 Nikolaus Rath <[email-masked]>
 
+	* nnimap.el (nnimap-request-group-scan)
+	(nnimap-request-create-group, nnimap-request-delete-group)
+	(nnimap-request-rename-group, nnimap-request-move-article)
+	(nnimap-process-expiry-targets)
+	(nnimap-request-update-group-status)
+	(nnimap-request-accept-article, nnimap-request-list)
+	(nnimap-retrieve-group-data-early, nnimap-change-group)
+	(nnimap-split-incoming-mail): use nnimap-group-to-imap.
+	(nnimap-group-to-imap): new function to map Gnus group names to
+	IMAP folder names.
+
+2015-07-12 Nikolaus Rath <[email-masked]>
+
 	* nnimap.el (nnimap-open-connection-1): explicitly ask server for
 	capabilities instead of relying on LOGIN response.
 
diff --git a/lisp/nnimap.el b/lisp/nnimap.el
index b24c3c5..19632ad 100644
--- a/lisp/nnimap.el
+++ b/lisp/nnimap.el
@@ -178,6 +178,10 @@ If non-nil, articles flagged as deleted (using the IMAP
 
 (defvar nnimap-inhibit-logging nil)
 
+(defun nnimap-group-to-imap (group)
+  "Convert Gnus group name to IMAP mailbox name"
+  (utf7-encode group t))
+
 (defun nnimap-buffer ()
   (nnimap-find-process-buffer nntp-server-buffer))
 
@@ -859,7 +863,7 @@ If non-nil, articles flagged as deleted (using the IMAP
       (with-current-buffer (nnimap-buffer)
 	(erase-buffer)
 	(let ((group-sequence
-	       (nnimap-send-command "SELECT %S" (utf7-encode group t)))
+	       (nnimap-send-command "SELECT %S" (nnimap-group-to-imap group)))
 	      (flag-sequence
 	       (nnimap-send-command "UID FETCH 1:* FLAGS")))
 	  (setf (nnimap-group nnimap-object) group)
@@ -891,13 +895,13 @@ If non-nil, articles flagged as deleted (using the IMAP
   (setq group (nnimap-decode-gnus-group group))
   (when (nnimap-change-group nil server)
     (with-current-buffer (nnimap-buffer)
-      (car (nnimap-command "CREATE %S" (utf7-encode group t))))))
+      (car (nnimap-command "CREATE %S" (nnimap-group-to-imap group))))))
 
 (deffoo nnimap-request-delete-group (group &optional force server)
   (setq group (nnimap-decode-gnus-group group))
   (when (nnimap-change-group nil server)
     (with-current-buffer (nnimap-buffer)
-      (car (nnimap-command "DELETE %S" (utf7-encode group t))))))
+      (car (nnimap-command "DELETE %S" (nnimap-group-to-imap group))))))
 
 (deffoo nnimap-request-rename-group (group new-name &optional server)
   (setq group (nnimap-decode-gnus-group group))
@@ -905,7 +909,7 @@ If non-nil, articles flagged as deleted (using the IMAP
     (with-current-buffer (nnimap-buffer)
       (nnimap-unselect-group)
       (car (nnimap-command "RENAME %S %S"
-			   (utf7-encode group t) (utf7-encode new-name t))))))
+			   (nnimap-group-to-imap group) (nnimap-group-to-imap new-name))))))
 
 (defun nnimap-unselect-group ()
   ;; Make sure we don't have this group open read/write by asking
@@ -966,7 +970,7 @@ If non-nil, articles flagged as deleted (using the IMAP
                                  "UID MOVE %d %S"
                                "UID COPY %d %S"))
                     (result (nnimap-command command article
-                                            (utf7-encode internal-move-group t))))
+                                            (nnimap-group-to-imap internal-move-group))))
                 (when (and (car result) (not can-move))
                   (nnimap-delete-article article))
                 (cons internal-move-group
@@ -1032,7 +1036,7 @@ If non-nil, articles flagged as deleted (using the IMAP
                     "UID MOVE %s %S"
                   "UID COPY %s %S")
                 (nnimap-article-ranges (gnus-compress-sequence articles))
-                (utf7-encode (gnus-group-real-name nnmail-expiry-target) t))
+                (nnimap-group-to-imap (gnus-group-real-name nnmail-expiry-target)))
                (set (if can-move 'deleted-articles 'articles-to-delete) articles))))
       t)
      (t
@@ -1172,7 +1176,7 @@ If sync is non-nil, wait for server response."
 		      (unsubscribe "UNSUBSCRIBE")))))
       (when command
 	(with-current-buffer (nnimap-buffer)
-	  (nnimap-command "%s %S" (cadr command) (utf7-encode group t)))))))
+	  (nnimap-command "%s %S" (cadr command) (nnimap-group-to-imap group)))))))
 
 (deffoo nnimap-request-set-mark (group actions &optional server)
   (setq group (nnimap-decode-gnus-group group))
@@ -1227,7 +1231,7 @@ If sync is non-nil, wait for server response."
 	    (nnimap-unselect-group))
 	  (erase-buffer)
 	  (setq sequence (nnimap-send-command
-			  "APPEND %S {%d}" (utf7-encode group t)
+			  "APPEND %S {%d}" (nnimap-group-to-imap group)
 			  (length message)))
 	  (unless nnimap-streaming
 	    (nnimap-wait-for-connection "^[+]"))
@@ -1354,7 +1358,7 @@ If sync is non-nil, wait for server response."
 	    (dolist (group groups)
 	      (setf (nnimap-examined nnimap-object) group)
 	      (push (list (nnimap-send-command "EXAMINE %S"
-					       (utf7-encode group t))
+					       (nnimap-group-to-imap group))
 			  group)
 		    sequences))
 	    (nnimap-wait-for-response (caar sequences))
@@ -1426,7 +1430,7 @@ If sync is non-nil, wait for server response."
 		   unexist)
 	      (push
 	       (list (nnimap-send-command "EXAMINE %S (%s (%s %s))"
-					  (utf7-encode group t)
+					  (nnimap-group-to-imap group)
 					  (nnimap-quirk "QRESYNC")
 					  uidvalidity modseq)
 		     'qresync
@@ -1448,7 +1452,7 @@ If sync is non-nil, wait for server response."
 		(incf (nnimap-initial-resync nnimap-object))
 		(setq start 1))
 	      (push (list (nnimap-send-command "%s %S" command
-					       (utf7-encode group t))
+					       (nnimap-group-to-imap group))
 			  (nnimap-send-command "UID FETCH %d:* FLAGS" start)
 			  start group command)
 		    sequences))))
@@ -1878,7 +1882,7 @@ Return the server's response to the SELECT or EXAMINE command."
                                       (if read-only
                                           "EXAMINE"
                                         "SELECT")
-                                      (utf7-encode group t))))
+                                      (nnimap-group-to-imap group))))
           (when (car result)
             (setf (nnimap-group nnimap-object) group
                   (nnimap-select-result nnimap-object) result)
@@ -2135,7 +2139,7 @@ Return the server's response to the SELECT or EXAMINE command."
 	    (dolist (spec specs)
 	      (when (and (not (member (car spec) groups))
 			 (not (eq (car spec) 'junk)))
-		(nnimap-command "CREATE %S" (utf7-encode (car spec) t))))
+		(nnimap-command "CREATE %S" (nnimap-group-to-imap (car spec)))))
 	    ;; Then copy over all the messages.
 	    (erase-buffer)
 	    (dolist (spec specs)
@@ -2151,7 +2155,7 @@ Return the server's response to the SELECT or EXAMINE command."
                                      "UID MOVE %d %S"
                                      "UID COPY %s %S")
 				(nnimap-article-ranges ranges)
-				(utf7-encode group t))
+				(nnimap-group-to-imap group))
 			       ranges)
 			 sequences)))))
 	    ;; Wait for the last COPY response...
-- 
2.1.4

>From 2f05bec6c1d928e16cc345af35d51c391d88a3ce Mon Sep 17 00:00:00 2001
From: Nikolaus Rath <[email-masked]>
Date: Tue, 14 Jul 2015 19:03:09 -0700
Subject: [PATCH 2/2] nnimap.el: Add support for IMAP namespaces.

* lisp/nnimap.el (nnimap-use-namespaces): introduced new server variable.
(nnimap-group-to-imap, nnimap-get-groups): transform IMAP group names
to Gnus group name by stripping / prefixing personal namespace prefix.
(nnimap-open-connection-1): ask server for namespaces and store them.
---
 lisp/ChangeLog |  7 +++++++
 lisp/nnimap.el | 65 +++++++++++++++++++++++++++++++++++++++++++++++-----------
 2 files changed, 60 insertions(+), 12 deletions(-)

diff --git a/lisp/ChangeLog b/lisp/ChangeLog
index be5d3fb..27de0d0 100644
--- a/lisp/ChangeLog
+++ b/lisp/ChangeLog
@@ -1,3 +1,10 @@
+2015-07-14 Nikolaus Rath <[email-masked]>
+
+	* nnimap.el (nnimap-use-namespaces): introduced new server variable.
+	(nnimap-group-to-imap, nnimap-get-groups): transform IMAP group names
+	to Gnus group name by stripping / prefixing personal namespace prefix.
+	(nnimap-open-connection-1): ask server for namespaces and store them.
+
 2015-07-12 Nikolaus Rath <[email-masked]>
 
 	* nnimap.el (nnimap-request-group-scan)
diff --git a/lisp/nnimap.el b/lisp/nnimap.el
index 19632ad..8cbf187 100644
--- a/lisp/nnimap.el
+++ b/lisp/nnimap.el
@@ -62,6 +62,13 @@
 If nnimap-stream is `ssl', this will default to `imaps'.  If not,
 it will default to `imap'.")
 
+(defvoo nnimap-use-namespaces nil
+  "Whether to use IMAP namespaces
+If in Gnus your folder names in all start with (e.g.) `INBOX',
+you probably want to set this to t. The effects of this are
+purely cosmetical, but changing this variable will affect the
+names of your nnimap groups. ")
+
 (defvoo nnimap-stream 'undecided
   "How nnimap talks to the IMAP server.
 The value should be either `undecided', `ssl' or `tls',
@@ -124,6 +131,8 @@ some servers.")
 
 (defvoo nnimap-current-infos nil)
 
+(setq nnimap-namespaces nil)
+
 (defvoo nnimap-fetch-partial-articles nil
   "If non-nil, Gnus will fetch partial articles.
 If t, Gnus will fetch only the first part.  If a string, it
@@ -180,7 +189,17 @@ If non-nil, articles flagged as deleted (using the IMAP
 
 (defun nnimap-group-to-imap (group)
   "Convert Gnus group name to IMAP mailbox name"
-  (utf7-encode group t))
+  (let* ((prefix (cadr (assoc (nnoo-current-server 'nnimap)
+                              nnimap-namespaces)))
+         (inbox (substring prefix 0 -1)))
+    (utf7-encode 
+     (cond ((or (not prefix)
+                (string-equal group inbox))
+            group)
+           ((string-prefix-p "#" group) 
+            (substring group 1))
+           (t
+            (concat prefix group))) t)))
 
 (defun nnimap-buffer ()
   (nnimap-find-process-buffer nntp-server-buffer))
@@ -467,7 +486,8 @@ If non-nil, articles flagged as deleted (using the IMAP
 	     (props (cdr stream-list))
 	     (greeting (plist-get props :greeting))
 	     (capabilities (plist-get props :capabilities))
-	     (stream-type (plist-get props :type)))
+	     (stream-type (plist-get props :type))
+             (server (nnoo-current-server 'nnimap)))
 	(when (and stream (not (memq (process-status stream) '(open run))))
 	  (setq stream nil))
 
@@ -502,9 +522,7 @@ If non-nil, articles flagged as deleted (using the IMAP
                                ;; the virtual server name and the address
                                (nnimap-credentials
 				(gnus-delete-duplicates
-				 (list
-                                  (nnoo-current-server 'nnimap)
-				  nnimap-address))
+				 (list server nnimap-address))
                                 ports
                                 nnimap-user))))
 		  (setq nnimap-object nil)
@@ -523,7 +541,21 @@ If non-nil, articles flagged as deleted (using the IMAP
 		      (dolist (response (cddr (nnimap-command "CAPABILITY")))
 			(when (string= "CAPABILITY" (upcase (car response)))
 			  (setf (nnimap-capabilities nnimap-object)
-				(mapcar #'upcase (cdr response))))))
+				(mapcar #'upcase (cdr response)))))
+                      (when (and nnimap-use-namespaces
+                                 (nnimap-capability "NAMESPACE"))
+                        (erase-buffer)
+                        (nnimap-wait-for-response (nnimap-send-command "NAMESPACE"))
+                        (let ((response (nnimap-last-response-string)))
+                          (when (string-match
+                                 "^\\*\\W+NAMESPACE\\W+((\"\\([^\"\n]+\\)\"\\W+\"\\(.\\)\"))\\W+"
+                                 response)
+                            (let ((namespace (cons (match-string 1 response)
+                                                   (match-string 2 response)))
+                                  (entry (assoc server nnimap-namespaces)))
+                              (if entry
+                                  (setcdr entry namespace)
+                                (push (cons server namespace) nnimap-namespaces)))))))
 		  ;; If the login failed, then forget the credentials
 		  ;; that are now possibly cached.
 		  (dolist (host (list (nnoo-current-server 'nnimap)
@@ -1311,8 +1343,12 @@ If sync is non-nil, wait for server response."
 
 (defun nnimap-get-groups ()
   (erase-buffer)
-  (let ((sequence (nnimap-send-command "LIST \"\" \"*\""))
-	groups)
+  (let* ((sequence (nnimap-send-command "LIST \"\" \"*\""))
+         (prefix (cadr (assoc (nnoo-current-server 'nnimap)
+                              nnimap-namespaces)))
+         (prefix-len (length prefix))
+         (inbox (substring prefix 0 -1))
+         groups)
     (nnimap-wait-for-response sequence)
     (subst-char-in-region (point-min) (point-max)
 			  ?\\ ?% t)
@@ -1329,10 +1365,15 @@ If sync is non-nil, wait for server response."
 			   (skip-chars-backward " \r\"")
 			   (point)))))
 	(unless (member '%NoSelect flags)
-	  (push (utf7-decode (if (stringp group)
-				 group
-			       (format "%s" group)) t)
-		groups))))
+          (let* ((group (utf7-decode (if (stringp group) group
+                                       (format "%s" group)) t))
+                 (group (cond ((equal inbox group)
+                               group)
+                              ((string-prefix-p prefix group)
+                               (substring group prefix-len))
+                              (t
+                               (concat "#" group)))))
+            (push group groups)))))
     (nreverse groups)))
 
 (defun nnimap-get-responses (sequences)
-- 
2.1.4