]> mj.ucw.cz Git - checkmail.git/commitdiff
Support for maildir format
authorMartin Mares <mj@ucw.cz>
Sun, 15 Feb 2015 00:10:50 +0000 (01:10 +0100)
committerMartin Mares <mj@ucw.cz>
Sun, 15 Feb 2015 00:10:50 +0000 (01:10 +0100)
Whenever the mbox pattern given as an argument matches a directory,
it is considered to be in maildir format. All features should work
with maildirs as well as with mailboxes.

README
cm.c

diff --git a/README b/README
index 4b4f6223ec6eac427948afcdc775a595a39de09e..6a965a2d77f3fd0d076e3cb02c46a6eeae0929b7 100644 (file)
--- a/README
+++ b/README
@@ -7,9 +7,9 @@
 ================================================================================
 
 Checkmail is nothing more than a smart textual menu allowing easy browsing
 ================================================================================
 
 Checkmail is nothing more than a smart textual menu allowing easy browsing
-through a set of mailboxes, displayed together with numbers of all messages
-and new messages. It is also able to notify the user that a new mail has
-arrived by means of beeps, keyboard LEDs and on-screen display.
+through a set of mailboxes (or maildirs), displayed together with numbers of
+all messages and new messages. It is also able to notify the user that a new
+mail has arrived by means of beeps, keyboard LEDs and on-screen display.
 
 Checkmail can be freely used and distributed according to the terms of the GNU
 General Public License version 2, as published by the Free Software Foundation.
 
 Checkmail can be freely used and distributed according to the terms of the GNU
 General Public License version 2, as published by the Free Software Foundation.
@@ -58,7 +58,7 @@ Each <text> can contain the following formatting sequences:
        %f              sender of the highest-priority new message
        %s              subject of the same message
        %n              total number of new messages
        %f              sender of the highest-priority new message
        %s              subject of the same message
        %n              total number of new messages
-       %m              the number of more new messages (i.e., %m-1)
+       %m              the number of more new messages (i.e., %n-1)
                        if it is 0, the whole <text> line is skipped
        %<width>...     you can add an optional maximum width in characters
 
                        if it is 0, the whole <text> line is skipped
        %<width>...     you can add an optional maximum width in characters
 
diff --git a/cm.c b/cm.c
index 1b65717b27b6734a9d48b98c07dd33eb96425f53..3648eda983c6fe7cb4a5bbdeb4ca6fefac827ee1 100644 (file)
--- a/cm.c
+++ b/cm.c
@@ -1,25 +1,26 @@
 /*
  *     Incoming Mail Checker
  *
 /*
  *     Incoming Mail Checker
  *
- *     (c) 2005--2014 Martin Mares <mj@ucw.cz>
+ *     (c) 2005--2015 Martin Mares <mj@ucw.cz>
  */
 
 #define _GNU_SOURCE
 
  */
 
 #define _GNU_SOURCE
 
-#include <stdio.h>
-#include <string.h>
-#include <stdlib.h>
 #include <ctype.h>
 #include <ctype.h>
-#include <getopt.h>
+#include <dirent.h>
 #include <fcntl.h>
 #include <fcntl.h>
-#include <glob.h>
 #include <fnmatch.h>
 #include <fnmatch.h>
-#include <sys/stat.h>
-#include <unistd.h>
+#include <getopt.h>
+#include <glob.h>
 #include <pwd.h>
 #include <pwd.h>
-#include <time.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/stat.h>
 #include <sys/types.h>
 #include <sys/wait.h>
 #include <sys/types.h>
 #include <sys/wait.h>
+#include <time.h>
+#include <unistd.h>
 
 #ifdef CONFIG_WIDE_CURSES
 #include <ncursesw/ncurses.h>
 
 #ifdef CONFIG_WIDE_CURSES
 #include <ncursesw/ncurses.h>
@@ -73,6 +74,8 @@ static struct options global_options = {
   .sender_personal = 1
 };
 
   .sender_personal = 1
 };
 
+#define MDIR_MAX_NAME_LEN 128
+
 struct mbox {
   cnode n;
   struct options o;
 struct mbox {
   cnode n;
   struct options o;
@@ -82,15 +85,17 @@ struct mbox {
   int scanning;
   int seen;
   time_t last_time;
   int scanning;
   int seen;
   time_t last_time;
+  time_t best_time;
   time_t display_valid_until;
   int last_size, last_pos;
   int total, new, flagged;
   int last_total, last_new, last_flagged;
   int last_beep_new;
   int force_refresh;
   time_t display_valid_until;
   int last_size, last_pos;
   int total, new, flagged;
   int last_total, last_new, last_flagged;
   int last_beep_new;
   int force_refresh;
-  int snippet_is_new;
+  int best_is_new;
   char sender_snippet[128];
   char subject_snippet[128];
   char sender_snippet[128];
   char subject_snippet[128];
+  char mdir_best[MDIR_MAX_NAME_LEN];
 };
 
 static clist mboxes;
 };
 
 static clist mboxes;
@@ -386,14 +391,56 @@ mb_skip(int len)
     }
 }
 
     }
 }
 
+#define HDR_BUF_SIZE 1024
+
+static int
+read_hdr_field(char *buf)
+{
+  uns i = 0;
+  for (;;)
+    {
+      int c = mb_get();
+      if (c < 0)
+       {
+         debug("[truncated] ");
+         return -1;
+       }
+      if (c == '\n')
+       {
+         if (!i)
+           break;
+         int fold = -1;
+         do
+           {
+             fold++;
+             c = mb_get();
+           }
+         while (c == ' ' || c == '\t');
+         mb_unget(c);
+         if (!fold)
+           break;
+         c = ' ';
+       }
+      if (c == '\r')
+       continue;
+      if (i < HDR_BUF_SIZE - 1)
+       buf[i++] = c;
+    }
+  buf[i] = 0;
+  if (debug_mode > 2)
+    debug("Header: <%s>\n", buf);
+  return buf[0] ? 1 : 0;
+}
+
 static void
 scan_mbox(struct mbox *b, struct stat *st)
 {
 static void
 scan_mbox(struct mbox *b, struct stat *st)
 {
-  char buf[1024], sender[1024], subject[1024];
+  char buf[HDR_BUF_SIZE], sender[HDR_BUF_SIZE], subject[HDR_BUF_SIZE];
   int c;
   int compressed = 0;
   const char from[] = "\nFrom ";
 
   int c;
   int compressed = 0;
   const char from[] = "\nFrom ";
 
+  b->best_time = st->st_mtime;
   if (!st->st_size)
     {
       b->total = b->new = b->flagged = 0;
   if (!st->st_size)
     {
       b->total = b->new = b->flagged = 0;
@@ -401,8 +448,6 @@ scan_mbox(struct mbox *b, struct stat *st)
       return;
     }
 
       return;
     }
 
-  /* FIXME: Should we do some locking? */
-
   mb_fd = open(b->path, O_RDONLY);
   if (mb_fd < 0)
     {
   mb_fd = open(b->path, O_RDONLY);
   if (mb_fd < 0)
     {
@@ -468,7 +513,7 @@ scan_mbox(struct mbox *b, struct stat *st)
        }
       b->total = b->new = b->flagged = 0;
       b->last_total = b->last_new = b->last_flagged = 0;
        }
       b->total = b->new = b->flagged = 0;
       b->last_total = b->last_new = b->last_flagged = 0;
-      b->snippet_is_new = 0;
+      b->best_is_new = 0;
     }
   else
     {
     }
   else
     {
@@ -495,38 +540,10 @@ scan_mbox(struct mbox *b, struct stat *st)
       subject[0] = 0;
       for (;;)
        {
       subject[0] = 0;
       for (;;)
        {
-         uns i = 0;
-         for (;;)
-           {
-             c = mb_get();
-             if (c < 0)
-               {
-                 debug("[truncated] ");
-                 goto done;
-               }
-             if (c == '\n')
-               {
-                 if (!i)
-                   break;
-                 int fold = -1;
-                 do
-                   {
-                     fold++;
-                     c = mb_get();
-                   }
-                 while (c == ' ' || c == '\t');
-                 mb_unget(c);
-                 if (!fold)
-                   break;
-                 c = ' ';
-               }
-             if (c == '\r')
-               continue;
-             if (i < sizeof(buf) - 1)
-               buf[i++] = c;
-           }
-         buf[i] = 0;
-         if (!buf[0])
+         int c = read_hdr_field(buf);
+         if (c < 0)
+           goto done;
+         if (!c)
            break;
          if (!strncasecmp(buf, "Status:", 7))
            {
            break;
          if (!strncasecmp(buf, "Status:", 7))
            {
@@ -550,9 +567,9 @@ scan_mbox(struct mbox *b, struct stat *st)
        b->flagged++;
       if (debug_mode > 1)
        debug("new=%d flagged=%d len=%d sender=<%s> subject=<%s>\n", new, flagged, content_length, sender, subject);
        b->flagged++;
       if (debug_mode > 1)
        debug("new=%d flagged=%d len=%d sender=<%s> subject=<%s>\n", new, flagged, content_length, sender, subject);
-      if (new || (flagged && !b->snippet_is_new))
+      if (new || (flagged && !b->best_is_new))
        {
        {
-         b->snippet_is_new = new;
+         b->best_is_new = new;
          prepare_snippets(b, sender, subject);
        }
 
          prepare_snippets(b, sender, subject);
        }
 
@@ -580,6 +597,137 @@ scan_mbox(struct mbox *b, struct stat *st)
     }
 }
 
     }
 }
 
+static time_t
+mdir_mtime(struct mbox *b)
+{
+  time_t mtime = 0;
+  char path[strlen(b->path) + 5];
+
+  for (int new=0; new<2; new++)
+    {
+      sprintf(path, "%s/%s", b->path, (new ? "new" : "cur"));
+      struct stat st;
+      if (stat(path, &st) < 0)
+       debug("[cannot stat %s] ", path);
+      else if (st.st_mtime > mtime)
+       mtime = st.st_mtime;
+    }
+  return mtime;
+}
+
+static void
+mdir_get_snippet(struct mbox *b)
+{
+  char buf[HDR_BUF_SIZE], sender[HDR_BUF_SIZE], subject[HDR_BUF_SIZE];
+  sender[0] = 0;
+  subject[0] = 0;
+
+  char path[strlen(b->path) + MDIR_MAX_NAME_LEN];
+  sprintf(path, "%s/%s", b->path, b->mdir_best);
+  mb_fd = open(path, O_RDONLY);
+  if (mb_fd < 0)
+    {
+      debug("[open failed: %m] ");
+      prepare_snippets(b, sender, subject);
+      return;
+    }
+  mb_seekable = 1;
+  mb_reset(0);
+
+  while (read_hdr_field(buf) > 0)
+    {
+      if (!strncasecmp(buf, "From:", 5))
+       strcpy(sender, buf+5);
+      else if (!strncasecmp(buf, "Subject:", 8))
+       strcpy(subject, buf+8);
+    }
+
+  close(mb_fd);
+  prepare_snippets(b, sender, subject);
+}
+
+static void
+scan_mdir(struct mbox *b)
+{
+  int dir_len = strlen(b->path);
+  char path[dir_len + MDIR_MAX_NAME_LEN];
+  strcpy(path, b->path);
+
+  b->total = b->new = b->flagged = 0;
+  b->best_time = 0;
+  b->best_is_new = 0;
+
+  for (int new=0; new<2; new++)
+    {
+      strcpy(path + dir_len, (new ? "/new" : "/cur"));
+      DIR *d = opendir(path);
+      if (!d)
+       {
+         debug("[cannot open %s: %m] ");
+         continue;
+       }
+      struct dirent *de;
+      while (de = readdir(d))
+       {
+         char *name = de->d_name;
+         if (name[0] == '.' || strlen(name) + 10 > MDIR_MAX_NAME_LEN)
+           continue;
+         path[dir_len + 4] = '/';
+         strcpy(path + dir_len + 5, name);
+
+         char *colon = strchr(name, ':');
+         int seen = 0;
+         int flagged = 0;
+         if (colon && colon[1] == '2' && colon[2] == ',')
+           {
+             for (int i=3; colon[i]; i++)
+               switch (colon[i])
+                 {
+                 case 'S':
+                   seen = 1;
+                   break;
+                 case 'F':
+                   flagged = 1;
+                   break;
+                 }
+           }
+
+         int is_new = new;
+         if (b->o.unread_is_new && !seen)
+           is_new = 1;
+
+         b->total++;
+         if (is_new)
+           b->new++;
+         if (flagged)
+           b->flagged++;
+         if (debug_mode > 1)
+           debug("%s: new=%d flagged=%d seen=%d\n", path, is_new, flagged, seen);
+
+         if (is_new || flagged)
+           {
+             // Need to pick the best message to display
+             struct stat st;
+             if (stat(path, &st) < 0)
+               debug("[cannot stat %s: %m] ", path);
+             else if (is_new > b->best_is_new || is_new == b->best_is_new && st.st_mtime > b->best_time)
+               {
+                 b->best_is_new = is_new;
+                 b->best_time = st.st_mtime;
+                 strcpy(b->mdir_best, path + dir_len + 1);
+               }
+           }
+       }
+      closedir(d);
+    }
+
+  if (b->best_time && b->o.snippets)
+    {
+      debug("best <%s> ", b->mdir_best);
+      mdir_get_snippet(b);
+    }
+}
+
 static void
 scan(int notify)
 {
 static void
 scan(int notify)
 {
@@ -632,27 +780,57 @@ scan(int notify)
          debug("inactive\n");
          continue;
        }
          debug("inactive\n");
          continue;
        }
-      if (force_refresh)
-       b->force_refresh = 1;
+      b->force_refresh = force_refresh;
       if (stat(b->path, &st) < 0)
        {
          b->total = b->new = b->flagged = -1;
          debug("%m\n");
       if (stat(b->path, &st) < 0)
        {
          b->total = b->new = b->flagged = -1;
          debug("%m\n");
+         continue;
        }
        }
-      else if (!b->last_time || st.st_mtime != b->last_time || st.st_size != b->last_size || b->force_refresh)
+
+      time_t current_mtime;
+      int current_size;
+      int is_mdir;
+      if (S_ISREG(st.st_mode))
+       {
+         // Regular mailbox
+         is_mdir = 0;
+         current_mtime = st.st_mtime;
+         current_size = st.st_size;
+         debug("[mbox] ");
+       }
+      else if (S_ISDIR(st.st_mode))
+       {
+         // Maildir
+         is_mdir = 1;
+         current_mtime = mdir_mtime(b);
+         current_size = 0;
+         debug("[mdir] ");
+       }
+      else
+       {
+         debug("neither file nor directory\n");
+         continue;
+       }
+
+      if (!b->last_time || current_mtime != b->last_time || current_size != b->last_size || b->force_refresh)
        {
          b->scanning = 1;
          redraw_line(b->index);
          refresh();
 
        {
          b->scanning = 1;
          redraw_line(b->index);
          refresh();
 
-         scan_mbox(b, &st);
-         b->last_time = st.st_mtime;
-         b->last_size = st.st_size;
+         if (is_mdir)
+           scan_mdir(b);
+         else
+           scan_mbox(b, &st);
+         b->last_time = current_mtime;
+         b->last_size = current_size;
          debug("%d %d %d (stopped at %d of %d)\n", b->total, b->new, b->flagged, b->last_pos, b->last_size);
 
          b->scanning = 0;
          redraw_line(b->index);
          refresh();
          debug("%d %d %d (stopped at %d of %d)\n", b->total, b->new, b->flagged, b->last_pos, b->last_size);
 
          b->scanning = 0;
          redraw_line(b->index);
          refresh();
+         b->force_refresh = 0;
        }
       else if (b->display_valid_until <= last_scan_time)
        {
        }
       else if (b->display_valid_until <= last_scan_time)
        {
@@ -661,7 +839,6 @@ scan(int notify)
        }
       else
        debug("not changed\n");
        }
       else
        debug("not changed\n");
-      b->force_refresh = 0;
     }
   force_refresh = 0;
 
     }
   force_refresh = 0;
 
@@ -890,7 +1067,7 @@ rethink_osd(int notify)
     if (b->o.osd > 0)
       {
        p.total_new += b->new;
     if (b->o.osd > 0)
       {
        p.total_new += b->new;
-       if (b->new && b->last_time > osd_last_time && (!p.mbox || p.mbox->o.priority < b->o.priority))
+       if (b->new && b->best_time > osd_last_time && (!p.mbox || p.mbox->o.priority < b->o.priority))
          p.mbox = b;
       }
 
          p.mbox = b;
       }
 
@@ -1005,7 +1182,7 @@ redraw_line(int i)
              attrset(attrs[cc][hi][M_NEW]);
              printw("%6d  ", b->new);
              attrset(attrs[cc][hi][M_IDLE]);
              attrset(attrs[cc][hi][M_NEW]);
              printw("%6d  ", b->new);
              attrset(attrs[cc][hi][M_IDLE]);
-             int age = (last_scan_time - b->last_time);
+             int age = (last_scan_time - b->best_time);
              if (age < 0)
                age = 0;
              if (age < 3600)
              if (age < 0)
                age = 0;
              if (age < 3600)