summaryrefslogtreecommitdiff
path: root/pith/sort.c
diff options
context:
space:
mode:
Diffstat (limited to 'pith/sort.c')
-rw-r--r--pith/sort.c710
1 files changed, 710 insertions, 0 deletions
diff --git a/pith/sort.c b/pith/sort.c
new file mode 100644
index 00000000..68a9c10c
--- /dev/null
+++ b/pith/sort.c
@@ -0,0 +1,710 @@
+#if !defined(lint) && !defined(DOS)
+static char rcsid[] = "$Id: sort.c 1142 2008-08-13 17:22:21Z hubert@u.washington.edu $";
+#endif
+
+/*
+ * ========================================================================
+ * Copyright 2006-2007 University of Washington
+ * Copyright 2013 Eduardo Chappa
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * ========================================================================
+ */
+
+#include "../pith/headers.h"
+#include "../pith/sort.h"
+#include "../pith/state.h"
+#include "../pith/status.h"
+#include "../pith/msgno.h"
+#include "../pith/flag.h"
+#include "../pith/pineelt.h"
+#include "../pith/thread.h"
+#include "../pith/search.h"
+#include "../pith/pattern.h"
+#include "../pith/util.h"
+#include "../pith/signal.h"
+#include "../pith/busy.h"
+#include "../pith/icache.h"
+
+
+/*
+ * global place to store mail_sort and mail_thread results
+ */
+struct global_sort_data g_sort;
+
+
+/*
+ * Internal prototypes
+ */
+void sort_sort_callback(MAILSTREAM *, unsigned long *, unsigned long);
+int percent_sorted(void);
+int pine_compare_long(const qsort_t *, const qsort_t *);
+int pine_compare_long_rev(const qsort_t *, const qsort_t *);
+int pine_compare_scores(const qsort_t *, const qsort_t *);
+void build_score_array(MAILSTREAM *, MSGNO_S *);
+void free_score_array(void);
+
+
+/*----------------------------------------------------------------------
+ Map sort types to names
+ ----*/
+char *
+sort_name(SortOrder so)
+{
+ /*
+ * Make sure the first upper case letter of any new sort name is
+ * unique. The command char and label for sort selection is
+ * derived from this name and its first upper case character.
+ * See mailcmd.c:select_sort().
+ */
+ return((so == SortArrival) ? "Arrival" :
+ (so == SortDate) ? "Date" :
+ (so == SortSubject) ? "Subject" :
+ (so == SortCc) ? "Cc" :
+ (so == SortFrom) ? "From" :
+ (so == SortTo) ? "To" :
+ (so == SortSize) ? "siZe" :
+ (so == SortSubject2) ? "OrderedSubj" :
+ (so == SortScore) ? "scorE" :
+ (so == SortThread) ? "tHread" :
+ "BOTCH");
+}
+
+
+/*----------------------------------------------------------------------
+ Sort the current folder into the order set in the msgmap
+
+Args: msgmap --
+ new_sort --
+ new_rev --
+
+ The idea of the deferred sort is to let the user interrupt a long sort
+ and have a chance to do a different command, such as a sort by arrival
+ or a Goto. The next newmail call will increment the deferred variable,
+ then the user may do a command, then the newmail call after that
+ causes the sort to happen if it is still needed.
+ ----*/
+void
+sort_folder(MAILSTREAM *stream, MSGNO_S *msgmap, SortOrder new_sort,
+ int new_rev, unsigned int flags)
+{
+ long raw_current, i, j;
+ unsigned long *sort = NULL;
+ int we_cancel = 0;
+ char sort_msg[MAX_SCREEN_COLS+1];
+ SortOrder current_sort;
+ int current_rev;
+ MESSAGECACHE *mc;
+
+ dprint((2, "Sorting by %s%s\n",
+ sort_name(new_sort), new_rev ? "/reverse" : ""));
+
+ if(!msgmap)
+ return;
+
+ current_sort = mn_get_sort(msgmap);
+ current_rev = mn_get_revsort(msgmap);
+ /*
+ * If we were previously threading (spare == 1) and now we're switching
+ * sorts (other than just a rev switch) then erase the information
+ * about the threaded state (collapsed and so forth).
+ */
+ if(stream && stream->spare && (current_sort != new_sort))
+ erase_threading_info(stream, msgmap);
+
+ if(mn_get_total(msgmap) <= 1L
+ && !(mn_get_total(msgmap) == 1L
+ && (new_sort == SortThread || new_sort == SortSubject2))){
+ mn_set_sort(msgmap, new_sort);
+ mn_set_revsort(msgmap, new_rev);
+ if(!mn_get_mansort(msgmap))
+ mn_set_mansort(msgmap, (flags & SRT_MAN) ? 1 : 0);
+
+ return;
+ }
+
+ raw_current = mn_m2raw(msgmap, mn_get_cur(msgmap));
+
+ if(new_sort == SortArrival){
+ /*
+ * NOTE: RE c-client sorting, our idea of arrival is really
+ * just the natural sequence order. C-client, and probably
+ * rightly so, considers "arrival" the order based on the
+ * message's internal date. This is more costly to compute
+ * since it means touching the message store (say "nntp"?),
+ * so we just preempt it here.
+ *
+ * Someday c-client will support "unsorted" and do what
+ * we're doing here. That day this gets scrapped.
+ */
+
+ mn_set_sort(msgmap, new_sort);
+ mn_set_revsort(msgmap, new_rev);
+
+ if(current_sort != new_sort || current_rev != new_rev ||
+ any_lflagged(msgmap, MN_EXLD))
+ clear_index_cache(stream, 0);
+
+ if(any_lflagged(msgmap, MN_EXLD)){
+ /*
+ * BEWARE: "exclusion" may leave holes in the unsorted sort order
+ * so we have to do a real sort if that is the case.
+ */
+ qsort(msgmap->sort+1, (size_t) mn_get_total(msgmap),
+ sizeof(long),
+ new_rev ? pine_compare_long_rev : pine_compare_long);
+ }
+ else if(mn_get_total(msgmap) > 0L){
+ if(new_rev){
+ clear_index_cache(stream, 0);
+ for(i = 1L, j = mn_get_total(msgmap); j >= 1; i++, j--)
+ msgmap->sort[i] = j;
+ }
+ else
+ for(i = 1L; i <= mn_get_total(msgmap); i++)
+ msgmap->sort[i] = i;
+ }
+
+ /* reset the inverse array */
+ msgno_reset_isort(msgmap);
+ }
+ else if(new_sort == SortScore){
+
+ /*
+ * We have to build a temporary array which maps raw msgno to
+ * score. We use the index cache machinery to build the array.
+ */
+
+ mn_set_sort(msgmap, new_sort);
+ mn_set_revsort(msgmap, new_rev);
+
+ if(flags & SRT_VRB){
+ /* TRANSLATORS: tell user they are waiting for Sorting of %s, the foldername */
+ snprintf(sort_msg, sizeof(sort_msg), _("Sorting \"%s\""),
+ strsquish(tmp_20k_buf + 500, 500, ps_global->cur_folder,
+ ps_global->ttyo->screen_cols - 20));
+ we_cancel = busy_cue(sort_msg, NULL, 0);
+ }
+
+ /*
+ * We do this so that we don't have to lookup the scores with function
+ * calls for each qsort compare.
+ */
+ build_score_array(stream, msgmap);
+
+ qsort(msgmap->sort+1, (size_t) mn_get_total(msgmap),
+ sizeof(long), pine_compare_scores);
+ free_score_array();
+ clear_index_cache(stream, 0);
+
+ if(we_cancel)
+ cancel_busy_cue(1);
+
+ /*
+ * Flip the sort if necessary (cheaper to do it once than for
+ * every comparison in pine_compare_scores.
+ */
+ if(new_rev && mn_get_total(msgmap) > 1L){
+ long *ep = &msgmap->sort[mn_get_total(msgmap)],
+ *sp = &msgmap->sort[1], tmp;
+
+ do{
+ tmp = *sp;
+ *sp++ = *ep;
+ *ep-- = tmp;
+ }
+ while(ep > sp);
+ }
+
+ /* reset the inverse array */
+ msgno_reset_isort(msgmap);
+ }
+ else{
+
+ mn_set_sort(msgmap, new_sort);
+ mn_set_revsort(msgmap, new_rev);
+ clear_index_cache(stream, 0);
+
+ if(flags & SRT_VRB){
+ int (*sort_func)() = NULL;
+
+ /*
+ * IMAP sort doesn't give us any way to get progress,
+ * so just spin the bar rather than show zero percent
+ * forever while a slow sort's going on...
+ */
+ if(!(stream && stream->dtb && stream->dtb->name
+ && !strucmp(stream->dtb->name, "imap")
+ && LEVELSORT(stream)))
+ sort_func = percent_sorted;
+
+ snprintf(sort_msg, sizeof(sort_msg), _("Sorting \"%s\""),
+ strsquish(tmp_20k_buf + 500, 500, ps_global->cur_folder,
+ ps_global->ttyo->screen_cols - 20));
+ we_cancel = busy_cue(sort_msg, sort_func, 0);
+ }
+
+ /*
+ * Limit the sort/thread if messages are hidden from view
+ * by lighting searched bit of every interesting msg in
+ * the folder and call c-client thread/sort to do the dirty work.
+ *
+ * Unfortunately it isn't that easy. IMAP servers are not able to
+ * handle medium to large sized sequence sets (more than 1000
+ * characters in the command line breaks some) so we have to try
+ * to handle it locally. By lighting the searched bits and
+ * providing a NULL search program we get a special c-client
+ * interface. This means that c-client will attempt to send the
+ * sequence set with the SORT or THREAD but it may get back
+ * a BAD response because of long command lines. In that case,
+ * if it is a SORT call, c-client will issue the full SORT
+ * without the sequence sets and will then filter the results
+ * locally. So sort_sort_callback will see the correctly
+ * filtered results. If it is a mail_thread call, a similar thing
+ * will be done. If a BAD is received, then there is no way to
+ * easily filter the results. C-client (in this special case where
+ * we provide a NULL search program) will set tree->num to zero
+ * for nodes of the thread tree which were supposed to be
+ * filtered out of the thread. Then pine, in sort_thread_callback,
+ * will treat those as dummy nodes (nodes which are part of the
+ * tree logically but where we don't have access to the messages).
+ * This will give us a different answer than we would have gotten
+ * if the restricted thread would have worked, but it's something.
+ *
+ * It isn't in general possible to give some shorter search set
+ * in place of the long sequence set because the long sequence set
+ * may be the result of several filter rules or of excluded
+ * messages in news (in this 2nd case maybe we could give
+ * a shorter search set).
+ *
+ * We note also that the too-long commands in imap is a general
+ * imap deficiency. It comes up in particular also in SEARCH
+ * commands. Pine likes to exclude the hidden messages from the
+ * SEARCH. SEARCH will be handled transparently by the local
+ * c-client by first issuing the full SEARCH command, if that
+ * comes back with a BAD and there is a pgm->msgno at the top
+ * level of pgm, then c-client will re-issue the SEARCH command
+ * but without the msgno sequence set in hopes that the resulting
+ * command will now be short enough, and then it will filter out
+ * the sequence set locally. If that doesn't work, it will
+ * download the messages and do the SEARCH locally. That is
+ * controllable by a flag bit.
+ */
+ for(i = 1L; i <= stream->nmsgs; i++)
+ if((mc = mail_elt(stream, i)) != NULL)
+ mc->searched = !get_lflag(stream, NULL, i, MN_EXLD);
+
+ g_sort.msgmap = msgmap;
+ if(new_sort == SortThread || new_sort == SortSubject2){
+ THREADNODE *thread;
+
+ /*
+ * Install callback to collect thread results
+ * and update sort mapping. Problem this solves
+ * is that of receiving exists/expunged events
+ * within sort/thread response. Since we update
+ * the sorted table within those handlers, we
+ * can get out of sync when we replace possibly
+ * stale sort/thread results once the function
+ * call's returned. Make sense? Thought so.
+ */
+ mail_parameters(NULL, SET_THREADRESULTS,
+ (void *) sort_thread_callback);
+
+ thread = mail_thread(stream,
+ (new_sort == SortThread)
+ ? "REFERENCES" : "ORDEREDSUBJECT",
+ NULL, NULL, 0L);
+
+ mail_parameters(NULL, SET_THREADRESULTS, (void *) NULL);
+
+ if(!thread){
+ new_sort = current_sort;
+ new_rev = current_rev;
+ q_status_message1(SM_ORDER, 3, 3,
+ "Sort Failed! Restored %.200s sort.",
+ sort_name(new_sort));
+ }
+
+ if(thread)
+ mail_free_threadnode(&thread);
+ }
+ else{
+ /*
+ * Set up the sort program.
+ * NOTE: we deal with reverse bit below.
+ */
+ g_sort.prog = mail_newsortpgm();
+ g_sort.prog->function = (new_sort == SortSubject)
+ ? SORTSUBJECT
+ : (new_sort == SortFrom)
+ ? SORTFROM
+ : (new_sort == SortTo)
+ ? SORTTO
+ : (new_sort == SortCc)
+ ? SORTCC
+ : (new_sort == SortDate)
+ ? SORTDATE
+ : (new_sort == SortSize)
+ ? SORTSIZE
+ : SORTARRIVAL;
+
+ mail_parameters(NULL, SET_SORTRESULTS,
+ (void *) sort_sort_callback);
+
+ /* Where the rubber meets the road. */
+ sort = mail_sort(stream, NULL, NULL, g_sort.prog, 0L);
+
+ mail_parameters(NULL, SET_SORTRESULTS, (void *) NULL);
+
+ if(!sort){
+ new_sort = current_sort;
+ new_rev = current_rev;
+ q_status_message1(SM_ORDER, 3, 3,
+ "Sort Failed! Restored %s sort.",
+ sort_name(new_sort));
+ }
+
+ if(sort)
+ fs_give((void **) &sort);
+
+ mail_free_sortpgm(&g_sort.prog);
+ }
+
+ if(we_cancel)
+ cancel_busy_cue(1);
+
+ /*
+ * Flip the sort if necessary (cheaper to do it once than for
+ * every comparison underneath mail_sort()
+ */
+ if(new_rev && mn_get_total(msgmap) > 1L){
+ long *ep = &msgmap->sort[mn_get_total(msgmap)],
+ *sp = &msgmap->sort[1], tmp;
+
+ do{
+ tmp = *sp;
+ *sp++ = *ep;
+ *ep-- = tmp;
+ }
+ while(ep > sp);
+
+ /* reset the inverse array */
+ msgno_reset_isort(msgmap);
+
+ /*
+ * Flip the thread numbers around.
+ * This puts us in a weird state that requires keeping track
+ * of. The direction of the thread list hasn't changed, but the
+ * thrdnos have and the display direction has.
+ *
+ * For Sort thrdno 1 thread list head
+ * thrdno 2 |
+ * thrdno . v nextthd this direction
+ * thrdno .
+ * thrdno n thread list tail
+ *
+ * Rev Sort thrdno 1 thread list tail
+ * thrdno 2
+ * thrdno . ^ nextthd this direction
+ * thrdno . |
+ * thrdno n thread list head
+ */
+ if(new_sort == SortThread || new_sort == SortSubject2){
+ PINETHRD_S *thrd;
+
+ thrd = fetch_head_thread(stream);
+ for(j = msgmap->max_thrdno; thrd && j >= 1L; j--){
+ thrd->thrdno = j;
+
+ if(thrd->nextthd)
+ thrd = fetch_thread(stream,
+ thrd->nextthd);
+ else
+ thrd = NULL;
+ }
+ }
+ }
+ }
+
+ /* Fix up sort structure */
+ mn_set_sort(msgmap, new_sort);
+ mn_set_revsort(msgmap, new_rev);
+ /*
+ * Once a folder has been sorted manually, we continue treating it
+ * as manually sorted until it is closed.
+ */
+ if(!mn_get_mansort(msgmap))
+ mn_set_mansort(msgmap, (flags & SRT_MAN) ? 1 : 0);
+
+ if(!msgmap->hilited){
+ /*
+ * If current is hidden, change current to visible parent.
+ * It can only be hidden if we are threading.
+ *
+ * Don't do this if hilited is set, because it means we're in the
+ * middle of an aggregate op, and this will mess up our selection.
+ * "hilited" means we've done a pseudo_selected, which we'll later
+ * fix with restore_selected.
+ */
+ if(THREADING())
+ mn_reset_cur(msgmap, first_sorted_flagged(new_rev ? F_NONE : F_SRCHBACK,
+ stream,
+ mn_raw2m(msgmap, raw_current),
+ FSF_SKIP_CHID));
+ else
+ mn_reset_cur(msgmap, mn_raw2m(msgmap, raw_current));
+ }
+
+ msgmap->top = -1L;
+
+ if(!sp_mail_box_changed(stream))
+ sp_set_unsorted_newmail(stream, 0);
+
+ /*
+ * Turn off the MN_USOR flag. Don't bother going through the
+ * function call and the message number mappings.
+ */
+ if(THREADING()){
+ PINELT_S *pelt;
+
+ for(i = 1L; i <= stream->nmsgs; i++)
+ if((mc = mail_elt(stream, i)) != NULL && (pelt = mc->sparep) != NULL)
+ pelt->unsorted = 0;
+ }
+}
+
+
+void
+sort_sort_callback(MAILSTREAM *stream, long unsigned int *list, long unsigned int nmsgs)
+{
+ long i;
+
+ dprint((2, "sort_sort_callback\n"));
+
+ if(mn_get_total(g_sort.msgmap) < nmsgs)
+ panic("Message count shrank after sort!");
+
+ /* copy ulongs to array of longs */
+ for(i = nmsgs; i > 0; i--)
+ g_sort.msgmap->sort[i] = list ? (long) list[i-1] : i-1;
+
+ /* reset the inverse array */
+ msgno_reset_isort(g_sort.msgmap);
+
+ dprint((2, "sort_sort_callback done\n"));
+}
+
+
+/*
+ * Return value for use by progress bar.
+ */
+int
+percent_sorted(void)
+{
+ /*
+ * C-client's sort routine exports two types of status
+ * indicators. One's the progress thru loading the cache (typically
+ * the elephantine bulk of the delay, and the progress thru the
+ * actual sort (typically qsort). Our job is to balance the two
+ */
+
+ return(g_sort.prog && g_sort.prog->nmsgs
+ ? (((((g_sort.prog->progress.cached * 100)
+ / g_sort.prog->nmsgs) * 9) / 10)
+ + (((g_sort.prog->progress.sorted) * 10)
+ / g_sort.prog->nmsgs))
+ : 0);
+}
+
+
+/*
+ * This is only used at startup time. It sets ps->def_sort and
+ * ps->def_sort_rev. The syntax of the sort_spec is type[/reverse].
+ * A reverse without a type is the same as arrival/reverse. A blank
+ * argument also means arrival/reverse.
+ */
+int
+decode_sort(char *sort_spec, SortOrder *def_sort, int *def_sort_rev)
+{
+ char *sep;
+ char *fix_this = NULL;
+ int x, reverse;
+
+ if(!sort_spec || !*sort_spec){
+ *def_sort = SortArrival;
+ *def_sort_rev = 0;
+ return(0);
+ }
+
+ if(struncmp(sort_spec, "reverse", strlen(sort_spec)) == 0){
+ *def_sort = SortArrival;
+ *def_sort_rev = 1;
+ return(0);
+ }
+
+ reverse = 0;
+ if((sep = strindex(sort_spec, '/')) != NULL){
+ *sep = '\0';
+ fix_this = sep;
+ sep++;
+ if(struncmp(sep, "reverse", strlen(sep)) == 0)
+ reverse = 1;
+ else{
+ *fix_this = '/';
+ return(-1);
+ }
+ }
+
+ for(x = 0; ps_global->sort_types[x] != EndofList; x++)
+ if(struncmp(sort_name(ps_global->sort_types[x]),
+ sort_spec, strlen(sort_spec)) == 0)
+ break;
+
+ if(fix_this)
+ *fix_this = '/';
+
+ if(ps_global->sort_types[x] == EndofList)
+ return(-1);
+
+ *def_sort = ps_global->sort_types[x];
+ *def_sort_rev = reverse;
+ return(0);
+}
+
+
+/*----------------------------------------------------------------------
+ Compare raw message numbers
+ ----*/
+int
+pine_compare_long(const qsort_t *a, const qsort_t *b)
+{
+ long *mess_a = (long *)a, *mess_b = (long *)b, mdiff;
+
+ return((mdiff = *mess_a - *mess_b) ? ((mdiff > 0L) ? 1 : -1) : 0);
+}
+
+/*
+ * reverse version of pine_compare_long
+ */
+int
+pine_compare_long_rev(const qsort_t *a, const qsort_t *b)
+{
+ long *mess_a = (long *)a, *mess_b = (long *)b, mdiff;
+
+ return((mdiff = *mess_a - *mess_b) ? ((mdiff < 0L) ? 1 : -1) : 0);
+}
+
+
+long *g_score_arr;
+
+/*
+ * This calculate all of the scores and also puts them into a temporary array
+ * for speed when sorting.
+ */
+void
+build_score_array(MAILSTREAM *stream, MSGNO_S *msgmap)
+{
+ SEARCHSET *searchset;
+ long msgno, cnt, nmsgs, rawmsgno;
+ long score;
+ MESSAGECACHE *mc;
+
+ nmsgs = mn_get_total(msgmap);
+ g_score_arr = (long *) fs_get((nmsgs+1) * sizeof(score));
+ memset(g_score_arr, 0, (nmsgs+1) * sizeof(score));
+
+ /*
+ * Build a searchset that contains everything except those that have
+ * already been looked up.
+ */
+
+ for(msgno=1L; msgno <= stream->nmsgs; msgno++)
+ if((mc = mail_elt(stream, msgno)) != NULL)
+ mc->sequence = 0;
+
+ for(cnt=0L, msgno=1L; msgno <= nmsgs; msgno++){
+ rawmsgno = mn_m2raw(msgmap, msgno);
+ if(get_msg_score(stream, rawmsgno) == SCORE_UNDEF
+ && rawmsgno > 0L && stream && rawmsgno <= stream->nmsgs
+ && (mc = mail_elt(stream, rawmsgno))){
+ mc->sequence = 1;
+ cnt++;
+ }
+ }
+
+ if(cnt){
+ searchset = build_searchset(stream);
+ (void)calculate_some_scores(stream, searchset, 0);
+ mail_free_searchset(&searchset);
+ }
+
+ /*
+ * Copy scores to g_score_arr. They should all be defined now but if
+ * they aren't assign score zero.
+ */
+ for(rawmsgno = 1L; rawmsgno <= nmsgs; rawmsgno++){
+ score = get_msg_score(stream, rawmsgno);
+ g_score_arr[rawmsgno] = (score == SCORE_UNDEF) ? 0L : score;
+ }
+}
+
+
+void
+free_score_array(void)
+{
+ if(g_score_arr)
+ fs_give((void **) &g_score_arr);
+}
+
+
+/*----------------------------------------------------------------------
+ Compare scores
+ ----*/
+int
+pine_compare_scores(const qsort_t *a, const qsort_t *b)
+{
+ long *mess_a = (long *)a, *mess_b = (long *)b, mdiff;
+ long sdiff;
+
+ return((sdiff = g_score_arr[*mess_a] - g_score_arr[*mess_b])
+ ? ((sdiff > 0L) ? 1 : -1)
+ : ((mdiff = *mess_a - *mess_b) ? ((mdiff > 0) ? 1 : -1) : 0));
+}
+
+
+void
+reset_sort_order(unsigned int flags)
+{
+ long rflags = ROLE_DO_OTHER;
+ PAT_STATE pstate;
+ PAT_S *pat;
+ SortOrder the_sort_order;
+ int sort_is_rev;
+
+ /* set default order */
+ the_sort_order = ps_global->def_sort;
+ sort_is_rev = ps_global->def_sort_rev;
+
+ if(ps_global->mail_stream && nonempty_patterns(rflags, &pstate)){
+ for(pat = first_pattern(&pstate); pat; pat = next_pattern(&pstate)){
+ if(match_pattern(pat->patgrp, ps_global->mail_stream, NULL,
+ NULL, NULL, SE_NOSERVER|SE_NOPREFETCH))
+ break;
+ }
+
+ if(pat && pat->action && !pat->action->bogus
+ && pat->action->sort_is_set){
+ the_sort_order = pat->action->sortorder;
+ sort_is_rev = pat->action->revsort;
+ }
+ }
+
+ sort_folder(ps_global->mail_stream, ps_global->msgmap,
+ the_sort_order, sort_is_rev, flags);
+}