Logo Search packages:      
Sourcecode: mailutils version File versions

sortm.c

/* GNU Mailutils -- a suite of utilities for electronic mail
   Copyright (C) 2003 Free Software Foundation, Inc.

   GNU Mailutils is free software; you can redistribute it and/or modify
   it under the terms of the GNU General Public License as published by
   the Free Software Foundation; either version 2, or (at your option)
   any later version.

   GNU Mailutils is distributed in the hope that it will be useful,
   but WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   GNU General Public License for more details.

   You should have received a copy of the GNU General Public License
   along with GNU Mailutils; if not, write to the Free Software
   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307  USA  */

/* MH sortm command */

#include <mh.h>
#include <sys/stat.h>
#include <unistd.h>
#include <signal.h>

const char *program_version = "sortm (" PACKAGE_STRING ")";
static char doc[] = N_("GNU MH sortm\v"
"Use -help to obtain the list of traditional MH options.");
static char args_doc[] = N_("[msgs]");

#define ARG_QUICKSORT 1024
#define ARG_SHELL     1025

/* GNU options */
static struct argp_option options[] = {
  {"folder",        ARG_FOLDER,        N_("FOLDER"), 0,
   N_("Specify folder to operate upon")},

  {N_("Setting sort keys:"), 0,  NULL, OPTION_DOC,  NULL, 0},
  {"datefield",     ARG_DATEFIELD,     N_("STRING"), 0,
   N_("Sort on the date field (default `Date:')"), 10},
  {"nodatefield",   ARG_NODATEFIELD,   NULL,       0,
   N_("Undo the effect of the last --datefield option"), 10},
  {"limit",         ARG_LIMIT,         N_("DAYS"), 0,
   N_("Consider two datefields equal if their difference lies within the given nuber of DAYS."), 11},
  {"nolimit",       ARG_NOLIMIT,       NULL,       0,
   N_("Undo the effect of the last --limit option"), 11},
  {"textfield",     ARG_TEXTFIELD,     N_("STRING"), 15,
   N_("Sort on the text field"), 1},
  {"notextfield",   ARG_NOTEXTFIELD,   NULL,       0,
   N_("Undo the effect of the last --textfield option"), 15},
  {"numfield",      ARG_NUMFIELD,      N_("STRING"), 0,
   N_("Sort on the numeric field"), 16},
  
  {N_("Actions:"), 0,  NULL, OPTION_DOC,  NULL, 16},
  {"reorder", ARG_REORDER,    0, 0,
   N_("Reorder the messages (default)"), 20 },
  {"dry-run", ARG_DRY_RUN,    0, 0,
   N_("Do not do anything, only show what would have been done"), 20 },
  {"list",    ARG_LIST,   0, 0,
   N_("List the sorted messages"), 20 },
  {"form",    ARG_FORM, N_("FILE"),   0,
   N_("Read format from given file"), 23},
  {"format",  ARG_FORMAT, N_("FORMAT"), 0,
   N_("Use this format string"), 23},

  {"verbose",       ARG_VERBOSE,       N_("BOOL"), OPTION_ARG_OPTIONAL,
   N_("Verbosely list executed actions"), 30 },
  {"noverbose",     ARG_NOVERBOSE,     NULL, OPTION_HIDDEN, "" },

  {N_("Select sort algorithm:"), 0,  NULL, OPTION_DOC,  NULL, 30},

  {"shell",     ARG_SHELL,      0, 0,
   N_("Use shell algorithm"), 40 },
  {"quicksort", ARG_QUICKSORT,  0, 0,
   N_("Use quicksort algorithm (default)"), 40 },

  {"license", ARG_LICENSE, 0,      0,
   N_("Display software license"), -1},

  { NULL },
};

/* Traditional MH options */
struct mh_option mh_option[] = {
  {"datefield",     1, 0, "field" },
  {"nodatefield",   3, 0, 0 },
  {"textfield",     1, 0, "field" },
  {"notextfield",   3, 0, 0 },
  {"limit",         1, 0, "days" },
  {"nolimit",       3, 0, 0 },
  {"verbose",       1, MH_OPT_BOOL, NULL},
  { NULL },
};

static int limit;
static int verbose;
static mailbox_t mbox;
static const char *mbox_path;
static mh_msgset_t msgset;

#define ACTION_REORDER   0
#define ACTION_DRY_RUN   1
#define ACTION_LIST      2  

static int algorithm = ARG_QUICKSORT;
static int action = ACTION_REORDER;
static char *format_str = mh_list_format;
static mh_format_t format;

typedef int (*compfun) __PMT((void *, void *));
static void addop __P((char *field, compfun comp));
static void remop __P((compfun comp));
static int comp_text __P((void *a, void *b));
static int comp_date __P((void *a, void *b));
static int comp_number __P((void *a, void *b));

static int
opt_handler (int key, char *arg, void *unused, struct argp_state *state)
{
  switch (key)
    {
    case ARG_FOLDER:
      current_folder = arg;
      break;
      
    case ARG_DATEFIELD:
      addop (arg, comp_date);
      break;
      
    case ARG_NUMFIELD:
      addop (arg, comp_number);
      break;

    case ARG_NODATEFIELD:
      remop (comp_date);
      break;
      
    case ARG_TEXTFIELD:
      addop (arg, comp_text);
      break;
      
    case ARG_NOTEXTFIELD:
      remop (comp_text);
      break;
      
    case ARG_LIMIT:
      limit = strtoul (arg, NULL, 0);
      break;
      
    case ARG_NOLIMIT:
      limit = -1;
      break;
      
    case ARG_VERBOSE:
      if (!arg || isalpha (arg[0]))
      verbose = is_true (arg);
      else
      verbose = arg[0] - '0';
      break;
      
    case ARG_NOVERBOSE:
      verbose = 0;
      break;
      
    case ARG_FORM:
      mh_read_formfile (arg, &format_str);
      break;
      
    case ARG_FORMAT:
      format_str = arg;
      break;

    case ARG_REORDER:
      action = ACTION_REORDER;
      break;
      
    case ARG_LIST:
      action = ACTION_LIST;
      break;

    case ARG_DRY_RUN:
      action = ACTION_DRY_RUN;
      if (!verbose)
      verbose = 1;
      break;

    case ARG_SHELL:
    case ARG_QUICKSORT:
      algorithm = key;
      break;
      
    case ARG_LICENSE:
      mh_license (argp_program_version);
      break;

    default:
      return 1;
    }
  return 0;
}


/* *********************** Comparison functions **************************** */
struct comp_op {
  char *field;
  compfun comp;
};

static list_t oplist;

static void
addop (char *field, compfun comp)
{
  struct comp_op *op = xmalloc (sizeof (*op));
  
  if (!oplist && list_create (&oplist))
    {
      mh_error (_("can't create operation list"));
      exit (1);
    }
  op->field = field;
  op->comp = comp;
  list_append (oplist, op);
}

struct rem_data {
  struct comp_op *op;
  compfun comp;
};

static int
rem_action (void *item, void *data)
{
  struct comp_op *op = item;
  struct rem_data *d = data;
  if (d->comp == op->comp)
    d->op = op;
  return 0;
}

static void
remop (compfun comp)
{
  struct rem_data d;
  d.comp = comp;
  d.op = NULL;
  list_do (oplist, rem_action, &d);
  list_remove (oplist, d.op);
  free (d.op);
}

struct comp_data {
  int r;
  message_t m[2];
};

static int
compare_action (void *item, void *data)
{
  struct comp_op *op = item;
  struct comp_data *dp = data;
  char *a, *ap, *b, *bp;
  header_t h;
  
  if (message_get_header (dp->m[0], &h)
      || header_aget_value (h, op->field, &a))
    return 0;

  if (message_get_header (dp->m[1], &h)
      || header_aget_value (h, op->field, &b))
    {
      free (a);
      return 0;
    }

  ap = a;
  bp = b;
  if (strcasecmp (op->field, MU_HEADER_SUBJECT) == 0)
    {
      if (strncasecmp (ap, "re:", 3) == 0)
      ap += 3;
      if (strncasecmp (b, "re:", 3) == 0)
      bp += 3;
    }
  
  dp->r = op->comp (ap, bp);
  free (a);
  free (b);

  return dp->r; /* go on until the difference is found */
}

static int
compare_messages (message_t a, message_t b)
{
  struct comp_data d;

  d.r = 0;
  d.m[0] = a;
  d.m[1] = b;
  list_do (oplist, compare_action, &d);
  if (verbose > 1)
    fprintf (stderr, "%d\n", d.r);
  return d.r;
}

static int
comp_text (void *a, void *b)
{
  return strcasecmp (a, b);
}

static int
comp_number (void *a, void *b)
{
  long na, nb;

  na = strtol (a, NULL, 0);
  nb = strtol (b, NULL, 0);
  if (na > nb)
    return 1;
  else if (na < nb)
    return -1;
  return 0;
}

/*FIXME: Also used in imap4d*/
static int
_parse_822_date (char *date, time_t * timep)
{
  struct tm tm;
  mu_timezone tz;
  const char *p = date;

  if (parse822_date_time (&p, date + strlen (date), &tm, &tz) == 0)
    {
      *timep = mu_tm2time (&tm, &tz);
      return 0;
    }
  return 1;
}

static int
comp_date (void *a, void *b)
{
  time_t ta, tb;
  
  if (_parse_822_date (a, &ta) || _parse_822_date (b, &tb))
    return 0;

  if (ta < tb)
    {
      if (limit && tb - ta <= limit)
      return 0;
      return -1;
    }
  else if (ta > tb)
    {
      if (limit && ta - tb <= limit)
      return 0;
      return 1;
    }
  return 0;
}


/* *********************** Sorting routines ***************************** */

static int
comp0 (size_t na, size_t nb)
{
  message_t a, b;

  if (mailbox_get_message (mbox, na, &a)
      || mailbox_get_message (mbox, nb, &b))
    return 0;
  if (verbose > 1)
    fprintf (stderr,
           _("comparing messages %lu and %lu: "),
           (unsigned long) na,
           (unsigned long) nb);
  return compare_messages (a, b);
}

int
comp (const void *a, const void *b)
{
  return comp0 (* (size_t*) a, * (size_t*) b);
}


/* ****************************** Shell sort ****************************** */
#define prevdst(h) ((h)-1)/3

static int
startdst (unsigned count, int *num)
{
  int i, h;

  for (i = h = 1; 9*h + 4 < count; i++, h = 3*h+1)
    ;
  *num = i;
  return h;
}

void
shell_sort ()
{
    int h, s, i, j;
    size_t hold;

    for (h = startdst (msgset.count, &s); s > 0; s--, h = prevdst (h))
      {
      if (verbose > 1)
        fprintf (stderr, _("distance %d\n"), h);
        for (j = h; j < msgset.count; j++)
        {
            hold = msgset.list[j];
            for (i = j - h;
             i >= 0 && comp0 (hold, msgset.list[i]) < 0; i -= h)
            msgset.list[i + h] = msgset.list[i];
          msgset.list[i + h] = hold;
        }
      }
}


/* ****************************** Actions ********************************* */

void
list_message (size_t num)
{
  message_t msg = NULL;
  char *buffer;
  mailbox_get_message (mbox, num, &msg);
  mh_format (&format, msg, num, 76, &buffer);
  printf ("%s\n", buffer);
  free (buffer);
}

void
swap_message (size_t a, size_t b)
{
  char *path_a, *path_b;
  char *tmp;
  
  asprintf (&path_a, "%s/%lu", mbox_path, (unsigned long) a);
  asprintf (&path_b, "%s/%lu", mbox_path, (unsigned long) b);
  tmp = mu_tempname (mbox_path);
  rename (path_a, tmp);
  unlink (path_a);
  rename (path_b, path_a);
  unlink (path_b);
  rename (tmp, path_b);
  free (tmp);
}

void
transpose(size_t i, size_t n)
{
  size_t j;

  for (j = i+1; j < msgset.count; j++)
    if (msgset.list[j] == n)
      {
      size_t t = msgset.list[i];
      msgset.list[i] = msgset.list[j];
      msgset.list[j] = t;
      break;
      }
}
  
static int got_signal;

RETSIGTYPE
sighandler (int sig)
{
  got_signal = 1;
}

void
sort ()
{
  size_t *oldlist, i;
  oldlist = xmalloc (msgset.count * sizeof (*oldlist));
  memcpy (oldlist, msgset.list, msgset.count * sizeof (*oldlist));

  switch (algorithm)
    {
    case ARG_QUICKSORT:
      qsort(msgset.list, msgset.count, sizeof(msgset.list[0]),
          comp);
      break;

    case ARG_SHELL:
      shell_sort();
      break;
    }

  switch (action)
    {
    case ACTION_LIST:
      for (i = 0; i < msgset.count; i++)
      list_message (msgset.list[i]);
      break;

    default:
      /* Install signal handlers */
      signal (SIGINT, sighandler);
      signal (SIGQUIT, sighandler);
      signal (SIGTERM, sighandler);
      
      if (verbose)
      fprintf (stderr, _("Transpositions:\n"));
      for (i = 0, got_signal = 0; !got_signal && i < msgset.count; i++)
      {
        if (msgset.list[i] != oldlist[i])
          {
            size_t old_num, new_num;
            message_t msg;

            mailbox_get_message (mbox, oldlist[i], &msg);
            mh_message_number (msg, &old_num);
            mailbox_get_message (mbox, msgset.list[i], &msg);
            mh_message_number (msg, &new_num);
            transpose (i, oldlist[i]);
            if (verbose)
            fprintf (stderr, "{%lu, %lu}\n",
                   (unsigned long) old_num,
                   (unsigned long) new_num);
            if (action == ACTION_REORDER)
            swap_message (old_num, new_num);
          }
      }
    }
}


/* Main */

int
main (int argc, char **argv)
{
  int index;
  url_t url;
  
  mu_init_nls ();
  mu_argp_init (program_version, NULL);
  mh_argp_parse (&argc, &argv, 0, options, mh_option,
             args_doc, doc, opt_handler, NULL, &index);
  if (!oplist)
    addop ("date", comp_date);

  if (action == ACTION_LIST && mh_format_parse (format_str, &format))
    {
      mh_error (_("Bad format string"));
      exit (1);
    }
  
  mbox = mh_open_folder (current_folder, 0);
  mailbox_get_url (mbox, &url);
  mbox_path = url_to_string (url);
  if (memcmp (mbox_path, "mh:", 3) == 0)
    mbox_path += 3;
  
  argc -= index;
  argv += index;

  mh_msgset_parse (mbox, &msgset, argc, argv, "all");
  sort (mbox, msgset);
  return 0;
}

Generated by  Doxygen 1.6.0   Back to index