"Fossies" - the Fresh Open Source Software Archive

Member "tin-2.4.4/src/tags.c" (20 Nov 2019, 13447 Bytes) of package /linux/misc/tin-2.4.4.tar.xz:


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.3_vs_2.4.4.

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