Commit f4e4082a f4e4082ae1da285ec7abe072a945d606f2f79056 by Sergey Poznyakoff

New file. Source for the sortm utility.

1 parent 1157f827
Showing 1 changed file with 563 additions and 0 deletions
1 /* GNU Mailutils -- a suite of utilities for electronic mail
2 Copyright (C) 2003 Free Software Foundation, Inc.
3
4 GNU Mailutils is free software; you can redistribute it and/or modify
5 it under the terms of the GNU General Public License as published by
6 the Free Software Foundation; either version 2, or (at your option)
7 any later version.
8
9 GNU Mailutils is distributed in the hope that it will be useful,
10 but WITHOUT ANY WARRANTY; without even the implied warranty of
11 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 GNU General Public License for more details.
13
14 You should have received a copy of the GNU General Public License
15 along with GNU Mailutils; if not, write to the Free Software
16 Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */
17
18 /* MH sortm command */
19
20 #include <mh.h>
21 #include <sys/stat.h>
22 #include <unistd.h>
23 #include <signal.h>
24
25 const char *argp_program_version = "sortm (" PACKAGE_STRING ")";
26 static char doc[] = N_("GNU MH sortm\v"
27 "Use -help to obtain the list of traditional MH options.");
28 static char args_doc[] = N_("[msgs]");
29
30 #define ARG_QUICKSORT 1024
31 #define ARG_SHELL 1025
32
33 /* GNU options */
34 static struct argp_option options[] = {
35 {"folder", ARG_FOLDER, N_("FOLDER"), 0,
36 N_("Specify folder to operate upon")},
37
38 {N_("Setting sort keys:"), 0, NULL, OPTION_DOC, NULL, 0},
39 {"datefield", ARG_DATEFIELD, N_("STRING"), 0,
40 N_("Sort on the date field (default `Date:')"), 10},
41 {"nodatefield", ARG_NODATEFIELD, NULL, 0,
42 N_("Undo the effect of the last --datefield option"), 10},
43 {"limit", ARG_LIMIT, N_("DAYS"), 0,
44 N_("Consider two datefields equal if their difference lies within the given nuber of DAYS."), 11},
45 {"nolimit", ARG_NOLIMIT, NULL, 0,
46 N_("Undo the effect of the last --limit option"), 11},
47 {"textfield", ARG_TEXTFIELD, N_("STRING"), 15,
48 N_("Sort on the text field"), 1},
49 {"notextfield", ARG_NOTEXTFIELD, NULL, 0,
50 N_("Undo the effect of the last --textfield option"), 15},
51 {"numfield", ARG_NUMFIELD, N_("STRING"), 0,
52 N_("Sort on the numeric field"), 16},
53
54 {N_("Actions:"), 0, NULL, OPTION_DOC, NULL, 16},
55 {"reorder", ARG_REORDER, 0, 0,
56 N_("Reorder the messages (default)"), 20 },
57 {"dry-run", ARG_DRY_RUN, 0, 0,
58 N_("Do not do anything, only show what would have been done"), 20 },
59 {"list", ARG_LIST, 0, 0,
60 N_("List the sorted messages"), 20 },
61 {"form", ARG_FORM, N_("FILE"), 0,
62 N_("Read format from given file"), 23},
63 {"format", ARG_FORMAT, N_("FORMAT"), 0,
64 N_("Use this format string"), 23},
65
66 {"verbose", ARG_VERBOSE, N_("BOOL"), OPTION_ARG_OPTIONAL,
67 N_("Verbosely list executed actions"), 30 },
68 {"noverbose", ARG_NOVERBOSE, NULL, OPTION_HIDDEN, "" },
69
70 {N_("Select sort algorithm:"), 0, NULL, OPTION_DOC, NULL, 30},
71
72 {"shell", ARG_SHELL, 0, 0,
73 N_("Use shell algorithm"), 40 },
74 {"quicksort", ARG_QUICKSORT, 0, 0,
75 N_("Use quicksort algorithm (default)"), 40 },
76 { NULL },
77 };
78
79 /* Traditional MH options */
80 struct mh_option mh_option[] = {
81 {"datefield", 1, 0, "field" },
82 {"nodatefield", 3, 0, 0 },
83 {"textfield", 1, 0, "field" },
84 {"notextfield", 3, 0, 0 },
85 {"limit", 1, 0, "days" },
86 {"nolimit", 3, 0, 0 },
87 {"verbose", 1, MH_OPT_BOOL, NULL},
88 { NULL },
89 };
90
91 static int limit;
92 static int verbose;
93 static mailbox_t mbox;
94 static const char *mbox_path;
95 static mh_msgset_t msgset;
96
97 #define ACTION_REORDER 0
98 #define ACTION_DRY_RUN 1
99 #define ACTION_LIST 2
100
101 static int algorithm = ARG_QUICKSORT;
102 static int action = ACTION_REORDER;
103 static char *format_str = mh_list_format;
104 static mh_format_t format;
105
106 typedef int (*compfun) __PMT((void *, void *));
107 static void addop __P((char *field, compfun comp));
108 static void remop __P((compfun comp));
109 static int comp_text __P((void *a, void *b));
110 static int comp_date __P((void *a, void *b));
111 static int comp_number __P((void *a, void *b));
112
113 static int
114 opt_handler (int key, char *arg, void *unused, struct argp_state *state)
115 {
116 switch (key)
117 {
118 case ARG_FOLDER:
119 current_folder = arg;
120 break;
121
122 case ARG_DATEFIELD:
123 addop (arg, comp_date);
124 break;
125
126 case ARG_NUMFIELD:
127 addop (arg, comp_number);
128 break;
129
130 case ARG_NODATEFIELD:
131 remop (comp_date);
132 break;
133
134 case ARG_TEXTFIELD:
135 addop (arg, comp_text);
136 break;
137
138 case ARG_NOTEXTFIELD:
139 remop (comp_text);
140 break;
141
142 case ARG_LIMIT:
143 limit = strtoul (arg, NULL, 0);
144 break;
145
146 case ARG_NOLIMIT:
147 limit = -1;
148 break;
149
150 case ARG_VERBOSE:
151 if (!arg || isalpha (arg[0]))
152 verbose = is_true (arg);
153 else
154 verbose = arg[0] - '0';
155 break;
156
157 case ARG_NOVERBOSE:
158 verbose = 0;
159 break;
160
161 case ARG_FORM:
162 mh_read_formfile (arg, &format_str);
163 break;
164
165 case ARG_FORMAT:
166 format_str = arg;
167 break;
168
169 case ARG_REORDER:
170 action = ACTION_REORDER;
171 break;
172
173 case ARG_LIST:
174 action = ACTION_LIST;
175 break;
176
177 case ARG_DRY_RUN:
178 action = ACTION_DRY_RUN;
179 if (!verbose)
180 verbose = 1;
181 break;
182
183 case ARG_SHELL:
184 case ARG_QUICKSORT:
185 algorithm = key;
186 break;
187
188 default:
189 return 1;
190 }
191 return 0;
192 }
193
194
195 /* *********************** Comparison functions **************************** */
196 struct comp_op {
197 char *field;
198 compfun comp;
199 };
200
201 static list_t oplist;
202
203 static void
204 addop (char *field, compfun comp)
205 {
206 struct comp_op *op = xmalloc (sizeof (*op));
207
208 if (!oplist && list_create (&oplist))
209 {
210 mh_error (_("can't create operation list"));
211 exit (1);
212 }
213 op->field = field;
214 op->comp = comp;
215 list_append (oplist, op);
216 }
217
218 struct rem_data {
219 struct comp_op *op;
220 compfun comp;
221 };
222
223 static int
224 rem_action (void *item, void *data)
225 {
226 struct comp_op *op = item;
227 struct rem_data *d = data;
228 if (d->comp == op->comp)
229 d->op = op;
230 return 0;
231 }
232
233 static void
234 remop (compfun comp)
235 {
236 struct rem_data d;
237 d.comp = comp;
238 d.op = NULL;
239 list_do (oplist, rem_action, &d);
240 list_remove (oplist, d.op);
241 free (d.op);
242 }
243
244 struct comp_data {
245 int r;
246 message_t m[2];
247 };
248
249 static int
250 compare_action (void *item, void *data)
251 {
252 struct comp_op *op = item;
253 struct comp_data *dp = data;
254 char *a, *ap, *b, *bp;
255 header_t h;
256
257 if (message_get_header (dp->m[0], &h)
258 || header_aget_value (h, op->field, &a))
259 return 0;
260
261 if (message_get_header (dp->m[1], &h)
262 || header_aget_value (h, op->field, &b))
263 {
264 free (a);
265 return 0;
266 }
267
268 ap = a;
269 bp = b;
270 if (strcasecmp (op->field, MU_HEADER_SUBJECT) == 0)
271 {
272 if (strncasecmp (ap, "re:", 3) == 0)
273 ap += 3;
274 if (strncasecmp (b, "re:", 3) == 0)
275 bp += 3;
276 }
277
278 dp->r = op->comp (ap, bp);
279 free (a);
280 free (b);
281
282 return dp->r; /* go on until the difference is found */
283 }
284
285 static int
286 compare_messages (message_t a, message_t b)
287 {
288 struct comp_data d;
289
290 d.r = 0;
291 d.m[0] = a;
292 d.m[1] = b;
293 list_do (oplist, compare_action, &d);
294 if (verbose > 1)
295 fprintf (stderr, "%d\n", d.r);
296 return d.r;
297 }
298
299 static int
300 comp_text (void *a, void *b)
301 {
302 return strcasecmp (a, b);
303 }
304
305 static int
306 comp_number (void *a, void *b)
307 {
308 long na, nb;
309
310 na = strtol (a, NULL, 0);
311 nb = strtol (b, NULL, 0);
312 if (na > nb)
313 return 1;
314 else if (na < nb)
315 return -1;
316 return 0;
317 }
318
319 /*FIXME: Also used in imap4d*/
320 static int
321 _parse_822_date (char *date, time_t * timep)
322 {
323 struct tm tm;
324 mu_timezone tz;
325 const char *p = date;
326
327 if (parse822_date_time (&p, date + strlen (date), &tm, &tz) == 0)
328 {
329 *timep = mu_tm2time (&tm, &tz);
330 return 0;
331 }
332 return 1;
333 }
334
335 static int
336 comp_date (void *a, void *b)
337 {
338 time_t ta, tb;
339
340 if (_parse_822_date (a, &ta) || _parse_822_date (b, &tb))
341 return 0;
342
343 if (ta < tb)
344 {
345 if (limit && tb - ta <= limit)
346 return 0;
347 return -1;
348 }
349 else if (ta > tb)
350 {
351 if (limit && ta - tb <= limit)
352 return 0;
353 return 1;
354 }
355 return 0;
356 }
357
358
359 /* *********************** Sorting routines ***************************** */
360
361 static int
362 comp0 (size_t na, size_t nb)
363 {
364 message_t a, b;
365
366 if (mailbox_get_message (mbox, na, &a)
367 || mailbox_get_message (mbox, nb, &b))
368 return 0;
369 if (verbose > 1)
370 fprintf (stderr,
371 _("comparing messages %lu and %lu: "),
372 (unsigned long) na,
373 (unsigned long) nb);
374 return compare_messages (a, b);
375 }
376
377 int
378 comp (const void *a, const void *b)
379 {
380 return comp0 (* (size_t*) a, * (size_t*) b);
381 }
382
383
384 /* ****************************** Shell sort ****************************** */
385 #define prevdst(h) ((h)-1)/3
386
387 static int
388 startdst (unsigned count, int *num)
389 {
390 int i, h;
391
392 for (i = h = 1; 9*h + 4 < count; i++, h = 3*h+1)
393 ;
394 *num = i;
395 return h;
396 }
397
398 void
399 shell_sort ()
400 {
401 int h, s, i, j;
402 size_t hold;
403
404 for (h = startdst (msgset.count, &s); s > 0; s--, h = prevdst (h))
405 {
406 if (verbose > 1)
407 fprintf (stderr, _("distance %d\n"), h);
408 for (j = h; j < msgset.count; j++)
409 {
410 hold = msgset.list[j];
411 for (i = j - h;
412 i >= 0 && comp0 (hold, msgset.list[i]) < 0; i -= h)
413 msgset.list[i + h] = msgset.list[i];
414 msgset.list[i + h] = hold;
415 }
416 }
417 }
418
419
420 /* ****************************** Actions ********************************* */
421
422 void
423 list_message (size_t num)
424 {
425 message_t msg = NULL;
426 char *buffer;
427 mailbox_get_message (mbox, num, &msg);
428 mh_format (&format, msg, num, 76, &buffer);
429 printf ("%s\n", buffer);
430 free (buffer);
431 }
432
433 void
434 swap_message (size_t a, size_t b)
435 {
436 char *path_a, *path_b;
437 char *tmp;
438
439 asprintf (&path_a, "%s/%lu", mbox_path, (unsigned long) a);
440 asprintf (&path_b, "%s/%lu", mbox_path, (unsigned long) b);
441 tmp = mu_tempname (mbox_path);
442 rename (path_a, tmp);
443 unlink (path_a);
444 rename (path_b, path_a);
445 unlink (path_b);
446 rename (tmp, path_b);
447 free (tmp);
448 }
449
450 void
451 transpose(size_t i, size_t n)
452 {
453 size_t j;
454
455 for (j = i+1; j < msgset.count; j++)
456 if (msgset.list[j] == n)
457 {
458 size_t t = msgset.list[i];
459 msgset.list[i] = msgset.list[j];
460 msgset.list[j] = t;
461 break;
462 }
463 }
464
465 static int got_signal;
466
467 RETSIGTYPE
468 sighandler (int sig)
469 {
470 got_signal = 1;
471 }
472
473 void
474 sort ()
475 {
476 size_t *oldlist, i;
477 oldlist = xmalloc (msgset.count * sizeof (*oldlist));
478 memcpy (oldlist, msgset.list, msgset.count * sizeof (*oldlist));
479
480 switch (algorithm)
481 {
482 case ARG_QUICKSORT:
483 qsort(msgset.list, msgset.count, sizeof(msgset.list[0]),
484 comp);
485 break;
486
487 case ARG_SHELL:
488 shell_sort();
489 break;
490 }
491
492 switch (action)
493 {
494 case ACTION_LIST:
495 for (i = 0; i < msgset.count; i++)
496 list_message (msgset.list[i]);
497 break;
498
499 default:
500 /* Install signal handlers */
501 signal (SIGINT, sighandler);
502 signal (SIGQUIT, sighandler);
503 signal (SIGTERM, sighandler);
504
505 if (verbose)
506 fprintf (stderr, _("Transpositions:\n"));
507 for (i = 0, got_signal = 0; !got_signal && i < msgset.count; i++)
508 {
509 if (msgset.list[i] != oldlist[i])
510 {
511 size_t old_num, new_num;
512 message_t msg;
513
514 mailbox_get_message (mbox, oldlist[i], &msg);
515 mh_message_number (msg, &old_num);
516 mailbox_get_message (mbox, msgset.list[i], &msg);
517 mh_message_number (msg, &new_num);
518 transpose (i, oldlist[i]);
519 if (verbose)
520 fprintf (stderr, "{%lu, %lu}\n",
521 (unsigned long) old_num,
522 (unsigned long) new_num);
523 if (action == ACTION_REORDER)
524 swap_message (old_num, new_num);
525 }
526 }
527 }
528 }
529
530
531 /* Main */
532
533 int
534 main (int argc, char **argv)
535 {
536 int index;
537 url_t url;
538
539 mu_init_nls ();
540 mh_argp_parse (argc, argv, 0, options, mh_option,
541 args_doc, doc, opt_handler, NULL, &index);
542 if (!oplist)
543 addop ("date", comp_date);
544
545 if (action == ACTION_LIST && mh_format_parse (format_str, &format))
546 {
547 mh_error (_("Bad format string"));
548 exit (1);
549 }
550
551 mbox = mh_open_folder (current_folder, 0);
552 mailbox_get_url (mbox, &url);
553 mbox_path = url_to_string (url);
554 if (memcmp (mbox_path, "mh:", 3) == 0)
555 mbox_path += 3;
556
557 argc -= index;
558 argv += index;
559
560 mh_msgset_parse (mbox, &msgset, argc, argv, "all");
561 sort (mbox, msgset);
562 return 0;
563 }