"Fossies" - the Fresh Open Source Software Archive

Member "tin-2.4.1/src/tags.c" (12 Oct 2016, 13345 Bytes) of package /linux/misc/tin-2.4.1.tar.gz:


As a special service "Fossies" has tried to format the requested source page into HTML format using (guessed) C and C++ source code syntax highlighting (style: standard) with prefixed line numbers and code folding option. Alternatively you can here view or download the uninterpreted source code file. For more information about "tags.c" see the Fossies "Dox" file reference documentation and the latest Fossies "Diffs" side-by-side code changes report: 2.4.0_vs_2.4.1.

    1 /*
    2  *  Project   : tin - a Usenet reader
    3  *  Module    : tags.c
    4  *  Author    : Jason Faultless <jason@altarstone.com>
    5  *  Created   : 1999-12-06
    6  *  Updated   : 2010-04-02
    7  *  Notes     : Split out from other modules
    8  *
    9  * Copyright (c) 1999-2017 Jason Faultless <jason@altarstone.com>
   10  * All rights reserved.
   11  *
   12  * Redistribution and use in source and binary forms, with or without
   13  * modification, are permitted provided that the following conditions
   14  * are met:
   15  * 1. Redistributions of source code must retain the above copyright
   16  *    notice, this list of conditions and the following disclaimer.
   17  * 2. Redistributions in binary form must reproduce the above copyright
   18  *    notice, this list of conditions and the following disclaimer in the
   19  *    documentation and/or other materials provided with the distribution.
   20  * 3. The name of the author may not be used to endorse or promote
   21  *    products derived from this software without specific prior written
   22  *    permission.
   23  *
   24  * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS
   25  * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
   26  * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
   27  * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
   28  * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
   29  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE
   30  * GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
   31  * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
   32  * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
   33  * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
   34  * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
   35  */
   36 
   37 #ifndef TIN_H
   38 #   include "tin.h"
   39 #endif /* !TIN_H */
   40 
   41 /* Local prototypes */
   42 static int get_multipart_info(int base_index, MultiPartInfo *setme);
   43 static int get_multiparts(int base_index, MultiPartInfo **malloc_and_setme_info);
   44 static int look_for_multipart_info(int base_index, MultiPartInfo *setme, char start, char stop, int *offset);
   45 static t_bool parse_range(char *range, int min, int max, int curr, int *range_start, int *range_end);
   46 
   47 int num_of_tagged_arts = 0;
   48 
   49 /*
   50  * Parses a subject header of the type "multipart message subject (01/42)"
   51  * into a MultiPartInfo struct, or fails if the message subject isn't in the
   52  * right form.
   53  *
   54  * @return nonzero on success
   55  */
   56 static int
   57 get_multipart_info(
   58     int base_index,
   59     MultiPartInfo *setme)
   60 {
   61     int i, j, offi, offj;
   62     MultiPartInfo setmei, setmej;
   63 
   64     i = look_for_multipart_info(base_index, &setmei, '[', ']', &offi);
   65     j = look_for_multipart_info(base_index, &setmej, '(', ')', &offj);
   66 
   67     /* Ok i hits first */
   68     if (offi > offj) {
   69         *setme = setmei;
   70         return i;
   71     }
   72 
   73     /* Its j or they are both the same (which must be zero!) so we don't care */
   74     *setme = setmej;
   75     return j;
   76 }
   77 
   78 
   79 static int
   80 look_for_multipart_info(
   81     int base_index,
   82     MultiPartInfo* setme,
   83     char start,
   84     char stop,
   85     int *offset)
   86 {
   87     MultiPartInfo tmp;
   88     char *subj;
   89     char *pch;
   90 
   91     *offset = 0;
   92 
   93     /* entry assertions */
   94     assert(0 <= base_index && base_index < grpmenu.max && "invalid base_index");
   95     assert(setme != NULL && "setme must not be NULL");
   96 
   97     /* parse the message */
   98     subj = arts[base[base_index]].subject;
   99     if (!(pch = strrchr(subj, start)))
  100         return 0;
  101     if (!isdigit((int) pch[1]))
  102         return 0;
  103     tmp.base_index = base_index;
  104     tmp.subject_compare_len = pch - subj;
  105     tmp.part_number = (int) strtol(pch + 1, &pch, 10);
  106     if (*pch != '/' && *pch != '|')
  107         return 0;
  108     if (!isdigit((int) pch[1]))
  109         return 0;
  110     tmp.total = (int) strtol(pch + 1, &pch, 10);
  111     if (*pch != stop)
  112         return 0;
  113     tmp.subject = subj;
  114     *setme = tmp;
  115     *offset = pch - subj;
  116     return 1;
  117 }
  118 
  119 
  120 /*
  121  * Tries to find all the parts to the multipart message pointed to by
  122  * base_index.
  123  *
  124  * Weakness(?): only walks through the base messages.
  125  *
  126  * @return on success, the number of parts found. On failure, zero if not a
  127  * multipart or the negative value of the first missing part.
  128  * @param base_index index pointing to one of the messages in a multipart
  129  * message.
  130  * @param malloc_and_setme_info on success, set to a malloced array the
  131  * parts found. Untouched on failure.
  132  */
  133 static int
  134 get_multiparts(
  135     int base_index,
  136     MultiPartInfo **malloc_and_setme_info)
  137 {
  138     MultiPartInfo tmp, tmp2;
  139     MultiPartInfo *info = NULL;
  140     int i;
  141     int part_index;
  142 
  143     /* entry assertions */
  144     assert(0 <= base_index && base_index < grpmenu.max && "Invalid base index");
  145     assert(malloc_and_setme_info != NULL && "malloc_and_setme_info must not be NULL");
  146 
  147     /* make sure this is a multipart message... */
  148     if (!get_multipart_info(base_index, &tmp) || tmp.total < 1)
  149         return 0;
  150 
  151     /* make a temporary buffer to hold the multipart info... */
  152     info = my_malloc(sizeof(MultiPartInfo) * tmp.total);
  153 
  154     /* zero out part-number for the repost check below */
  155     for (i = 0; i < tmp.total; ++i)
  156         info[i].part_number = -1;
  157 
  158     /* try to find all the multiparts... */
  159     for (i = 0; i < grpmenu.max; ++i) {
  160         if (strncmp(arts[base[i]].subject, tmp.subject, tmp.subject_compare_len))
  161             continue;
  162         if (!get_multipart_info(i, &tmp2))
  163             continue;
  164 
  165         part_index = tmp2.part_number - 1;
  166 
  167         /* skip the "blah (00/102)" info messages... */
  168         if (part_index < 0)
  169             continue;
  170 
  171         /* skip insane "blah (103/102) subjects... */
  172         if (part_index >= tmp.total)
  173             continue;
  174 
  175         /* repost check: do we already have this part? */
  176         if (info[part_index].part_number != -1) {
  177             assert(info[part_index].part_number == tmp2.part_number && "bookkeeping error");
  178             continue;
  179         }
  180 
  181         /* we have a match, hooray! */
  182         info[part_index] = tmp2;
  183     }
  184 
  185     /* see if we got them all. */
  186     for (i = 0; i < tmp.total; ++i) {
  187         if (info[i].part_number != i + 1) {
  188             free(info);
  189             return -(i + 1); /* missing part #(i+1) */
  190         }
  191     }
  192 
  193     /* looks like a success .. */
  194     *malloc_and_setme_info = info;
  195     return tmp.total;
  196 }
  197 
  198 
  199 /*
  200  * Tags all parts of a multipart index if base_index points
  201  * to a multipart message and all its parts can be found.
  202  *
  203  * @param base_index points to one message in a multipart message.
  204  * @return number of messages tagged, or zero on failure
  205  */
  206 int
  207 tag_multipart(
  208     int base_index)
  209 {
  210     MultiPartInfo *info = NULL;
  211     int i;
  212     const int qty = get_multiparts(base_index, &info);
  213 
  214     /* check for failure... */
  215     if (qty == 0) {
  216         info_message(_(txt_info_not_multipart_message));
  217         return 0;
  218     }
  219     if (qty < 0) {
  220         info_message(_(txt_info_missing_part), -qty);
  221         return 0;
  222     }
  223 
  224     /*
  225      * if any are already tagged, untag 'em first
  226      * so num_of_tagged_arts doesn't get corrupted
  227      */
  228     for (i = 0; i < qty; ++i) {
  229         if (arts[base[info[i].base_index]].tagged != 0)
  230             untag_article(base[info[i].base_index]);
  231     }
  232 
  233     /*
  234      * get_multiparts() sorts info by part number,
  235      * so a simple for loop tags in the right order
  236      */
  237     for (i = 0; i < qty; ++i)
  238         arts[base[info[i].base_index]].tagged = ++num_of_tagged_arts;
  239 
  240     free(info);
  241 
  242     return qty;
  243 }
  244 
  245 
  246 /*
  247  * Return the highest tag number of any article in thread
  248  * rooted at base[n]
  249  */
  250 int
  251 line_is_tagged(
  252     int n)
  253 {
  254     int code = 0;
  255 
  256     if (curr_group->attribute->thread_articles) {
  257         int i;
  258         for (i = n; i >= 0; i = arts[i].thread) {
  259             if (arts[i].tagged > code)
  260                 code = arts[i].tagged;
  261         }
  262     } else
  263         code = arts[n].tagged;
  264 
  265     return code;
  266 }
  267 
  268 
  269 /*
  270  * Toggle tag status of an article. Returns TRUE if we tagged the article
  271  * FALSE if we untagged it.
  272  */
  273 t_bool
  274 tag_article(
  275     int art)
  276 {
  277     if (arts[art].tagged != 0) {
  278         untag_article(art);
  279         info_message(_(txt_prefix_untagged), txt_article_singular);
  280         return FALSE;
  281     } else {
  282         arts[art].tagged = ++num_of_tagged_arts;
  283         info_message(_(txt_prefix_tagged), txt_article_singular);
  284         return TRUE;
  285     }
  286 }
  287 
  288 
  289 /*
  290  * Remove the tag from an article
  291  * Work through all the threads and decrement the tag counter on all arts
  292  * greater than 'tag', fixup counters
  293  */
  294 void
  295 untag_article(
  296     long art)
  297 {
  298     int i, j;
  299 
  300     for (i = 0; i < grpmenu.max; ++i) {
  301         for_each_art_in_thread(j, i) {
  302             if (arts[j].tagged > arts[art].tagged)
  303                 --arts[j].tagged;
  304         }
  305     }
  306     arts[art].tagged = 0;
  307     --num_of_tagged_arts;
  308 }
  309 
  310 
  311 /*
  312  * Clear tag status of all articles. If articles were untagged, return TRUE
  313  */
  314 t_bool
  315 untag_all_articles(
  316     void)
  317 {
  318     int i;
  319     t_bool untagged = FALSE;
  320 
  321     for_each_art(i) {
  322         if (arts[i].tagged != 0) {
  323             arts[i].tagged = 0;
  324             untagged = TRUE;
  325         }
  326     }
  327     num_of_tagged_arts = 0;
  328 
  329     return untagged;
  330 }
  331 
  332 
  333 /*
  334  * RANGE CODE
  335  */
  336 /*
  337  * Allows user to specify an group/article range that a followup
  338  * command will operate on (eg. catchup articles 1-56) # 1-56 K
  339  * min/max/curr are the lowest/highest and current positions on the
  340  * menu from which this was called; used as defaults if needed
  341  * Return TRUE if a range was successfully read, parsed and set
  342  *
  343  * Allowed syntax is 0123456789-.$ (blanks are ignored):
  344  *   1-23    mark grp/art 1 through 23
  345  *   1-.     mark grp/art 1 through current
  346  *   1-$     mark grp/art 1 through last
  347  *   .-$     mark grp/art current through last
  348  */
  349 t_bool
  350 set_range(
  351     int level,
  352     int min,
  353     int max,
  354     int curr)
  355 {
  356     char *range;
  357     char *prompt;
  358     int artnum;
  359     int i;
  360     int depth;
  361     int range_min;
  362     int range_max;
  363 
  364     switch (level) {
  365         case SELECT_LEVEL:
  366             range = tinrc.default_range_select;
  367             break;
  368 
  369         case GROUP_LEVEL:
  370             range = tinrc.default_range_group;
  371             break;
  372 
  373         case THREAD_LEVEL:
  374             range = tinrc.default_range_thread;
  375             break;
  376 
  377         default:    /* should no happen */
  378             return FALSE;
  379     }
  380 
  381 #if 0
  382     error_message(2, "Min=[%d] Max=[%d] Cur=[%d] DefRng=[%s]", min, max, curr, range);
  383 #endif /* 0 */
  384     prompt = fmt_string(_(txt_enter_range), range);
  385 
  386     if (!(prompt_string_default(prompt, range, _(txt_range_invalid), HIST_OTHER))) {
  387         free(prompt);
  388         return FALSE;
  389     }
  390     free(prompt);
  391 
  392     /*
  393      * Parse range string
  394      */
  395     if (!parse_range(range, min, max, curr, &range_min, &range_max)) {
  396         info_message(_(txt_range_invalid));
  397         return FALSE;
  398     }
  399 
  400     switch (level) {
  401         case SELECT_LEVEL:
  402             for (i = 0; i < max; i++)           /* Clear existing range */
  403                 active[my_group[i]].inrange = FALSE;
  404 
  405             for (i = range_min - 1; i < range_max; i++)
  406                 active[my_group[i]].inrange = TRUE;
  407             break;
  408 
  409         case GROUP_LEVEL:
  410             for (i = 0; i < max; i++) {         /* Clear existing range */
  411                 for_each_art_in_thread(artnum, i)
  412                     arts[artnum].inrange = FALSE;
  413             }
  414 
  415             for (i = range_min - 1; i < range_max; i++) {
  416                 for_each_art_in_thread(artnum, i)
  417                     arts[artnum].inrange = TRUE;
  418             }
  419             break;
  420 
  421         case THREAD_LEVEL:
  422             /*
  423              * Debatably should clear all of arts[] depending on how you
  424              * interpret the (non)spec
  425              */
  426             for (i = 0; i < grpmenu.max; i++) {         /* Clear existing range */
  427                 for_each_art_in_thread(artnum, i)
  428                     arts[artnum].inrange = FALSE;
  429             }
  430 
  431             depth = 1;
  432             for_each_art_in_thread(artnum, thread_basenote) {
  433                 if (depth > range_max)
  434                     break;
  435                 if (depth >= range_min)
  436                     arts[artnum].inrange = TRUE;
  437                 depth++;
  438             }
  439             break;
  440 
  441         default:
  442             return FALSE;
  443             /* NOTREACHED */
  444             break;
  445     }
  446     return TRUE;
  447 }
  448 
  449 
  450 /*
  451  * Parse 'range', return the range start and end values in range_start and range_end
  452  * min/max/curr are used to select defaults when n explicit start/end are given
  453  */
  454 static t_bool
  455 parse_range(
  456     char *range,
  457     int min,
  458     int max,
  459     int curr,
  460     int *range_start,
  461     int *range_end)
  462 {
  463     char *ptr = range;
  464     enum states { FINDMIN, FINDMAX, DONE };
  465     int state = FINDMIN;
  466     t_bool ret = FALSE;
  467 
  468     *range_start = -1;
  469     *range_end = -1;
  470 
  471     while (*ptr && state != DONE) {
  472         if (isdigit((int) *ptr)) {
  473             if (state == FINDMAX) {
  474                 *range_end = atoi(ptr);
  475                 state = DONE;
  476             } else
  477                 *range_start = atoi(ptr);
  478             while (isdigit((int) *ptr))
  479                 ptr++;
  480         } else {
  481             switch (*ptr) {
  482                 case '-':
  483                     state = FINDMAX;
  484                     break;
  485 
  486                 case '.':
  487                     if (state == FINDMAX) {
  488                         *range_end = curr;
  489                         state = DONE;
  490                     } else
  491                         *range_start = curr;
  492                     break;
  493 
  494                 case '$':
  495                     if (state == FINDMAX) {
  496                         *range_end = max;
  497                         state = DONE;
  498                     }
  499                     break;
  500 
  501                 default:
  502                     break;
  503             }
  504             ptr++;
  505         }
  506     }
  507 
  508     if (*range_start >= min && *range_end >= *range_start && *range_end <= max)
  509         ret = TRUE;
  510 
  511     return ret;
  512 }
  513 
  514 
  515 /*
  516  * SELECTED CODE
  517  */
  518 void
  519 do_auto_select_arts(
  520     void)
  521 {
  522     int i;
  523 
  524     for_each_art(i) {
  525         if (arts[i].status == ART_UNREAD && !arts[i].selected) {
  526 #   ifdef DEBUG
  527             if (debug & DEBUG_NEWSRC)
  528                 debug_print_comment("group.c: X command");
  529 #   endif /* DEBUG */
  530             art_mark(curr_group, &arts[i], ART_READ);
  531             arts[i].zombie = TRUE;
  532         }
  533         if (curr_group->attribute->show_only_unread_arts)
  534             arts[i].keep_in_base = FALSE;
  535     }
  536     if (curr_group->attribute->show_only_unread_arts)
  537         find_base(curr_group);
  538 
  539     grpmenu.curr = 0;
  540     show_group_page();
  541 }
  542 
  543 
  544 /* selection already happened in filter_articles() */
  545 void
  546 undo_auto_select_arts(
  547     void)
  548 {
  549     int i;
  550 
  551     for_each_art(i) {
  552         if (arts[i].status == ART_READ && arts[i].zombie) {
  553 #   ifdef DEBUG
  554             if (debug & DEBUG_NEWSRC)
  555                 debug_print_comment("group.c: + command");
  556 #   endif /* DEBUG */
  557             art_mark(curr_group, &arts[i], ART_UNREAD);
  558             arts[i].zombie = FALSE;
  559         }
  560     }
  561     if (curr_group->attribute->show_only_unread_arts)
  562         find_base(curr_group);
  563 
  564     grpmenu.curr = 0;   /* do we want this? */
  565     show_group_page();
  566 }
  567 
  568 
  569 void
  570 undo_selections(
  571     void)
  572 {
  573     int i;
  574 
  575     for_each_art(i) {
  576         arts[i].selected = FALSE;
  577         arts[i].zombie = FALSE;
  578     }
  579 }
  580 
  581 
  582 /*
  583  * Return TRUE if there are any selected arts
  584  */
  585 t_bool
  586 arts_selected(
  587     void)
  588 {
  589     int i;
  590 
  591     for_each_art(i) {
  592         if (arts[i].selected)
  593             return TRUE;
  594     }
  595 
  596     return FALSE;
  597 }