New file. Source for the sortm utility.
Showing
1 changed file
with
563 additions
and
0 deletions
mh/sortm.c
0 → 100644
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 | } |
-
Please register or sign in to post a comment