Changeset 4465:11d366f0c81a

Show
Ignore:
Timestamp:
2005-12-11 14:51:29 (3 years ago)
Author:
Brendan Cully <brendan@…>
Branch:
HEAD
Message:

IMAP command batching code, used to pipeline mailbox poll requests.
Up to 10 poll commands will be sent at a time (tunable in imap_private.h).
This is a huge win on my currently awful wireless link. It takes a knife
to a lot of fundamental IMAP code (mostly for the better), so it may
have destabilised things. Time for some brave (or lazy non-Changelog-reading)
testers to report... next up, IDLE support.

Files:
6 modified

Legend:

Unmodified
Added
Removed
  • buffy.c

    r4439 r4465  
    287287 
    288288#ifdef USE_IMAP 
     289  BuffyCount += imap_buffy_check (force); 
     290 
    289291  if (!Context || Context->magic != M_IMAP) 
    290292#endif 
     
    301303  for (tmp = Incoming; tmp; tmp = tmp->next) 
    302304  { 
     305#ifndef USE_IMAP 
    303306    tmp->new = 0; 
    304  
    305 #ifdef USE_IMAP 
    306     if (mx_is_imap (tmp->path)) 
    307       tmp->magic = M_IMAP; 
    308     else 
    309 #endif 
     307#endif 
     308 
    310309#ifdef USE_POP 
    311310    if (mx_is_pop (tmp->path)) 
     
    399398          BuffyCount++; 
    400399        break; 
    401          
    402 #ifdef USE_IMAP 
    403       case M_IMAP: 
    404         if ((tmp->new = imap_mailbox_check (tmp->path, 1)) > 0) 
    405           BuffyCount++; 
    406         else 
    407           tmp->new = 0; 
    408  
    409         break; 
    410 #endif 
    411  
    412 #ifdef USE_POP 
    413       case M_POP: 
    414         break; 
    415 #endif 
    416400      } 
    417401    } 
  • imap/command.c

    r4464 r4465  
    3030#include "message.h" 
    3131#include "mx.h" 
     32#include "buffy.h" 
    3233 
    3334#include <ctype.h> 
     
    4748static void cmd_parse_myrights (IMAP_DATA* idata, const char* s); 
    4849static void cmd_parse_search (IMAP_DATA* idata, const char* s); 
     50static void cmd_parse_status (IMAP_DATA* idata, char* s); 
    4951 
    5052static char *Capabilities[] = { 
     
    6365}; 
    6466 
    65 /* imap_cmd_start: Given an IMAP command, send it to the server. 
    66  *   Currently a minor convenience, but helps to route all IMAP commands 
    67  *   through a single interface. */ 
    68 int imap_cmd_start (IMAP_DATA* idata, const char* cmdstr) 
     67/* imap_cmd_queue: Add command to command queue. Fails if the queue is full. */ 
     68int imap_cmd_queue (IMAP_DATA* idata, const char* cmdstr) 
    6969{ 
    7070  IMAP_COMMAND* cmd; 
    71   char* out; 
    72   int outlen; 
    73   int rc; 
     71  unsigned int cmdlen; 
    7472 
    7573  if (idata->status == IMAP_FATAL) 
     
    8179  if (!(cmd = cmd_new (idata))) 
    8280    return IMAP_CMD_BAD; 
    83    
     81 
    8482  /* seq, space, cmd, \r\n\0 */ 
    85   outlen = strlen (cmd->seq) + strlen (cmdstr) + 4; 
    86   out = (char*) safe_malloc (outlen); 
    87   snprintf (out, outlen, "%s %s\r\n", cmd->seq, cmdstr); 
    88  
    89   rc = mutt_socket_write (idata->conn, out); 
    90  
    91   FREE (&out); 
     83  cmdlen = strlen (cmd->seq) + strlen (cmdstr) + 4; 
     84  if (idata->cmdbuflen < cmdlen + (idata->cmdtail - idata->cmdbuf)) 
     85  { 
     86    unsigned int tailoff = idata->cmdtail - idata->cmdbuf; 
     87    safe_realloc (&idata->cmdbuf, tailoff + cmdlen); 
     88    idata->cmdbuflen = tailoff + cmdlen; 
     89    idata->cmdtail = idata->cmdbuf + tailoff; 
     90  } 
     91  snprintf (idata->cmdtail, cmdlen, "%s %s\r\n", cmd->seq, cmdstr); 
     92  idata->cmdtail += cmdlen - 1; 
     93 
     94  return 0; 
     95} 
     96 
     97/* imap_cmd_start: Given an IMAP command, send it to the server. 
     98 *   If cmdstr is NULL, sends queued commands. */ 
     99int imap_cmd_start (IMAP_DATA* idata, const char* cmdstr) 
     100{ 
     101  int rc; 
     102 
     103  if (cmdstr && (rc = imap_cmd_queue (idata, cmdstr)) < 0) 
     104    return rc; 
     105 
     106  /* don't write old or empty commands */ 
     107  if (idata->cmdtail == idata->cmdbuf) 
     108    return IMAP_CMD_BAD; 
     109 
     110  rc = mutt_socket_write (idata->conn, idata->cmdbuf); 
     111  idata->cmdtail = idata->cmdbuf; 
    92112 
    93113  return (rc < 0) ? IMAP_CMD_BAD : 0; 
     
    190210int imap_exec (IMAP_DATA* idata, const char* cmdstr, int flags) 
    191211{ 
    192   IMAP_COMMAND* cmd; 
    193   char* out; 
    194   int outlen; 
    195212  int rc; 
    196213 
     
    201218  } 
    202219 
    203   if (!(cmd = cmd_new (idata))) 
    204     return -1; 
    205  
    206   /* seq, space, cmd, \r\n\0 */ 
    207   outlen = strlen (cmd->seq) + strlen (cmdstr) + 4; 
    208   out = (char*) safe_malloc (outlen); 
    209   snprintf (out, outlen, "%s %s\r\n", cmd->seq, cmdstr); 
    210  
    211   rc = mutt_socket_write_d (idata->conn, out, 
     220  if (cmdstr && (rc = imap_cmd_queue (idata, cmdstr)) < 0) 
     221    return rc; 
     222   
     223  /* don't write old or empty commands */ 
     224  if (idata->cmdtail == idata->cmdbuf) 
     225    return IMAP_CMD_BAD; 
     226   
     227  rc = mutt_socket_write_d (idata->conn, idata->cmdbuf, 
    212228    flags & IMAP_CMD_PASS ? IMAP_LOG_PASS : IMAP_LOG_CMD); 
    213   FREE (&out); 
     229  idata->cmdtail = idata->cmdbuf; 
    214230 
    215231  if (rc < 0) 
     
    408424  else if (ascii_strncasecmp ("SEARCH", s, 6) == 0) 
    409425    cmd_parse_search (idata, s); 
     426  else if (ascii_strncasecmp ("STATUS", s, 6) == 0) 
     427    cmd_parse_status (idata, s); 
    410428  else if (ascii_strncasecmp ("BYE", s, 3) == 0) 
    411429  { 
     
    689707  } 
    690708} 
     709 
     710/* first cut: just do buffy update. Later we may wish to cache all 
     711 * mailbox information, even that not desired by buffy */ 
     712static void cmd_parse_status (IMAP_DATA* idata, char* s) 
     713{ 
     714  char* mailbox; 
     715  BUFFY* inc; 
     716  IMAP_MBOX mx; 
     717  int count; 
     718 
     719  dprint (2, (debugfile, "Handling STATUS\n")); 
     720   
     721  mailbox = imap_next_word (s); 
     722  s = imap_next_word (mailbox); 
     723  *(s - 1) = '\0'; 
     724   
     725  imap_unmunge_mbox_name (mailbox); 
     726   
     727  for (inc = Incoming; inc; inc = inc->next) 
     728  { 
     729    if (inc->magic != M_IMAP) 
     730      continue; 
     731     
     732    if (imap_parse_path (inc->path, &mx) < 0) 
     733    { 
     734      dprint (1, (debugfile, "Error parsing mailbox %s, skipping\n", inc->path)); 
     735      continue; 
     736    } 
     737     
     738    if (mutt_account_match (&idata->conn->account, &mx.account) && mx.mbox 
     739        && strncmp (mailbox, mx.mbox, strlen (mailbox)) == 0) 
     740    { 
     741      if (*s++ != '(') 
     742      { 
     743        dprint (1, (debugfile, "Error parsing STATUS\n")); 
     744        FREE (&mx.mbox); 
     745        return; 
     746      } 
     747       
     748      while (*s && *s != ')') 
     749      { 
     750        if (!ascii_strncmp ("RECENT", s, 6)) 
     751        { 
     752          s = imap_next_word (s); 
     753          count = strtol (s, &s, 10); 
     754          dprint (2, (debugfile, "%d recent in %s\n", count, mx.mbox)); 
     755          inc->new = count; 
     756        } 
     757        else if (!ascii_strncmp ("UNSEEN", s, 6)) 
     758        { 
     759          s = imap_next_word (s); 
     760          count = strtol (s, &s, 10); 
     761          dprint (2, (debugfile, "%d unseen in %s\n", count, mx.mbox)); 
     762        } 
     763        else if (!ascii_strncmp ("MESSAGES", s, 8)) 
     764        { 
     765          s = imap_next_word (s); 
     766          count = strtol (s, &s, 10); 
     767          dprint (2, (debugfile, "%d messages in %s\n", count, mx.mbox)); 
     768        } 
     769      } 
     770       
     771      return; 
     772    } 
     773 
     774    FREE (&mx.mbox); 
     775  } 
     776} 
  • imap/imap.c

    r4464 r4465  
    12051205} 
    12061206 
     1207/* split path into (idata,mailbox name) */ 
     1208static int imap_buffy_split (const char* path, IMAP_DATA** hidata, char* buf, size_t blen) 
     1209{ 
     1210  IMAP_MBOX mx; 
     1211 
     1212  if (imap_parse_path (path, &mx)) 
     1213  { 
     1214    dprint (1, (debugfile, "imap_split_path: Error parsing %s\n", path)); 
     1215    return -1; 
     1216  } 
     1217  if (!(*hidata = imap_conn_find (&(mx.account), option (OPTIMAPPASSIVE) ? M_IMAP_CONN_NONEW : 0))) 
     1218  { 
     1219    FREE (&mx.mbox); 
     1220    return -1; 
     1221  } 
     1222   
     1223  imap_fix_path (*hidata, mx.mbox, buf, blen); 
     1224  FREE (&mx.mbox); 
     1225 
     1226  return 0; 
     1227} 
     1228 
     1229/* check for new mail in any subscribed mailboxes. Given a list of mailboxes 
     1230 * rather than called once for each so that it can batch the commands and 
     1231 * save on round trips. Returns number of mailboxes with new mail. */ 
     1232int imap_buffy_check (int force) 
     1233{ 
     1234  IMAP_DATA* idata; 
     1235  IMAP_DATA* lastdata = NULL; 
     1236  BUFFY* mailbox; 
     1237  char name[LONG_STRING]; 
     1238  char command[LONG_STRING]; 
     1239  char munged[LONG_STRING]; 
     1240  int buffies = 0; 
     1241  int rc; 
     1242 
     1243  for (mailbox = Incoming; mailbox; mailbox = mailbox->next) 
     1244  { 
     1245    /* Init newly-added mailboxes */ 
     1246    if (! mailbox->magic) 
     1247    { 
     1248      if (mx_is_imap (mailbox->path)) 
     1249        mailbox->magic = M_IMAP; 
     1250    } 
     1251     
     1252    if (mailbox->magic != M_IMAP) 
     1253      continue; 
     1254 
     1255    mailbox->new = 0; 
     1256 
     1257    if (imap_buffy_split (mailbox->path, &idata, name, sizeof (name)) < 0) 
     1258      continue; 
     1259 
     1260    /* Don't issue STATUS on the selected mailbox, it will be NOOPed or 
     1261     * IDLEd elsewhere */ 
     1262    if (mutt_strcmp (name, idata->mailbox) == 0 
     1263        || (ascii_strcasecmp (name, "INBOX") == 0 
     1264            && mutt_strcasecmp (name, idata->mailbox) == 0)) 
     1265      continue; 
     1266       
     1267    if (!lastdata) 
     1268      lastdata = idata; 
     1269 
     1270    if (idata != lastdata) 
     1271    { 
     1272      /* Send commands to previous server. Sorting the buffy list 
     1273       * may prevent some infelicitous interleavings */ 
     1274      if (imap_exec (lastdata, NULL, 0) != IMAP_CMD_OK) 
     1275        dprint (1, (debugfile, "Error polling mailboxes\n")); 
     1276 
     1277      lastdata = NULL; 
     1278    } 
     1279 
     1280    if (!mutt_bit_isset (idata->capabilities, IMAP4REV1) && 
     1281        !mutt_bit_isset (idata->capabilities, STATUS)) 
     1282    { 
     1283      dprint (2, (debugfile, "Server doesn't support STATUS\n")); 
     1284      continue; 
     1285    } 
     1286     
     1287    imap_munge_mbox_name (munged, sizeof (munged), name); 
     1288    /* we need a better way to detect new mail... */ 
     1289    snprintf (command, sizeof (command), "STATUS %s (RECENT)", munged); 
     1290 
     1291    if (imap_cmd_queue (idata, command) < 0) 
     1292    { 
     1293      /* pipeline must be full, drain it */ 
     1294      dprint (2, (debugfile, "IMAP command pipeline full, draining\n")); 
     1295 
     1296      if (imap_exec (idata, NULL, 0) != IMAP_CMD_OK) 
     1297        dprint (1, (debugfile, "Error polling mailboxes\n")); 
     1298       
     1299      if (imap_cmd_queue (idata, command) < 0) { 
     1300        /* real trouble */ 
     1301        dprint (1, (debugfile, "Error queueing command\n")); 
     1302        return 0; 
     1303      } 
     1304    } 
     1305  } 
     1306 
     1307  if (lastdata && (imap_exec (lastdata, NULL, 0) != IMAP_CMD_OK)) 
     1308  { 
     1309    dprint (1, (debugfile, "Error polling mailboxes")); 
     1310    return 0; 
     1311  } 
     1312 
     1313  /* collect results */ 
     1314  for (mailbox = Incoming; mailbox; mailbox = mailbox->next) 
     1315  { 
     1316    if (mailbox->magic == M_IMAP && mailbox->new) 
     1317      buffies++; 
     1318  } 
     1319 
     1320  return buffies; 
     1321} 
     1322 
    12071323/* returns count of recent messages if new = 1, else count of total messages. 
    12081324 * (useful for at least postponed function) 
     
    12181334  char mbox[LONG_STRING]; 
    12191335  char mbox_unquoted[LONG_STRING]; 
    1220   char *s; 
    1221   int msgcount = 0; 
    12221336  int connflags = 0; 
    12231337  IMAP_MBOX mx; 
    12241338  int rc; 
     1339  BUFFY* buffy; 
    12251340   
    12261341  if (imap_parse_path (path, &mx)) 
     
    12671382  imap_cmd_start (idata, buf); 
    12681383 
    1269   do  
    1270   { 
    1271     if ((rc = imap_cmd_step (idata)) != IMAP_CMD_CONTINUE) 
    1272       break; 
    1273  
    1274     s = imap_next_word (idata->buf); 
    1275     if (ascii_strncasecmp ("STATUS", s, 6) == 0) 
    1276     { 
    1277       s = imap_next_word (s); 
    1278       /* The mailbox name may or may not be quoted here. We could try to  
    1279        * munge the server response and compare with quoted (or vise versa) 
    1280        * but it is probably more efficient to just strncmp against both. */ 
    1281       if (mutt_strncmp (mbox_unquoted, s, mutt_strlen (mbox_unquoted)) == 0 
    1282           || mutt_strncmp (mbox, s, mutt_strlen (mbox)) == 0) 
    1283       { 
    1284         s = imap_next_word (s); 
    1285         s = imap_next_word (s); 
    1286         if (isdigit ((unsigned char) *s)) 
    1287         { 
    1288           if (*s != '0') 
    1289           { 
    1290             msgcount = atoi(s); 
    1291             dprint (2, (debugfile, "%d new messages in %s\n", msgcount, path)); 
    1292           } 
    1293         } 
    1294       } 
    1295       else 
    1296         dprint (1, (debugfile, "imap_mailbox_check: STATUS response doesn't match requested mailbox.\n")); 
    1297     } 
    1298   } 
     1384  do 
     1385    rc = imap_cmd_step (idata); 
    12991386  while (rc == IMAP_CMD_CONTINUE); 
    13001387 
    1301   return msgcount; 
     1388  for (buffy = Incoming; buffy; buffy = buffy->next) 
     1389  { 
     1390    if (!strncmp (buffy->path, path, strlen (path))) 
     1391      return buffy->new; 
     1392  } 
     1393 
     1394  return 0; 
    13021395} 
    13031396 
  • imap/imap.h

    r4343 r4465  
    4040int imap_sync_mailbox (CONTEXT *ctx, int expunge, int *index_hint); 
    4141void imap_close_mailbox (CONTEXT *ctx); 
    42 int imap_buffy_check (char *path); 
     42int imap_buffy_check (int force); 
    4343int imap_mailbox_check (char *path, int new); 
    4444int imap_search (CONTEXT* ctx, const pattern_t* pat); 
  • imap/imap_private.h

    r4464 r4465  
    5151#define IMAP_CACHE_LEN 10 
    5252 
    53 /* number of commands that can be batched into a single request */ 
    54 #define IMAP_PIPELINE_DEPTH 10 
     53/* number of commands that can be batched into a single request 
     54 * ( - 1, for the easy way to detect ring buffer wrap) */ 
     55#define IMAP_PIPELINE_DEPTH 11 
    5556 
    5657#define SEQLEN 5 
     
    178179  int nextcmd; 
    179180  int lastcmd; 
     181  char* cmdbuf; 
     182  char* cmdtail; 
     183  unsigned int cmdbuflen; 
    180184 
    181185  /* The following data is all specific to the currently SELECTED mbox */ 
     
    219223 
    220224/* command.c */ 
     225int imap_cmd_queue (IMAP_DATA* idata, const char* cmdstr); 
    221226int imap_cmd_start (IMAP_DATA* idata, const char* cmd); 
    222227int imap_cmd_step (IMAP_DATA* idata); 
  • imap/util.c

    r4464 r4465  
    326326char* imap_get_qualifier (char* buf) 
    327327{ 
    328   const char *s = buf; 
     328  char *s = buf; 
    329329 
    330330  /* skip tag */