/* GNU Mailutils -- a suite of utilities for electronic mail Copyright (C) 2003-2021 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 3, 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, see . */ /* MH sortm command */ #include #include #include #include static char prog_doc[] = N_("Sort GNU MH messages"); static char args_doc[] = N_("[MSGLIST]"); static int limit; static int verbose; static int width; static mu_mailbox_t mbox; static const char *mbox_path; static size_t *msgarr; static size_t msgcount; static size_t current_num; enum { ACTION_REORDER, ACTION_DRY_RUN, ACTION_LIST }; enum { algo_quicksort, algo_shell }; static int algorithm = algo_quicksort; static int action = ACTION_REORDER; static mh_format_t format; static mh_fvm_t fvm; typedef int (*compfun) (void *, void *); static void addop (char const *field, compfun comp); static void remop (compfun comp); static int comp_text (void *a, void *b); static int comp_date (void *a, void *b); static int comp_number (void *a, void *b); static void add_datefield (struct mu_parseopt *po, struct mu_option *opt, char const *arg) { addop (arg, comp_date); } static void add_numfield (struct mu_parseopt *po, struct mu_option *opt, char const *arg) { addop (arg, comp_number); } static void add_textfield (struct mu_parseopt *po, struct mu_option *opt, char const *arg) { addop (arg, comp_text); } static void rem_datefield (struct mu_parseopt *po, struct mu_option *opt, char const *arg) { remop (comp_date); } static void rem_numfield (struct mu_parseopt *po, struct mu_option *opt, char const *arg) { remop (comp_number); } static void rem_textfield (struct mu_parseopt *po, struct mu_option *opt, char const *arg) { remop (comp_text); } static void set_action_reorder (struct mu_parseopt *po, struct mu_option *opt, char const *arg) { action = ACTION_REORDER; } static void set_action_list (struct mu_parseopt *po, struct mu_option *opt, char const *arg) { action = ACTION_LIST; } static void set_action_dry_run (struct mu_parseopt *po, struct mu_option *opt, char const *arg) { action = ACTION_DRY_RUN; if (!verbose) verbose = 1; } static void set_algo_shell (struct mu_parseopt *po, struct mu_option *opt, char const *arg) { algorithm = algo_shell; } static void set_algo_quicksort (struct mu_parseopt *po, struct mu_option *opt, char const *arg) { algorithm = algo_quicksort; } static struct mu_option options[] = { { "width", 0, N_("NUMBER"), MU_OPTION_DEFAULT, N_("set output width (for -list)"), mu_c_int, &width }, MU_OPTION_GROUP (N_("Setting sort keys:")), { "datefield", 0, N_("STRING"), MU_OPTION_DEFAULT, N_("sort on the date field (default `Date:')"), mu_c_string, NULL, add_datefield }, { "nodatefield", 0, NULL, MU_OPTION_DEFAULT, N_("don't sort on the date field"), mu_c_string, NULL, rem_datefield }, { "limit", 0, N_("DAYS"), MU_OPTION_DEFAULT, N_("consider two datefields equal if their difference lies within the given nuber of DAYS."), mu_c_int, &limit }, { "nolimit", 0, NULL, MU_OPTION_DEFAULT, N_("undo the effect of the last -limit option"), mu_c_int, &limit, NULL, "-1" }, { "textfield", 0, N_("STRING"), MU_OPTION_DEFAULT, N_("sort on the text field"), mu_c_string, NULL, add_textfield }, { "notextfield", 0, NULL, MU_OPTION_DEFAULT, N_("don't sort on the text field"), mu_c_string, NULL, rem_textfield }, { "numfield", 0, N_("STRING"), MU_OPTION_DEFAULT, N_("sort on the numeric field"), mu_c_string, NULL, add_numfield }, { "nonumfield", 0, NULL, MU_OPTION_DEFAULT, N_("don't sort on the numeric field"), mu_c_string, NULL, rem_numfield }, MU_OPTION_GROUP (N_("Actions:")), { "reorder", 0, NULL, MU_OPTION_DEFAULT, N_("reorder the messages (default)"), mu_c_string, NULL, set_action_reorder }, { "dry-run", 0, NULL, MU_OPTION_DEFAULT, N_("do not do anything, only show what would have been done"), mu_c_string, NULL, set_action_dry_run }, { "list", 0, NULL, MU_OPTION_DEFAULT, N_("list the sorted messages"), mu_c_string, NULL, set_action_list }, { "form", 0, N_("FILE"), MU_OPTION_DEFAULT, N_("read format from given file"), mu_c_string, &format, mh_opt_parse_formfile }, { "format", 0, N_("FORMAT"), MU_OPTION_DEFAULT, N_("use this format string"), mu_c_string, &format, mh_opt_parse_format }, { "verbose", 0, NULL, MU_OPTION_DEFAULT, N_("verbosely list executed actions"), mu_c_bool, &verbose }, MU_OPTION_GROUP (N_("Select sort algorithm:")), { "shell", 0, NULL, MU_OPTION_DEFAULT, N_("use shell algorithm"), mu_c_string, NULL, set_algo_shell }, { "quicksort", 0, NULL, MU_OPTION_DEFAULT, N_("use quicksort algorithm (default)"), mu_c_string, NULL, set_algo_quicksort }, MU_OPTION_END }; /* *********************** Comparison functions **************************** */ struct comp_op { char const *field; compfun comp; }; static mu_list_t oplist; static void addop (char const *field, compfun comp) { struct comp_op *op = mu_alloc (sizeof (*op)); if (!oplist) { if (mu_list_create (&oplist)) { mu_error (_("can't create operation list")); exit (1); } mu_list_set_destroy_item (oplist, mu_list_free_item); } op->field = field; op->comp = comp; mu_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; mu_list_foreach (oplist, rem_action, &d); mu_list_remove (oplist, d.op); } struct comp_data { int r; mu_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; mu_header_t h; if (mu_message_get_header (dp->m[0], &h) || mu_header_aget_value (h, op->field, &a)) return 0; if (mu_message_get_header (dp->m[1], &h) || mu_header_aget_value (h, op->field, &b)) { free (a); return 0; } ap = a; bp = b; if (mu_c_strcasecmp (op->field, MU_HEADER_SUBJECT) == 0) { if (mu_c_strncasecmp (ap, "re:", 3) == 0) ap += 3; if (mu_c_strncasecmp (b, "re:", 3) == 0) bp += 3; } dp->r = op->comp (ap, bp); free (a); free (b); return dp->r ? MU_ERR_USER0 : 0; /* go on until the difference is found */ } static int compare_messages (mu_message_t a, mu_message_t b, size_t anum, size_t bnum) { struct comp_data d; d.r = 0; d.m[0] = a; d.m[1] = b; mu_list_foreach (oplist, compare_action, &d); if (d.r == 0) { if (anum < bnum) d.r = -1; else if (anum > bnum) d.r = 1; } if (verbose > 1) fprintf (stderr, "%d\n", d.r); return d.r; } static int comp_text (void *a, void *b) { return mu_c_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; struct mu_timezone tz; const char *p = date; if (mu_parse822_date_time (&p, date + strlen (date), &tm, &tz) == 0) { *timep = mu_datetime_to_utc (&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) { mu_message_t a, b; if (mu_mailbox_get_message (mbox, na, &a) || mu_mailbox_get_message (mbox, nb, &b)) return 0; if (verbose > 1) fprintf (stderr, _("comparing messages %s and %s: "), mu_umaxtostr (0, na), mu_umaxtostr (1, nb)); return compare_messages (a, b, na, nb); } 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 (msgcount, &s); s > 0; s--, h = prevdst (h)) { if (verbose > 1) fprintf (stderr, _("distance %d\n"), h); for (j = h; j < msgcount; j++) { hold = msgarr[j]; for (i = j - h; i >= 0 && comp0 (hold, msgarr[i]) < 0; i -= h) msgarr[i + h] = msgarr[i]; msgarr[i + h] = hold; } } } /* ****************************** Actions ********************************* */ void list_message (size_t num) { mu_message_t msg = NULL; mu_mailbox_get_message (mbox, num, &msg); mh_fvm_run (fvm, msg); } void swap_message (size_t a, size_t b) { char *path_a, *path_b; char *tmp; path_a = mh_safe_make_file_name (mbox_path, mu_umaxtostr (0, a)); path_b = mh_safe_make_file_name (mbox_path, mu_umaxtostr (1, 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 < msgcount; j++) if (msgarr[j] == n) { size_t t = msgarr[i]; msgarr[i] = msgarr[j]; msgarr[j] = t; break; } } static int got_signal; RETSIGTYPE sighandler (int sig) { got_signal = 1; } void sort (void) { size_t *oldlist, i; oldlist = mu_alloc (msgcount * sizeof (*oldlist)); memcpy (oldlist, msgarr, msgcount * sizeof (*oldlist)); switch (algorithm) { case algo_quicksort: qsort (msgarr, msgcount, sizeof (msgarr[0]), comp); break; case algo_shell: shell_sort (); break; } switch (action) { case ACTION_LIST: for (i = 0; i < msgcount; i++) list_message (msgarr[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 < msgcount; i++) { if (msgarr[i] != oldlist[i]) { size_t old_num, new_num; mu_message_t msg; mu_mailbox_get_message (mbox, oldlist[i], &msg); mh_message_number (msg, &old_num); mu_mailbox_get_message (mbox, msgarr[i], &msg); mh_message_number (msg, &new_num); transpose (i, oldlist[i]); if (verbose) { fprintf (stderr, "{%s, %s}", mu_umaxtostr (0, old_num), mu_umaxtostr (1, new_num)); } if (old_num == current_num) { if (verbose) fputc ('*', stderr); current_num = new_num; } else if (new_num == current_num) { if (verbose) fputc ('*', stderr); current_num = old_num; } if (verbose) fputc ('\n', stderr); if (action == ACTION_REORDER) swap_message (old_num, new_num); } } } if (action == ACTION_REORDER) { mu_mailbox_uidvalidity_reset (mbox); mu_mailbox_close (mbox); mu_mailbox_open (mbox, MU_STREAM_RDWR); mh_mailbox_set_cur (mbox, current_num); } } static int _add_msgno (size_t n, void *data) { size_t *pidx = data; msgarr[*pidx] = n; ++*pidx; return 0; } static void fill_msgarr (mu_msgset_t msgset) { size_t i; int rc; rc = mu_msgset_count (msgset, &msgcount); if (rc) { mu_diag_funcall (MU_DIAG_ERROR, "mu_msgset_count", NULL, rc); exit (1); } msgarr = mu_calloc (msgcount, sizeof (msgarr[0])); i = 0; rc = mu_msgset_foreach_msgno (msgset, _add_msgno, &i); if (rc) { mu_diag_funcall (MU_DIAG_ERROR, "mu_msgset_foreach_msgno", NULL, rc); exit (1); } } int main (int argc, char **argv) { mu_url_t url; mu_msgset_t msgset; mh_getopt (&argc, &argv, options, MH_GETOPT_DEFAULT_FOLDER, args_doc, prog_doc, NULL); if (!oplist) addop ("date", comp_date); if (action == ACTION_LIST) { if (!format) format = mh_scan_format (); mh_fvm_create (&fvm, MH_FMT_FORCENL); mh_fvm_set_format (fvm, format); mh_fvm_set_width (fvm, width ? width : mh_width ()); mh_format_destroy (&format); } mbox = mh_open_folder (mh_current_folder (), MU_STREAM_READ); mu_mailbox_get_url (mbox, &url); mbox_path = mu_url_to_string (url); if (memcmp (mbox_path, "mh:", 3) == 0) mbox_path += 3; mh_mailbox_get_cur (mbox, ¤t_num); mh_msgset_parse (&msgset, mbox, argc, argv, "all"); fill_msgarr (msgset); mu_msgset_free (msgset); sort (); mh_global_save_state (); mu_mailbox_destroy (&mbox); return 0; }