]> diplodocus.org Git - nmh/blob - uip/sortm.c
Fixed three LC_ALL settings:
[nmh] / uip / sortm.c
1
2 /*
3 * sortm.c -- sort messages in a folder by date/time
4 *
5 * This code is Copyright (c) 2002, by the authors of nmh. See the
6 * COPYRIGHT file in the root directory of the nmh distribution for
7 * complete copyright information.
8 */
9
10 #include <h/mh.h>
11 #include <h/tws.h>
12 #include <h/utils.h>
13
14 #define SORTM_SWITCHES \
15 X("datefield field", 0, DATESW) \
16 X("textfield field", 0, TEXTSW) \
17 X("notextfield", 0, NSUBJSW) \
18 X("subject", -3, SUBJSW) /* backward-compatibility */ \
19 X("limit days", 0, LIMSW) \
20 X("nolimit", 0, NLIMSW) \
21 X("verbose", 0, VERBSW) \
22 X("noverbose", 0, NVERBSW) \
23 X("all", 0, ALLMSGS) \
24 X("noall", 0, NALLMSGS) \
25 X("check", 0, CHECKSW) \
26 X("nocheck", 0, NCHECKSW) \
27 X("version", 0, VERSIONSW) \
28 X("help", 0, HELPSW) \
29
30 #define X(sw, minchars, id) id,
31 DEFINE_SWITCH_ENUM(SORTM);
32 #undef X
33
34 #define X(sw, minchars, id) { sw, minchars, id },
35 DEFINE_SWITCH_ARRAY(SORTM, switches);
36 #undef X
37
38 struct smsg {
39 int s_msg;
40 time_t s_clock;
41 char *s_subj;
42 };
43
44 static struct smsg *smsgs;
45 int nmsgs;
46
47 char *subjsort = (char *) 0; /* sort on subject if != 0 */
48 time_t datelimit = 0;
49 int submajor = 0; /* if true, sort on subject-major */
50 int verbose;
51 int allmsgs = 1;
52 int check_failed = 0;
53
54 /* This keeps compiler happy on calls to qsort */
55 typedef int (*qsort_comp) (const void *, const void *);
56
57 /*
58 * static prototypes
59 */
60 static int read_hdrs (struct msgs *, char *);
61 static int get_fields (char *, int, struct smsg *);
62 static int dsort (struct smsg **, struct smsg **);
63 static int subsort (struct smsg **, struct smsg **);
64 static int txtsort (struct smsg **, struct smsg **);
65 static void rename_chain (struct msgs *, struct smsg **, int, int);
66 static void rename_msgs (struct msgs *, struct smsg **);
67
68
69 int
70 main (int argc, char **argv)
71 {
72 int i, msgnum;
73 char *cp, *maildir, *datesw = NULL;
74 char *folder = NULL, buf[BUFSIZ], **argp;
75 char **arguments;
76 struct msgs_array msgs = { 0, 0, NULL };
77 struct msgs *mp;
78 struct smsg **dlist;
79 int checksw = 0;
80
81 setlocale(LC_ALL, "");
82 invo_name = r1bindex (argv[0], '/');
83
84 /* read user profile/context */
85 context_read();
86
87 arguments = getarguments (invo_name, argc, argv, 1);
88 argp = arguments;
89
90 /*
91 * Parse arguments
92 */
93 while ((cp = *argp++)) {
94 if (*cp == '-') {
95 switch (smatch (++cp, switches)) {
96 case AMBIGSW:
97 ambigsw (cp, switches);
98 done (1);
99 case UNKWNSW:
100 adios (NULL, "-%s unknown", cp);
101
102 case HELPSW:
103 snprintf(buf, sizeof(buf), "%s [+folder] [msgs] [switches]",
104 invo_name);
105 print_help (buf, switches, 1);
106 done (0);
107 case VERSIONSW:
108 print_version(invo_name);
109 done (0);
110
111 case DATESW:
112 if (datesw)
113 adios (NULL, "only one date field at a time");
114 if (!(datesw = *argp++) || *datesw == '-')
115 adios (NULL, "missing argument to %s", argp[-2]);
116 continue;
117
118 case TEXTSW:
119 if (subjsort)
120 adios (NULL, "only one text field at a time");
121 if (!(subjsort = *argp++) || *subjsort == '-')
122 adios (NULL, "missing argument to %s", argp[-2]);
123 continue;
124
125 case SUBJSW:
126 subjsort = "subject";
127 continue;
128 case NSUBJSW:
129 subjsort = (char *)0;
130 continue;
131
132 case LIMSW:
133 if (!(cp = *argp++) || *cp == '-')
134 adios (NULL, "missing argument to %s", argp[-2]);
135 while (*cp == '0')
136 cp++; /* skip any leading zeros */
137 if (!*cp) { /* hit end of string */
138 submajor++; /* sort subject-major */
139 continue;
140 }
141 if (!isdigit((unsigned char) *cp) || !(datelimit = atoi(cp)))
142 adios (NULL, "impossible limit %s", cp);
143 datelimit *= 60*60*24;
144 continue;
145 case NLIMSW:
146 submajor = 0; /* use date-major, but */
147 datelimit = 0; /* use no limit */
148 continue;
149
150 case VERBSW:
151 verbose++;
152 continue;
153 case NVERBSW:
154 verbose = 0;
155 continue;
156
157 case ALLMSGS:
158 allmsgs = 1;
159 continue;
160 case NALLMSGS:
161 allmsgs = 0;
162 continue;
163
164 case CHECKSW:
165 checksw = 1;
166 continue;
167 case NCHECKSW:
168 checksw = 0;
169 continue;
170 }
171 }
172 if (*cp == '+' || *cp == '@') {
173 if (folder)
174 adios (NULL, "only one folder at a time!");
175 else
176 folder = pluspath (cp);
177 } else
178 app_msgarg(&msgs, cp);
179 }
180
181 if (!context_find ("path"))
182 free (path ("./", TFOLDER));
183 if (!msgs.size) {
184 if (allmsgs) {
185 app_msgarg(&msgs, "all");
186 } else {
187 adios (NULL, "must specify messages to sort with -noall");
188 }
189 }
190 if (!datesw)
191 datesw = "date";
192 if (!folder)
193 folder = getfolder (1);
194 maildir = m_maildir (folder);
195
196 if (chdir (maildir) == NOTOK)
197 adios (maildir, "unable to change directory to");
198
199 /* read folder and create message structure */
200 if (!(mp = folder_read (folder, 1)))
201 adios (NULL, "unable to read folder %s", folder);
202
203 /* check for empty folder */
204 if (mp->nummsg == 0)
205 adios (NULL, "no messages in %s", folder);
206
207 /* parse all the message ranges/sequences and set SELECTED */
208 for (msgnum = 0; msgnum < msgs.size; msgnum++)
209 if (!m_convert (mp, msgs.msgs[msgnum]))
210 done (1);
211 seq_setprev (mp); /* set the previous sequence */
212
213 if ((nmsgs = read_hdrs (mp, datesw)) <= 0)
214 adios (NULL, "no messages to sort");
215
216 if (checksw && check_failed) {
217 adios (NULL, "errors found, no messages sorted");
218 }
219
220 /*
221 * sort a list of pointers to our "messages to be sorted".
222 */
223 dlist = (struct smsg **) mh_xmalloc ((nmsgs+1) * sizeof(*dlist));
224 for (i = 0; i < nmsgs; i++)
225 dlist[i] = &smsgs[i];
226 dlist[nmsgs] = 0;
227
228 if (verbose) { /* announce what we're doing */
229 if (subjsort)
230 if (submajor)
231 printf ("sorting by %s\n", subjsort);
232 else
233 printf ("sorting by %s-major %s-minor\n", subjsort, datesw);
234 else
235 printf ("sorting by datefield %s\n", datesw);
236 }
237
238 /* first sort by date, or by subject-major, date-minor */
239 qsort ((char *) dlist, nmsgs, sizeof(*dlist),
240 (qsort_comp) (submajor && subjsort ? txtsort : dsort));
241
242 /*
243 * if we're sorting on subject, we need another list
244 * in subject order, then a merge pass to collate the
245 * two sorts.
246 */
247 if (!submajor && subjsort) { /* already date sorted */
248 struct smsg **slist, **flist;
249 register struct smsg ***il, **fp, **dp;
250
251 slist = (struct smsg **) mh_xmalloc ((nmsgs+1) * sizeof(*slist));
252 memcpy((char *)slist, (char *)dlist, (nmsgs+1)*sizeof(*slist));
253 qsort((char *)slist, nmsgs, sizeof(*slist), (qsort_comp) subsort);
254
255 /*
256 * make an inversion list so we can quickly find
257 * the collection of messages with the same subj
258 * given a message number.
259 */
260 il = (struct smsg ***) calloc (mp->hghsel+1, sizeof(*il));
261 if (! il)
262 adios (NULL, "couldn't allocate msg list");
263 for (i = 0; i < nmsgs; i++)
264 il[slist[i]->s_msg] = &slist[i];
265 /*
266 * make up the final list, chronological but with
267 * all the same subjects grouped together.
268 */
269 flist = (struct smsg **) mh_xmalloc ((nmsgs+1) * sizeof(*flist));
270 fp = flist;
271 for (dp = dlist; *dp;) {
272 register struct smsg **s = il[(*dp++)->s_msg];
273
274 /* see if we already did this guy */
275 if (! s)
276 continue;
277
278 *fp++ = *s++;
279 /*
280 * take the next message(s) if there is one,
281 * its subject isn't null and its subject
282 * is the same as this one and it's not too
283 * far away in time.
284 */
285 while (*s && (*s)->s_subj[0] &&
286 strcmp((*s)->s_subj, s[-1]->s_subj) == 0 &&
287 (datelimit == 0 ||
288 (*s)->s_clock - s[-1]->s_clock <= datelimit)) {
289 il[(*s)->s_msg] = 0;
290 *fp++ = *s++;
291 }
292 }
293 *fp = 0;
294 free (slist);
295 free (dlist);
296 dlist = flist;
297 }
298
299 /*
300 * At this point, dlist is a sorted array of pointers to smsg structures,
301 * each of which contains a message number.
302 */
303
304 rename_msgs (mp, dlist);
305
306 context_replace (pfolder, folder); /* update current folder */
307 seq_save (mp); /* synchronize message sequences */
308 context_save (); /* save the context file */
309 folder_free (mp); /* free folder/message structure */
310 done (0);
311 return 1;
312 }
313
314 static int
315 read_hdrs (struct msgs *mp, char *datesw)
316 {
317 int msgnum;
318 struct tws tb;
319 register struct smsg *s;
320
321 twscopy (&tb, dlocaltimenow ());
322
323 smsgs = (struct smsg *)
324 calloc ((size_t) (mp->hghsel - mp->lowsel + 2),
325 sizeof(*smsgs));
326 if (smsgs == NULL)
327 adios (NULL, "unable to allocate sort storage");
328
329 s = smsgs;
330 for (msgnum = mp->lowsel; msgnum <= mp->hghsel; msgnum++) {
331 if (is_selected(mp, msgnum)) {
332 if (get_fields (datesw, msgnum, s)) {
333 s->s_msg = msgnum;
334 s++;
335 }
336 }
337 }
338 s->s_msg = 0;
339 return(s - smsgs);
340 }
341
342
343 /*
344 * Parse the message and get the data or subject field,
345 * if needed.
346 */
347
348 static int
349 get_fields (char *datesw, int msg, struct smsg *smsg)
350 {
351 register int state;
352 int compnum;
353 char *msgnam, buf[BUFSIZ], nam[NAMESZ];
354 register struct tws *tw;
355 register char *datecomp = NULL, *subjcomp = NULL;
356 register FILE *in;
357 m_getfld_state_t gstate = 0;
358
359 if ((in = fopen (msgnam = m_name (msg), "r")) == NULL) {
360 admonish (msgnam, "unable to read message");
361 return (0);
362 }
363 for (compnum = 1;;) {
364 int bufsz = sizeof buf;
365 switch (state = m_getfld (&gstate, nam, buf, &bufsz, in)) {
366 case FLD:
367 case FLDPLUS:
368 compnum++;
369 if (!strcasecmp (nam, datesw)) {
370 datecomp = add (buf, datecomp);
371 while (state == FLDPLUS) {
372 bufsz = sizeof buf;
373 state = m_getfld (&gstate, nam, buf, &bufsz, in);
374 datecomp = add (buf, datecomp);
375 }
376 if (!subjsort || subjcomp)
377 break;
378 } else if (subjsort && !strcasecmp (nam, subjsort)) {
379 subjcomp = add (buf, subjcomp);
380 while (state == FLDPLUS) {
381 bufsz = sizeof buf;
382 state = m_getfld (&gstate, nam, buf, &bufsz, in);
383 subjcomp = add (buf, subjcomp);
384 }
385 if (datecomp)
386 break;
387 } else {
388 /* just flush this guy */
389 while (state == FLDPLUS) {
390 bufsz = sizeof buf;
391 state = m_getfld (&gstate, nam, buf, &bufsz, in);
392 }
393 }
394 continue;
395
396 case BODY:
397 case FILEEOF:
398 break;
399
400 case LENERR:
401 case FMTERR:
402 if (state == LENERR || state == FMTERR) {
403 admonish (NULL, "format error in message %d (header #%d)",
404 msg, compnum);
405 check_failed = 1;
406 }
407 if (datecomp)
408 free (datecomp);
409 if (subjcomp)
410 free (subjcomp);
411 fclose (in);
412 return (0);
413
414 default:
415 adios (NULL, "internal error -- you lose");
416 }
417 break;
418 }
419 m_getfld_state_destroy (&gstate);
420
421 /*
422 * If no date component, then use the modification
423 * time of the file as its date
424 */
425 if (!datecomp || (tw = dparsetime (datecomp)) == NULL) {
426 struct stat st;
427
428 advise (NULL,
429 "can't parse %s field in message %d, "
430 "will use file modification time",
431 datesw, msg);
432 fstat (fileno (in), &st);
433 smsg->s_clock = st.st_mtime;
434 check_failed = 1;
435 } else {
436 smsg->s_clock = dmktime (tw);
437 }
438
439 if (subjsort) {
440 if (subjcomp) {
441 /*
442 * try to make the subject "canonical": delete
443 * leading "re:", everything but letters & smash
444 * letters to lower case.
445 */
446 register char *cp, *cp2, c;
447
448 cp = subjcomp;
449 cp2 = subjcomp;
450 if (strcmp (subjsort, "subject") == 0) {
451 while ((c = *cp)) {
452 if (! isspace((unsigned char) c)) {
453 if(uprf(cp, "re:"))
454 cp += 2;
455 else
456 break;
457 }
458 cp++;
459 }
460 }
461
462 while ((c = *cp++)) {
463 if (isascii((unsigned char) c) && isalnum((unsigned char) c))
464 *cp2++ = isupper((unsigned char) c) ?
465 tolower((unsigned char) c) : c;
466 }
467
468 *cp2 = '\0';
469 }
470 else
471 subjcomp = "";
472
473 smsg->s_subj = subjcomp;
474 }
475 fclose (in);
476 if (datecomp)
477 free (datecomp);
478
479 return (1);
480 }
481
482 /*
483 * sort on dates.
484 */
485 static int
486 dsort (struct smsg **a, struct smsg **b)
487 {
488 if ((*a)->s_clock < (*b)->s_clock)
489 return (-1);
490 else if ((*a)->s_clock > (*b)->s_clock)
491 return (1);
492 else if ((*a)->s_msg < (*b)->s_msg)
493 return (-1);
494 else
495 return (1);
496 }
497
498 /*
499 * sort on subjects.
500 */
501 static int
502 subsort (struct smsg **a, struct smsg **b)
503 {
504 register int i;
505
506 if ((i = strcmp ((*a)->s_subj, (*b)->s_subj)))
507 return (i);
508
509 return (dsort (a, b));
510 }
511
512 static int
513 txtsort (struct smsg **a, struct smsg **b)
514 {
515 register int i;
516
517 if ((i = strcmp ((*a)->s_subj, (*b)->s_subj)))
518 return (i);
519 else if ((*a)->s_msg < (*b)->s_msg)
520 return (-1);
521 else
522 return (1);
523 }
524
525 static void
526 rename_chain (struct msgs *mp, struct smsg **mlist, int msg, int endmsg)
527 {
528 int nxt, old, new;
529 char *newname, oldname[BUFSIZ];
530 char newbuf[PATH_MAX + 1];
531
532 for (;;) {
533 nxt = mlist[msg] - smsgs; /* mlist[msg] is a ptr into smsgs */
534 mlist[msg] = (struct smsg *)0;
535 old = smsgs[nxt].s_msg;
536 new = smsgs[msg].s_msg;
537 strncpy (oldname, m_name (old), sizeof(oldname));
538 newname = m_name (new);
539 if (verbose)
540 printf ("message %d becomes message %d\n", old, new);
541
542 (void)snprintf(oldname, sizeof (oldname), "%s/%d", mp->foldpath, old);
543 (void)snprintf(newbuf, sizeof (newbuf), "%s/%d", mp->foldpath, new);
544 ext_hook("ref-hook", oldname, newbuf);
545
546 if (rename (oldname, newname) == NOTOK)
547 adios (newname, "unable to rename %s to", oldname);
548
549 copy_msg_flags (mp, new, old);
550 if (mp->curmsg == old)
551 seq_setcur (mp, new);
552
553 if (nxt == endmsg)
554 break;
555
556 msg = nxt;
557 }
558 /* if (nxt != endmsg); */
559 /* rename_chain (mp, mlist, nxt, endmsg); */
560 }
561
562 static void
563 rename_msgs (struct msgs *mp, struct smsg **mlist)
564 {
565 int i, j, old, new;
566 bvector_t tmpset = bvector_create (0);
567 char f1[BUFSIZ], tmpfil[BUFSIZ];
568 char newbuf[PATH_MAX + 1];
569 struct smsg *sp;
570
571 strncpy (tmpfil, m_name (mp->hghmsg + 1), sizeof(tmpfil));
572
573 for (i = 0; i < nmsgs; i++) {
574 if (! (sp = mlist[i]))
575 continue; /* did this one */
576
577 j = sp - smsgs;
578 if (j == i)
579 continue; /* this one doesn't move */
580
581 /*
582 * the guy that was msg j is about to become msg i.
583 * rename 'j' to make a hole, then recursively rename
584 * guys to fill up the hole.
585 */
586 old = smsgs[j].s_msg;
587 new = smsgs[i].s_msg;
588 strncpy (f1, m_name (old), sizeof(f1));
589
590 if (verbose)
591 printf ("renaming message chain from %d to %d\n", old, new);
592
593 /*
594 * Run the external hook to refile the old message as the
595 * temporary message number that is off of the end of the
596 * messages in the folder.
597 */
598
599 (void)snprintf(f1, sizeof (f1), "%s/%d", mp->foldpath, old);
600 (void)snprintf(newbuf, sizeof (newbuf), "%s/%d", mp->foldpath, mp->hghmsg + 1);
601 ext_hook("ref-hook", f1, newbuf);
602
603 if (rename (f1, tmpfil) == NOTOK)
604 adios (tmpfil, "unable to rename %s to ", f1);
605
606 get_msg_flags (mp, tmpset, old);
607
608 rename_chain (mp, mlist, j, i);
609
610 /*
611 * Run the external hook to refile the temorary message number
612 * to the real place.
613 */
614
615 (void)snprintf(f1, sizeof (f1), "%s/%d", mp->foldpath, new);
616 ext_hook("ref-hook", newbuf, f1);
617
618 if (rename (tmpfil, m_name(new)) == NOTOK)
619 adios (m_name(new), "unable to rename %s to", tmpfil);
620
621 set_msg_flags (mp, tmpset, new);
622 mp->msgflags |= SEQMOD;
623 }
624
625 bvector_free (tmpset);
626 }