source: trunk/third/nmh/uip/sortm.c @ 12455

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