]> granicus.if.org Git - neomutt/commitdiff
Add IMAP keywords support
authorMehdi Abaakouk <sileht@sileht.net>
Mon, 20 Feb 2017 19:49:23 +0000 (20:49 +0100)
committerRichard Russon <rich@flatcap.org>
Tue, 3 Oct 2017 12:47:30 +0000 (13:47 +0100)
This change introduces IMAP keyword support.

* index_format gets %g, %G, %J and %Gx to display them
* pattern gets g to search/match imap keywords
* colors.c gets index_tag/index_tags to color them
* OPS gets modify-labels to edit them
* tag-transforms/tag-formats/hidden_tags can be display them in more sexy manner

Most of the code is shared with the notmuch backend.

Upstream: https://dev.mutt.org/trac/ticket/3210
Closes neomutt/neomutt#225

curs_main.c
hdrline.c
imap/imap.c
imap/imap.h
imap/imap_private.h
imap/message.c
imap/message.h

index be185cda005cdbeefdfd03520e02a10f587b0e4d..e271c7a18017e59fac20479b348f072306b9ee00 100644 (file)
@@ -1852,89 +1852,116 @@ int mutt_index_menu(void)
         break;
       }
 
+#endif
+#if defined(USE_NOTMUCH) || defined(USE_IMAP)
       case OP_MAIN_MODIFY_LABELS:
       case OP_MAIN_MODIFY_LABELS_THEN_HIDE:
       {
-        if (!Context || (Context->magic != MUTT_NOTMUCH))
+        if (!Context || ((Context->magic != MUTT_NOTMUCH) && (Context->magic != MUTT_IMAP)))
         {
           mutt_message(_("No virtual folder, aborting."));
           break;
         }
-        CHECK_MSGCOUNT;
-        CHECK_VISIBLE;
-        *buf = '\0';
-        if (mutt_get_field("Add/remove labels: ", buf, sizeof(buf), MUTT_NM_TAG) || !*buf)
+#ifdef USE_IMAP
+        if (Context->magic == MUTT_IMAP)
         {
-          mutt_message(_("No label specified, aborting."));
+          CHECK_MSGCOUNT;
+          CHECK_VISIBLE;
+          CHECK_READONLY;
+          rc = imap_keywords_message(tag ? NULL : CURHDR, op == OP_MAIN_MODIFY_LABELS_THEN_HIDE);
+          if (rc > 0)
+          {
+            Context->changed = 1;
+            menu->redraw = REDRAW_FULL;
+            mutt_message(_("%d keywords changed."), rc);
+          }
+          else if (rc == 0)
+            mutt_message(_("No keywords changed."));
           break;
         }
-        if (tag)
+#endif
+#ifdef USE_NOTMUCH
+        if (Context->magic == MUTT_NOTMUCH)
         {
-          char msgbuf[STRING];
-          struct Progress progress;
-          int px;
-
-          if (!Context->quiet)
+          CHECK_MSGCOUNT;
+          CHECK_VISIBLE;
+          *buf = '\0';
+          if (mutt_get_field("Add/remove labels: ", buf, sizeof(buf), MUTT_NM_TAG) || !*buf)
           {
-            snprintf(msgbuf, sizeof(msgbuf), _("Update labels..."));
-            mutt_progress_init(&progress, msgbuf, MUTT_PROGRESS_MSG, 1, Context->tagged);
+            mutt_message(_("No label specified, aborting."));
+            break;
           }
-          nm_longrun_init(Context, true);
-          for (px = 0, j = 0; j < Context->vcount; j++)
+          if (tag)
           {
-            if (Context->hdrs[Context->v2r[j]]->tagged)
-            {
-              if (!Context->quiet)
-                mutt_progress_update(&progress, ++px, -1);
-              nm_modify_message_tags(Context, Context->hdrs[Context->v2r[j]], buf);
+            char msgbuf[STRING];
+            struct Progress progress;
+            int px;
 
-              bool still_queried =
-                  nm_message_is_still_queried(Context, Context->hdrs[Context->v2r[j]]);
-              if (op == OP_MAIN_MODIFY_LABELS_THEN_HIDE)
+            if (!Context->quiet)
+            {
+              snprintf(msgbuf, sizeof(msgbuf), _("Update labels..."));
+              mutt_progress_init(&progress, msgbuf, MUTT_PROGRESS_MSG, 1,
+                                 Context->tagged);
+            }
+            nm_longrun_init(Context, true);
+            for (px = 0, j = 0; j < Context->vcount; j++)
+            {
+              if (Context->hdrs[Context->v2r[j]]->tagged)
               {
-                Context->hdrs[Context->v2r[j]]->quasi_deleted = !still_queried;
-                Context->changed = true;
+                if (!Context->quiet)
+                  mutt_progress_update(&progress, ++px, -1);
+                nm_modify_message_tags(Context, Context->hdrs[Context->v2r[j]], buf);
+                bool still_queried =
+                    nm_message_is_still_queried(Context, Context->hdrs[Context->v2r[j]]);
+                if (op == OP_MAIN_MODIFY_LABELS_THEN_HIDE)
+                {
+                  Context->hdrs[Context->v2r[j]]->quasi_deleted = !still_queried;
+                  Context->changed = true;
+                }
               }
             }
+            nm_longrun_done(Context);
+            menu->redraw = REDRAW_STATUS | REDRAW_INDEX;
           }
-          nm_longrun_done(Context);
-          menu->redraw = REDRAW_STATUS | REDRAW_INDEX;
-        }
-        else
-        {
-          if (nm_modify_message_tags(Context, CURHDR, buf))
-          {
-            mutt_message(_("Failed to modify labels, aborting."));
-            break;
-          }
-          if (op == OP_MAIN_MODIFY_LABELS_THEN_HIDE)
-          {
-            bool still_queried = nm_message_is_still_queried(Context, CURHDR);
-            CURHDR->quasi_deleted = !still_queried;
-            Context->changed = true;
-          }
-          if (menu->menu == MENU_PAGER)
-          {
-            op = OP_DISPLAY_MESSAGE;
-            continue;
-          }
-          if (option(OPT_RESOLVE))
+          else
           {
-            if ((menu->current = ci_next_undeleted(menu->current)) == -1)
+            if (nm_modify_message_tags(Context, CURHDR, buf))
             {
-              menu->current = menu->oldcurrent;
-              menu->redraw = REDRAW_CURRENT;
+              mutt_message(_("Failed to modify labels, aborting."));
+              break;
+            }
+            if (op == OP_MAIN_MODIFY_LABELS_THEN_HIDE)
+            {
+              bool still_queried = nm_message_is_still_queried(Context, CURHDR);
+              CURHDR->quasi_deleted = !still_queried;
+              Context->changed = true;
+            }
+            if (menu->menu == MENU_PAGER)
+            {
+              op = OP_DISPLAY_MESSAGE;
+              continue;
+            }
+            if (option(OPT_RESOLVE))
+            {
+              if ((menu->current = ci_next_undeleted(menu->current)) == -1)
+              {
+                menu->current = menu->oldcurrent;
+                menu->redraw = REDRAW_CURRENT;
+              }
+              else
+                menu->redraw = REDRAW_MOTION_RESYNCH;
             }
             else
-              menu->redraw = REDRAW_MOTION_RESYNCH;
+              menu->redraw = REDRAW_CURRENT;
           }
-          else
-            menu->redraw = REDRAW_CURRENT;
         }
+#endif
         menu->redraw |= REDRAW_STATUS;
         break;
       }
 
+#endif
+#ifdef USE_NOTMUCH
       case OP_MAIN_VFOLDER_FROM_QUERY:
         buf[0] = '\0';
         if (mutt_get_field("Query: ", buf, sizeof(buf), MUTT_NM_QUERY) != 0 || !buf[0])
index 80fdfc7e184da1956a472e2250802a03431a88b7..6fcda728dc40477b768d46672becaa16baa727b4 100644 (file)
--- a/hdrline.c
+++ b/hdrline.c
@@ -470,8 +470,8 @@ static char *apply_subject_mods(struct Envelope *env)
  * | \%E     | number of messages in current thread
  * | \%f     | entire from line
  * | \%F     | like %n, unless from self
- * | \%g     | message tags (e.g. notmuch tags)
- * | \%Gx    | individual message tag (e.g. notmuch tags)
+ * | \%g     | message tags (e.g. notmuch tags/imap flags)
+ * | \%Gx    | individual message tag (e.g. notmuch tags/imap flags)
  * | \%J     | message tags (if present, tree unfolded, and != parent's keywords)
  * | \%i     | message-id
  * | \%I     | initials of author
index dbc479104949a5b08bea71e7f693ef028c2353e0..e5925960d78d5aa0c01906f621349db23aa7f3dc 100644 (file)
@@ -51,6 +51,7 @@
 #include "message.h"
 #include "mutt_curses.h"
 #include "mutt_socket.h"
+#include "mutt_tags.h"
 #include "mx.h"
 #include "options.h"
 #include "pattern.h"
@@ -1165,9 +1166,15 @@ int imap_sync_message_for_copy(struct ImapData *idata, struct Header *hdr,
   imap_set_flag(idata, MUTT_ACL_DELETE, HEADER_DATA(hdr)->deleted, "\\Deleted ",
                 flags, sizeof(flags));
 
-  /* now make sure we don't lose custom tags */
   if (mutt_bit_isset(idata->ctx->rights, MUTT_ACL_WRITE))
-    imap_add_keywords(flags, hdr, &idata->flags, sizeof(flags));
+  {
+    /* restore system keywords */
+    if (HEADER_DATA(hdr)->keywords_system)
+      safe_strcat(flags, sizeof(flags), HEADER_DATA(hdr)->keywords_system);
+    /* set custom keywords */
+    if (hdr_tags_get_with_hidden(hdr))
+      safe_strcat(flags, sizeof(flags), hdr_tags_get_with_hidden(hdr));
+  }
 
   mutt_remove_trailing_ws(flags);
 
@@ -1182,6 +1189,10 @@ int imap_sync_message_for_copy(struct ImapData *idata, struct Header *hdr,
     imap_set_flag(idata, MUTT_ACL_DELETE, !HEADER_DATA(hdr)->deleted,
                   "\\Deleted ", flags, sizeof(flags));
 
+    /* erase custom keywords */
+    if (mutt_bit_isset(idata->ctx->rights, MUTT_ACL_WRITE) && HEADER_DATA(hdr)->keywords_remote)
+      safe_strcat(flags, sizeof(flags), HEADER_DATA(hdr)->keywords_remote);
+
     mutt_remove_trailing_ws(flags);
 
     mutt_buffer_addstr(cmd, " -FLAGS.SILENT (");
@@ -1208,6 +1219,10 @@ int imap_sync_message_for_copy(struct ImapData *idata, struct Header *hdr,
     }
   }
 
+  /* server have now the updated keywords */
+  FREE(&HEADER_DATA(hdr)->keywords_remote);
+  HEADER_DATA(hdr)->keywords_remote = safe_strdup(hdr_tags_get_with_hidden(hdr));
+
   hdr->active = true;
   if (hdr->deleted == HEADER_DATA(hdr)->deleted)
     hdr->changed = false;
@@ -1244,6 +1259,88 @@ static int sync_helper(struct ImapData *idata, int right, int flag, const char *
 }
 
 /**
+ * imap_sync_keywords - Add/Change/Remove keywords from headers
+ * @param idata: pointer to a struct ImapData
+ * @param h: pointer to a header struct
+ *
+ * @retval  0 Success
+ * @retval -1 Error
+ *
+ * This method update the server flags on the server by
+ * removing the last know custom keywords of a header
+ * and adds the local keywords
+ *
+ * If everything success we push the local keywords to the
+ * last know custom keywords (keywords_remote).
+ *
+ * Also this method check that each keywords is support by the server
+ * first and remove unsupported one.
+ */
+static int imap_sync_keywords(struct ImapData *idata, struct Header *h)
+{
+  struct Buffer *cmd = NULL;
+  char uid[11];
+
+  if (!mutt_bit_isset(idata->ctx->rights, MUTT_ACL_WRITE))
+    return 0;
+
+  snprintf(uid, sizeof(uid), "%u", HEADER_DATA(h)->uid);
+
+  /* Remove old custom keywords */
+  if (HEADER_DATA(h)->keywords_remote)
+  {
+    if (!(cmd = mutt_buffer_new()))
+    {
+      mutt_debug(1, "imap_sync_keywords: unable to allocate buffer\n");
+      return -1;
+    }
+    cmd->dptr = cmd->data;
+    mutt_buffer_addstr(cmd, "UID STORE ");
+    mutt_buffer_addstr(cmd, uid);
+    mutt_buffer_addstr(cmd, " -FLAGS.SILENT (");
+    mutt_buffer_addstr(cmd, HEADER_DATA(h)->keywords_remote);
+    mutt_buffer_addstr(cmd, ")");
+
+    /* Should we return here, or we are fine and we could
+     * continue to add new keywords *
+     */
+    if (imap_exec(idata, cmd->data, 0) != 0)
+      return -1;
+
+    mutt_buffer_free(&cmd);
+  }
+
+  /* Add new custom keywords */
+  if (hdr_tags_get_with_hidden(h))
+  {
+    if (!(cmd = mutt_buffer_new()))
+    {
+      mutt_debug(1, "imap_sync_keywords: fail to remove old keywords\n");
+      return -1;
+    }
+    cmd->dptr = cmd->data;
+    mutt_buffer_addstr(cmd, "UID STORE ");
+    mutt_buffer_addstr(cmd, uid);
+    mutt_buffer_addstr(cmd, " +FLAGS.SILENT (");
+    mutt_buffer_addstr(cmd, hdr_tags_get_with_hidden(h));
+    mutt_buffer_addstr(cmd, ")");
+
+    if (imap_exec(idata, cmd->data, 0) != 0)
+    {
+      mutt_debug(1, "imap_sync_keywords: fail to add new keywords\n");
+      return -1;
+    }
+
+    mutt_buffer_free(&cmd);
+  }
+
+  /* We are good sync them and only keep those supported by the server */
+  FREE(&HEADER_DATA(h)->keywords_remote);
+  HEADER_DATA(h)->keywords_remote = safe_strdup(hdr_tags_get_with_hidden(h));
+  return 0;
+}
+
+/*
  * imap_sync_mailbox - Sync all the changes to the server
  * @param ctx     the current context
  * @param expunge 0 or 1 - do expunge?
@@ -1314,6 +1411,12 @@ int imap_sync_mailbox(struct Context *ctx, int expunge)
 #endif
     }
 
+    if (mutt_strcmp(HEADER_DATA(h)->keywords_remote, hdr_tags_get_with_hidden(h)) != 0)
+    {
+      if (imap_sync_keywords(idata, h) != 0)
+        mutt_error(_("Error syncing keywords"));
+    }
+
     if (h->active && h->changed)
     {
 #ifdef USE_HCACHE
index f9c660fa464d9b015323b9197286adcb0012627f..2799920f0d35fb48e699a096f543b939249e1f99 100644 (file)
@@ -69,6 +69,7 @@ int imap_mailbox_rename(const char *mailbox);
 /* message.c */
 int imap_append_message(struct Context *ctx, struct Message *msg);
 int imap_copy_messages(struct Context *ctx, struct Header *h, char *dest, int delete);
+int imap_keywords_message(struct Header *h, bool quasi_deleted);
 
 /* socket.c */
 void imap_logout_all(void);
index cf942de50ee8dabd1f001d4c22afa019669598a9..4a6b64ca299e9262d3837b0d1e22d414639e8af8 100644 (file)
@@ -295,7 +295,6 @@ int imap_exec(struct ImapData *idata, const char *cmd, int flags);
 int imap_cmd_idle(struct ImapData *idata);
 
 /* message.c */
-void imap_add_keywords(char *s, struct Header *keywords, struct ListHead *mailbox_flags, size_t slen);
 void imap_free_header_data(struct ImapHeaderData **data);
 int imap_read_headers(struct ImapData *idata, unsigned int msn_begin, unsigned int msn_end);
 char *imap_set_flags(struct ImapData *idata, struct Header *h, char *s, int *server_changes);
index 8813793650ec8d5d5c10e6002b77f9ad356d12b0..02512328fb37ccaf8e8d89ec4e44498b9a7e5720 100644 (file)
@@ -46,6 +46,7 @@
 #include "mailbox.h"
 #include "mutt_curses.h"
 #include "mutt_socket.h"
+#include "mutt_tags.h"
 #include "mx.h"
 #include "options.h"
 #include "protos.h"
@@ -56,7 +57,6 @@
 static struct ImapHeaderData *imap_new_header_data(void)
 {
   struct ImapHeaderData *d = safe_calloc(1, sizeof(struct ImapHeaderData));
-  STAILQ_INIT(&d->keywords);
   return d;
 }
 
@@ -162,7 +162,9 @@ static char *msg_parse_flags(struct ImapHeader *h, char *s)
   }
   s++;
 
-  mutt_list_free(&hd->keywords);
+  FREE(&hd->keywords_system);
+  FREE(&hd->keywords_remote);
+
   hd->deleted = hd->flagged = hd->replied = hd->read = hd->old = false;
 
   /* start parsing */
@@ -197,15 +199,23 @@ static char *msg_parse_flags(struct ImapHeader *h, char *s)
     }
     else
     {
-      /* store custom flags as well */
       char ctmp;
       char *flag_word = s;
+      bool is_system_keyword = (mutt_strncasecmp("\\", s, 1) == 0);
 
       while (*s && !ISSPACE(*s) && *s != ')')
         s++;
+
       ctmp = *s;
       *s = '\0';
-      mutt_list_insert_tail(&hd->keywords, safe_strdup(flag_word));
+
+      /* store other system flags as well (mainly \\Draft) */
+      if (is_system_keyword)
+        mutt_str_append_item(&hd->keywords_system, flag_word, 32);
+      /* store custom flags as well */
+      else
+        mutt_str_append_item(&hd->keywords_remote, flag_word, 32);
+
       *s = ctmp;
     }
     SKIPWS(s);
@@ -468,6 +478,121 @@ static void imap_generate_seqset(struct Buffer *b, struct ImapData *idata,
 }
 
 /**
+ * imap_keywords_message - Add/Change/Remove keywords from headers
+ * @param[in] h: pointer to a header struct or NULL to modify
+ *               all headers of the Context
+ *
+ * @return int the number of headers changed
+ * @retval -1 in case of error
+ *
+ * This method prompts the user to edit keywords of current header
+ * And update keywords_local with the new keywords list
+ *
+ * It also ensure each keyword are valid IMAP rfc822 atom
+ */
+int imap_keywords_message(struct Header *h, bool quasi_deleted)
+{
+  char buf[LONG_STRING], *new, *checker;
+  int i;
+  int changed;
+
+  if (!Context || Context->magic != MUTT_IMAP)
+    return -1;
+
+  /* Check for \* flags capability */
+  struct ImapData* idata = (struct ImapData*) Context->data;
+  if (!imap_has_flag(&idata->flags, NULL))
+  {
+    mutt_error(_("IMAP server doesn't support custom keywords"));
+    return -1;
+  }
+
+  *buf = '\0';
+  if (h && hdr_tags_get(h))
+  {
+    strncpy(buf, hdr_tags_get_with_hidden(h), LONG_STRING);
+  }
+
+  if (mutt_get_field("Keywords: ", buf, sizeof(buf), 0) != 0)
+    return 0;
+
+  /* each keyword must be atom defined by rfc822 as:
+   *
+   * atom           = 1*<any CHAR except specials, SPACE and CTLs>
+   * CHAR           = ( 0.-127. )
+   * specials       = "(" / ")" / "<" / ">" / "@"
+   *                  / "," / ";" / ":" / "\" / <">
+   *                  / "." / "[" / "]"
+   * SPACE          = ( 32. )
+   * CTLS           = ( 0.-31., 127.)
+   *
+   * And must be separated by one space.
+   */
+
+  new = buf;
+  checker = buf;
+  SKIPWS(checker);
+  while (*checker != '\0')
+  {
+    if (*checker < 32 || *checker >= 127 || // We allow space because it's the separator
+        *checker == 40 ||                   // (
+        *checker == 41 ||                   // )
+        *checker == 60 ||                   // <
+        *checker == 62 ||                   // >
+        *checker == 64 ||                   // @
+        *checker == 44 ||                   // ,
+        *checker == 59 ||                   // ;
+        *checker == 58 ||                   // :
+        *checker == 92 ||                   // backslash
+        *checker == 34 ||                   // "
+        *checker == 46 ||                   // .
+        *checker == 91 ||                   // [
+        *checker == 93)                     // ]
+    {
+      mutt_error(_("Invalid IMAP keyworks"));
+      mutt_sleep(2);
+      return 0;
+    }
+
+    /* Skip duplicate space */
+    while (*checker == ' ' && *(checker + 1) == ' ')
+      checker++;
+
+    /* copy char to new and go the next one */
+    *new ++ = *checker++;
+  }
+  *new = '\0';
+  new = buf; /* rewind */
+  mutt_remove_trailing_ws(new);
+
+  if (*new == '\0')
+    new = NULL;
+
+  changed = 0;
+  if (h != NULL)
+  {
+    changed += hdr_tags_replace(h, new);
+    if (changed)
+      h->quasi_deleted = quasi_deleted;
+  }
+  else
+  {
+#define HDR_OF(index) Context->hdrs[Context->v2r[(index)]]
+    for (i = 0; i < Context->vcount; ++i)
+    {
+      if (HDR_OF(i)->tagged && hdr_tags_replace(HDR_OF(i), new))
+      {
+        HDR_OF(i)->quasi_deleted = quasi_deleted;
+        ++changed;
+        mutt_set_flag(Context, HDR_OF(i), MUTT_TAG, 0);
+      }
+    }
+  }
+
+  return changed;
+}
+
+/*
  * imap_read_headers - Read headers from the server
  *
  * Changed to read many headers instead of just one. It will return the msn of
@@ -626,6 +751,8 @@ int imap_read_headers(struct ImapData *idata, unsigned int msn_begin, unsigned i
           ctx->hdrs[idx]->changed = h.data->changed;
           /*  ctx->hdrs[msgno]->received is restored from mutt_hcache_restore */
           ctx->hdrs[idx]->data = (void *) (h.data);
+          hdr_tags_init(ctx->hdrs[idx]);
+          hdr_tags_replace(ctx->hdrs[idx], safe_strdup(h.data->keywords_remote));
 
           ctx->msgcount++;
           ctx->size += ctx->hdrs[idx]->content->length;
@@ -710,6 +837,7 @@ int imap_read_headers(struct ImapData *idata, unsigned int msn_begin, unsigned i
         /* make sure we don't get remnants from older larger message headers */
         fputs("\n\n", fp);
 
+
         if (h.data->msn < 1 || h.data->msn > fetch_msn_end)
         {
           mutt_debug(1, "imap_read_headers: skipping FETCH response for "
@@ -744,6 +872,8 @@ int imap_read_headers(struct ImapData *idata, unsigned int msn_begin, unsigned i
         ctx->hdrs[idx]->changed = h.data->changed;
         ctx->hdrs[idx]->received = h.received;
         ctx->hdrs[idx]->data = (void *) (h.data);
+        hdr_tags_init(ctx->hdrs[idx]);
+        hdr_tags_replace(ctx->hdrs[idx], safe_strdup(h.data->keywords_remote));
 
         if (maxuid < h.data->uid)
           maxuid = h.data->uid;
@@ -1392,29 +1522,6 @@ int imap_cache_clean(struct ImapData *idata)
   return 0;
 }
 
-/**
- * imap_add_keywords - concatenate custom IMAP tags to list
- *
- * If the tags appear in the folder flags list. Why wouldn't they?
- */
-void imap_add_keywords(char *s, struct Header *h, struct ListHead *mailbox_flags, size_t slen)
-{
-  struct ListHead *keywords = &HEADER_DATA(h)->keywords;
-
-  if (STAILQ_EMPTY(mailbox_flags) || !HEADER_DATA(h) || STAILQ_EMPTY(keywords))
-    return;
-
-  struct ListNode *np;
-  STAILQ_FOREACH(np, keywords, entries)
-  {
-    if (imap_has_flag(mailbox_flags, np->data))
-    {
-      safe_strcat(s, slen, np->data);
-      safe_strcat(s, slen, " ");
-    }
-  }
-}
-
 /**
  * imap_free_header_data - free ImapHeader structure
  */
@@ -1423,7 +1530,8 @@ void imap_free_header_data(struct ImapHeaderData **data)
   if (*data)
   {
     /* this should be safe even if the list wasn't used */
-    mutt_list_free(&(*data)->keywords);
+    FREE(&((*data)->keywords_system));
+    FREE(&((*data)->keywords_remote));
     FREE(data);
   }
 }
@@ -1487,6 +1595,9 @@ char *imap_set_flags(struct ImapData *idata, struct Header *h, char *s, int *ser
   if ((s = msg_parse_flags(&newh, s)) == NULL)
     return NULL;
 
+  /* Update tags system */
+  hdr_tags_replace(h, safe_strdup(hd->keywords_remote));
+
   /* YAUH (yet another ugly hack): temporarily set context to
    * read-write even if it's read-only, so *server* updates of
    * flags can be processed by mutt_set_flag. ctx->changed must
index a18e2f9befc1124b2d50b4ece877f12bcc25ac1a..7a8e2a3705994c2817934bb3c5072f95d4db129c 100644 (file)
@@ -47,7 +47,9 @@ struct ImapHeaderData
 
   unsigned int uid; /**< 32-bit Message UID */
   unsigned int msn; /**< Message Sequence Number */
-  struct ListHead keywords;
+
+  char *keywords_system;
+  char *keywords_remote;
 };
 
 /**