Added test of -nosequence to test-pick.
[mmh] / docs / historical / mh-6.8.5 / uip / sortm.c
1 /* sortm.c - sort messages in a folder by date/time */
2 /* 21Apr90 do subject sorts too - from V. Jacobson */
3 #ifndef lint
4 static char ident[] = "@(#)$Id: sortm.c,v 1.20 1995/12/06 23:46:23 jromine Exp $";
5 #endif  /* lint */
6
7 #include "../h/mh.h"
8 #include "../zotnet/tws.h"
9 #define getws _getws
10 #include <stdio.h>
11 #undef getws
12 #include <sys/types.h>
13 #include <sys/stat.h>
14 #include <ctype.h>
15 #ifdef LOCALE
16 #include        <locale.h>
17 #endif
18 #if     defined(SYS5) && defined(AUX)
19 #define u_short ushort
20 #define u_long  ulong
21 #endif
22
23
24 static struct swit switches[] = {
25 #define DATESW  0
26      "datefield field", 0,
27
28 #define TEXTSW 1
29      "textfield field", 0,
30 #define NSUBJSW 2
31      "notextfield", 0,
32
33 #define SUBJSW 3
34      "subject", -3,             /* backward-compatibility */
35 #define LIMSW 4
36      "limit days", 0,
37 #define NLIMSW 5
38      "nolimit", 0,
39
40 #define VERBSW  6
41      "verbose", 0,
42 #define NVERBSW 7
43      "noverbose", 0,
44
45 #define HELPSW  8
46      "help", 4,
47
48      NULL, 0
49 };
50
51 struct smsg {
52     int s_msg;
53     unsigned long s_clock;
54     char *s_subj;
55 };
56
57 static struct smsg *smsgs;
58 int     nmsgs;
59
60 char   *subjsort = (char *)0;           /* sort on subject if != 0 */
61 u_long  datelimit = 0;
62 int     submajor = 0;           /* if true, sort on subject-major */
63 int     verbose;
64
65 #ifdef __STDC__
66 static int getws (char *datesw, int msg, struct smsg *smsg);
67 #else
68 static int getws ();
69 #endif
70
71 static  int dsort(), read_hdrs (), subsort(), txtsort();
72 static void rename_chain(), rename_msgs();
73
74 /* \f */
75
76 main (argc, argv)
77 int argc;
78 char **argv;
79 {
80     int     msgp = 0,
81             i,
82             msgnum;
83     char   *cp,
84            *maildir,
85            *datesw = NULL,
86            *folder = NULL,
87             buf[100],
88           **ap,
89           **argp,
90            *arguments[MAXARGS],
91            *msgs[MAXARGS];
92     struct msgs *mp;
93     struct smsg **dlist;
94
95 #ifdef LOCALE
96         setlocale(LC_ALL, "");
97 #endif
98     invo_name = r1bindex (argv[0], '/');
99     if ((cp = m_find (invo_name)) != NULL) {
100         ap = brkstring (cp = getcpy (cp), " ", "\n");
101         ap = copyip (ap, arguments);
102     }
103     else
104         ap = arguments;
105     (void) copyip (argv + 1, ap);
106     argp = arguments;
107
108     while (cp = *argp++) {
109         if (*cp == '-')
110             switch (smatch (++cp, switches)) {
111             case AMBIGSW:
112                 ambigsw (cp, switches);
113                 done (1);
114             case UNKWNSW:
115                 adios (NULLCP, "-%s unknown", cp);
116             case HELPSW:
117                 (void) sprintf(buf, "%s [+folder] [msgs] [switches]",
118                           invo_name);
119                 help (buf, switches);
120                 done (1);
121
122             case DATESW:
123                 if (datesw)
124                     adios (NULLCP, "only one date field at a time");
125                 if (!(datesw = *argp++) || *datesw == '-')
126                     adios (NULLCP, "missing argument to %s", argp[-2]);
127                 continue;
128
129             case TEXTSW:
130                 if (subjsort)
131                     adios (NULLCP, "only one text field at a time");
132                 if (!(subjsort = *argp++) || *subjsort == '-')
133                     adios (NULLCP, "missing argument to %s", argp[-2]);
134                 continue;
135
136             case SUBJSW:
137                 subjsort = "subject";
138                 continue;
139             case NSUBJSW:
140                 subjsort = (char *)0;
141                 continue;
142
143             case LIMSW:
144                 if (!(cp = *argp++) || *cp == '-')
145                         adios (NULLCP, "missing argument to %s", argp[-2]);
146                 while (*cp == '0')
147                     cp++;               /* skip any leading zeros */
148                 if (!*cp) {             /* hit end of string */
149                     submajor++;         /* sort subject-major */
150                     continue;
151                 }
152                 if (!isdigit(*cp) || !(datelimit = atoi(cp)))
153                     adios (NULLCP, "impossible limit %s", cp);
154                 datelimit *= 60*60*24;
155                 continue;
156             case NLIMSW:
157                 submajor = 0;   /* use date-major, but */
158                 datelimit = 0;  /* use no limit */
159                 continue;
160
161             case VERBSW:
162                 verbose++;
163                 continue;
164             case NVERBSW:
165                 verbose = 0;
166                 continue;
167             }
168         if (*cp == '+' || *cp == '@') {
169             if (folder)
170                 adios (NULLCP, "only one folder at a time!");
171             else
172                 folder = path (cp + 1, *cp == '+' ? TFOLDER : TSUBCWF);
173         }
174         else
175             msgs[msgp++] = cp;
176     }
177
178     if (!m_find ("path"))
179         free (path ("./", TFOLDER));
180     if (!msgp)
181         msgs[msgp++] = "all";
182     if (!datesw)
183         datesw = "date";
184     if (!folder)
185         folder = m_getfolder ();
186     maildir = m_maildir (folder);
187
188     if (chdir (maildir) == NOTOK)
189         adios (maildir, "unable to change directory to");
190     if (!(mp = m_gmsg (folder)))
191         adios (NULLCP, "unable to read folder %s", folder);
192     if (mp->hghmsg == 0)
193         adios (NULLCP, "no messages in %s", folder);
194
195     for (msgnum = 0; msgnum < msgp; msgnum++)
196         if (!m_convert (mp, msgs[msgnum]))
197             done (1);
198     m_setseq (mp);
199
200     if ((nmsgs = read_hdrs (mp, datesw)) <= 0)
201         adios (NULLCP, "no messages to sort");
202
203     /*
204      * sort a list of pointers to our "messages to be sorted".
205      */
206     dlist = (struct smsg **) malloc ((nmsgs+1) * sizeof(*dlist));
207     if (! dlist)
208         adios (NULLCP, "couldn't allocate sort memory");
209     for (i = 0; i < nmsgs; i++)
210         dlist[i] = &smsgs[i];
211     dlist[nmsgs] = 0;
212
213     if (verbose)        /* announce what we're doing */
214         if (subjsort)
215             printf ("sorting by %s-major %s-minor\n", 
216                 submajor ? subjsort : datesw,
217                 submajor ? datesw : subjsort);
218         else
219             printf ("sorting by datefield %s\n", datesw);
220
221     /* first sort by date, or by subject-major, date-minor */
222     qsort ((char *) dlist, nmsgs, sizeof(*dlist), 
223             submajor && subjsort ? txtsort : dsort);
224
225     /*
226      * if we're sorting on subject, we need another list
227      * in subject order, then a merge pass to collate the
228      * two sorts.
229      */
230     if (!submajor && subjsort) {        /* already date sorted */
231         struct smsg             **slist,
232                                 **flist;
233         register struct smsg   ***il,
234                                 **fp,
235                                 **dp;
236
237         slist = (struct smsg **) malloc ((nmsgs+1) * sizeof(*slist));
238         if (! slist)
239             adios (NULLCP, "couldn't allocate sort memory");
240         bcopy ((char *)dlist, (char *)slist, (nmsgs+1)*sizeof(*slist));
241         qsort ((char *)slist, nmsgs, sizeof(*slist), subsort);
242
243         /*
244          * make an inversion list so we can quickly find
245          * the collection of messages with the same subj
246          * given a message number.
247          */
248         il = (struct smsg ***) calloc (mp->hghsel+1, sizeof(*il));
249         if (! il)
250             adios (NULLCP, "couldn't allocate msg list");
251         for (i = 0; i < nmsgs; i++)
252             il[slist[i]->s_msg] = &slist[i];
253         /*
254          * make up the final list, chronological but with
255          * all the same subjects grouped together.
256          */
257         flist = (struct smsg **) malloc ((nmsgs+1) * sizeof(*flist));
258         if (! flist)
259             adios (NULLCP, "couldn't allocate msg list");
260         fp = flist;
261         for (dp = dlist; *dp;) {
262             register struct smsg **s = il[(*dp++)->s_msg];
263
264             /* see if we already did this guy */
265             if (! s)
266                 continue;
267
268             *fp++ = *s++;
269             /*
270              * take the next message(s) if there is one,
271              * its subject isn't null and its subject
272              * is the same as this one and it's not too
273              * far away in time.
274              */
275             while (*s && (*s)->s_subj[0] &&
276                    strcmp((*s)->s_subj, s[-1]->s_subj) == 0 &&
277                    (datelimit == 0 || 
278                    (*s)->s_clock - s[-1]->s_clock <= datelimit)) {
279                 il[(*s)->s_msg] = 0;
280                 *fp++ = *s++;
281             }
282         }
283         *fp = 0;
284         (void) free (slist);
285         (void) free (dlist);
286         dlist = flist;
287     }
288     rename_msgs (mp, dlist);
289
290     m_replace (pfolder, folder);
291     m_sync (mp);
292     m_update ();
293     done (0);
294 }
295
296 static int 
297 read_hdrs (mp, datesw)
298 register struct msgs    *mp;
299 register char           *datesw;
300 {
301     int         msgnum;
302     struct tws  tb;
303     register struct smsg *s;
304
305     twscopy (&tb, dtwstime ());
306
307     smsgs = (struct smsg *)
308         calloc ((unsigned) (mp->hghsel - mp->lowsel + 2),
309             sizeof *smsgs);
310     if (smsgs == NULL)
311         adios (NULLCP, "unable to allocate sort storage");
312
313     s = smsgs;
314     for (msgnum = mp->lowsel; msgnum <= mp->hghsel; msgnum++) {
315         if (mp->msgstats[msgnum] & SELECTED) {
316             if (getws (datesw, msgnum, s)) {
317                 s->s_msg = msgnum;
318                 s++;
319             }
320         }
321     }
322     s->s_msg = 0;
323     return(s - smsgs);
324 }
325
326 static
327 getws (datesw, msg, smsg)
328 register char *datesw;
329 int msg;
330 register struct smsg *smsg;
331 {
332     register int state;
333     int      compnum;
334     char    *msgnam,
335              buf[BUFSIZ],
336              nam[NAMESZ];
337     register struct tws *tw;
338     register char *datecomp = NULLCP,
339                   *subjcomp = NULLCP;
340     register FILE *in;
341
342     if ((in = fopen (msgnam = m_name (msg), "r")) == NULL) {
343         admonish (msgnam, "unable to read message");
344         return (0);
345     }
346     for (compnum = 1, state = FLD;;) {
347         switch (state = m_getfld (state, nam, buf, sizeof buf, in)) {
348         case FLD:
349         case FLDEOF:
350         case FLDPLUS:
351             compnum++;
352             if (uleq (nam, datesw)) {
353                 datecomp = add (buf, datecomp);
354                 while (state == FLDPLUS) {
355                     state = m_getfld (state, nam, buf, sizeof buf, in);
356                     datecomp = add (buf, datecomp);
357                 }
358                 if (!subjsort || subjcomp)
359                     break;
360             }
361             else if (subjsort && uleq (nam, subjsort)) {
362                 subjcomp = add (buf, subjcomp);
363                 while (state == FLDPLUS) {
364                     state = m_getfld (state, nam, buf, sizeof buf, in);
365                     subjcomp = add (buf, subjcomp);
366                 }
367                 if (datecomp)
368                     break;
369             }
370             else {
371                 /* just flush this guy */
372                 while (state == FLDPLUS)
373                     state = m_getfld (state, nam, buf, sizeof buf, in);
374             }
375             continue;
376
377         case BODY:
378         case BODYEOF:
379         case FILEEOF:
380             break;
381
382         case LENERR:
383         case FMTERR:
384             if (state == LENERR || state == FMTERR)
385                 admonish (NULLCP, "format error in message %d (header #%d)",
386                       msg, compnum);
387             if (datecomp)
388                 free (datecomp);
389             if (subjcomp)
390                 free (subjcomp);
391             (void) fclose (in);
392             return (0);
393
394         default:
395             adios (NULLCP, "internal error -- you lose");
396         }
397         break;
398     }
399
400     if (!datecomp || (tw = dparsetime (datecomp)) == NULL) {
401         struct stat st;
402
403         admonish (NULLCP, "can't parse %s field in message %d",
404               datesw, msg);
405
406         /* use the modify time of the file as its date */
407         (void) fstat (fileno (in), &st);
408         smsg->s_clock = st.st_mtime;
409     }
410     else
411         smsg->s_clock = twclock (tw);
412
413     if (subjsort) {
414         if (subjcomp) {
415             /*
416              * try to make the subject "canonical": delete
417              * leading "re:", everything but letters & smash
418              * letters to lower case. 
419              */
420             register char  *cp,
421                            *cp2,
422                             c;
423
424             cp = subjcomp;
425             cp2 = subjcomp;
426             if (strcmp (subjsort, "subject") == 0)
427                 while (c = *cp) {
428                     if (! isspace(c)) {
429                         if(uprf(cp, "re:"))
430                             cp += 2;
431                         else {
432                             if (isalnum(c))
433                                 *cp2++ = isupper(c) ? tolower(c) : c;
434                             break;
435                         }
436                     }
437                     cp++;
438                 }
439             while (c = *cp++) {
440                 if (isalnum(c))
441                     *cp2++ = isupper(c) ? tolower(c) : c;
442
443             }
444             *cp2 = '\0';
445         }
446         else
447             subjcomp = "";
448
449         smsg->s_subj = subjcomp;
450     }
451     (void) fclose (in);
452     if (datecomp)
453         free (datecomp);
454
455     return (1);
456 }
457
458 /*
459  * sort on dates.
460  */
461 static int 
462 dsort (a, b)
463 register struct smsg **a,
464                      **b;
465 {
466     if ((*a)->s_clock < (*b)->s_clock)
467         return (-1);
468     else if ((*a)->s_clock > (*b)->s_clock)
469         return (1);
470     else if ((*a)->s_msg < (*b)->s_msg)
471         return (-1);
472     else
473         return (1);
474 }
475
476 /*
477  * sort on subjects.
478  */
479 static int 
480 subsort (a, b)
481 register struct smsg **a,
482                      **b;
483 {
484     register int i;
485
486     if (i = strcmp ((*a)->s_subj, (*b)->s_subj))
487         return (i);
488
489     return (dsort (a, b));
490 }
491
492 static int 
493 txtsort (a, b)
494 register struct smsg **a,
495                      **b;
496 {
497     register int i;
498
499     if (i = strcmp ((*a)->s_subj, (*b)->s_subj))
500         return (i);
501     else if ((*a)->s_msg < (*b)->s_msg)
502         return (-1);
503     else
504         return (1);
505 }
506
507 static void   rename_chain (mp, mlist, msg, endmsg)
508 register struct msgs *mp;
509 struct smsg         **mlist;
510 int       msg,
511           endmsg;
512 {
513     int   nxt,
514           old,
515           new;
516     char *newname,
517           oldname[BUFSIZ];
518
519     for (;;) {
520         nxt = mlist[msg] - smsgs;       /* mlist[msg] is a ptr into smsgs */
521         mlist[msg] = (struct smsg *)0;
522         old = smsgs[nxt].s_msg;
523         new = smsgs[msg].s_msg;
524         (void) strcpy (oldname, m_name (old));
525         newname = m_name (new);
526         if (verbose)
527             printf ("message %d becomes message %d\n", old, new);
528
529         if (rename (oldname, newname) == NOTOK)
530             adios (newname, "unable to rename %s to", oldname);
531
532         mp->msgstats[new] = mp->msgstats[old];
533         if (mp->curmsg == old)
534             m_setcur (mp, new);
535
536         if (nxt == endmsg) 
537             break;
538
539         msg = nxt;
540     }
541 /*      if (nxt != endmsg); */
542 /*      rename_chain (mp, mlist, nxt, endmsg); */
543 }
544
545 static void
546 rename_msgs (mp, mlist)
547 register struct msgs *mp;
548 register struct smsg **mlist;
549 {
550     register int i,
551                  j,
552                  old,
553                  new;
554     int          stats;
555     char         f1[BUFSIZ],
556                  tmpfil[BUFSIZ];
557     register struct smsg *sp;
558
559     (void) strcpy (tmpfil, m_name (mp->hghmsg + 1));
560
561     for (i = 0; i < nmsgs; i++) {
562         if (! (sp = mlist[i])) 
563             continue;   /* did this one */
564
565         j = sp - smsgs;
566         if (j == i)
567             continue;   /* this one doesn't move */
568
569         /*
570          * the guy that was msg j is about to become msg i.
571          * rename 'j' to make a hole, then recursively rename
572          * guys to fill up the hole.
573          */
574         old = smsgs[j].s_msg;
575         new = smsgs[i].s_msg;
576         (void) strcpy (f1, m_name (old));
577
578         if (verbose)
579             printf ("renaming message chain from %d to %d\n", old, new);
580
581         if (rename (f1, tmpfil) == NOTOK)
582             adios (tmpfil, "unable to rename %s to ", f1);
583         stats = mp->msgstats[old];
584
585         rename_chain (mp, mlist, j, i);
586         if (rename (tmpfil, m_name(new)) == NOTOK)
587             adios (m_name(new), "unable to rename %s to", tmpfil);
588
589         mp->msgstats[new] = stats;
590         mp->msgflags |= SEQMOD;
591     }
592 }