]> 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
-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.
@@ -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
-       %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
 
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
  *
- *     (c) 2005--2014 Martin Mares <mj@ucw.cz>
+ *     (c) 2005--2015 Martin Mares <mj@ucw.cz>
  */
 
 #define _GNU_SOURCE
 
-#include <stdio.h>
-#include <string.h>
-#include <stdlib.h>
 #include <ctype.h>
-#include <getopt.h>
+#include <dirent.h>
 #include <fcntl.h>
-#include <glob.h>
 #include <fnmatch.h>
-#include <sys/stat.h>
-#include <unistd.h>
+#include <getopt.h>
+#include <glob.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 <time.h>
+#include <unistd.h>
 
 #ifdef CONFIG_WIDE_CURSES
 #include <ncursesw/ncurses.h>
@@ -73,6 +74,8 @@ static struct options global_options = {
   .sender_personal = 1
 };
 
+#define MDIR_MAX_NAME_LEN 128
+
 struct mbox {
   cnode n;
   struct options o;
@@ -82,15 +85,17 @@ struct mbox {
   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;
-  int snippet_is_new;
+  int best_is_new;
   char sender_snippet[128];
   char subject_snippet[128];
+  char mdir_best[MDIR_MAX_NAME_LEN];
 };
 
 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)
 {
-  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 ";
 
+  b->best_time = st->st_mtime;
   if (!st->st_size)
     {
       b->total = b->new = b->flagged = 0;
@@ -401,8 +448,6 @@ scan_mbox(struct mbox *b, struct stat *st)
       return;
     }
 
-  /* FIXME: Should we do some locking? */
-
   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->snippet_is_new = 0;
+      b->best_is_new = 0;
     }
   else
     {
@@ -495,38 +540,10 @@ scan_mbox(struct mbox *b, struct stat *st)
       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))
            {
@@ -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);
-      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);
        }
 
@@ -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)
 {
@@ -632,27 +780,57 @@ scan(int notify)
          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");
+         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();
 
-         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();
+         b->force_refresh = 0;
        }
       else if (b->display_valid_until <= last_scan_time)
        {
@@ -661,7 +839,6 @@ scan(int notify)
        }
       else
        debug("not changed\n");
-      b->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->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;
       }
 
@@ -1005,7 +1182,7 @@ redraw_line(int i)
              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)