"Fossies" - the Fresh Open Source Software Archive

Member "tin-2.4.2/src/feed.c" (8 Dec 2017, 30716 Bytes) of package /linux/misc/tin-2.4.2.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 "feed.c" see the Fossies "Dox" file reference documentation and the latest Fossies "Diffs" side-by-side code changes report: 2.4.1_vs_2.4.2.

    1 /*
    2  *  Project   : tin - a Usenet reader
    3  *  Module    : feed.c
    4  *  Author    : I. Lea
    5  *  Created   : 1991-08-31
    6  *  Updated   : 2017-03-28
    7  *  Notes     : provides same interface to mail,pipe,print,save & repost commands
    8  *
    9  * Copyright (c) 1991-2018 Iain Lea <iain@bricbrac.de>
   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 
   38 #ifndef TIN_H
   39 #   include "tin.h"
   40 #endif /* !TIN_H */
   41 #ifndef TCURSES_H
   42 #   include "tcurses.h"
   43 #endif /* !TCURSES_H */
   44 
   45 
   46 static t_bool confirm;                  /* only used for FEED_MAIL */
   47 static t_bool is_mailbox = FALSE;
   48 static t_bool redraw_screen = FALSE;
   49 static t_bool supersede = FALSE;        /* for reposting only */
   50 static t_function pproc_func;           /* Post-processing type when saving */
   51 #ifndef DONT_HAVE_PIPING
   52     static FILE *pipe_fp = (FILE *) 0;
   53     static t_bool got_epipe = FALSE;
   54 #endif /* !DONT_HAVE_PIPING */
   55 
   56 
   57 struct t_counters {
   58     int success;        /* # arts fed okay */
   59     int total;          /* # arts fed */
   60     int max;            /* initial guesstimate of total */
   61 };
   62 
   63 /*
   64  * Local prototypes
   65  */
   66 static char *get_save_filename(struct t_group *group, int function, char *filename, int filelen, int respnum);
   67 static t_bool expand_feed_filename(char *outpath, size_t outpath_len, const char *path);
   68 static t_bool feed_article(int art, int function, struct t_counters *counter, t_bool use_current, const char *data, struct t_group *group);
   69 static t_function get_feed_key(int function, int level, struct t_group *group, struct t_art_stat *thread, int respnum);
   70 static t_function get_post_proc_type(void);
   71 static void print_save_summary(t_function type, int fed);
   72 #ifndef DISABLE_PRINTING
   73     static t_bool print_file(const char *command, int respnum, t_openartinfo *artinfo);
   74 #endif /* !DISABLE_PRINTING */
   75 
   76 #ifndef DONT_HAVE_PIPING
   77 #   define handle_EPIPE()   if (got_epipe) goto got_epipe_while_piping
   78 #else
   79 #   define handle_EPIPE() /*nothing*/
   80 #endif /* !DONT_HAVE_PIPING */
   81 
   82 /*
   83  * 'filename' holds 'filelen' amount of storage in which to place the
   84  * filename to save-to. The filename is also returned after basic syntax
   85  * checking. We default to the global save filename or group specific
   86  * filename if it exists
   87  */
   88 static char *
   89 get_save_filename(
   90     struct t_group *group,
   91     int function,
   92     char *filename,
   93     int filelen,
   94     int respnum)
   95 {
   96     char default_savefile[PATH_LEN];
   97 
   98     filename[0] = '\0';
   99 
  100     /*
  101      * Group attribute savefile overrides tinrc default savefile
  102      */
  103     my_strncpy(default_savefile, (group->attribute->savefile ? group->attribute->savefile : tinrc.default_save_file), sizeof(default_savefile) - 1);
  104 
  105     /*
  106      * We don't ask when auto'S'aving or Archive-Name saving with auto_save
  107      */
  108     if (!(function == FEED_AUTOSAVE || (group->attribute->auto_save && arts[respnum].archive))) {
  109         if (!prompt_default_string(_(txt_save_filename), filename, filelen, default_savefile, HIST_SAVE_FILE)) {
  110             clear_message();
  111             return NULL;
  112         }
  113         str_trim(filename);
  114     }
  115 
  116     /*
  117      * Update tinrc.default_save_file if changed
  118      */
  119     if (*filename)
  120         my_strncpy(tinrc.default_save_file, filename, sizeof(tinrc.default_save_file) - 1);
  121     else {
  122         /*
  123          * None chosen (or AUTOSAVING), use tinrc default
  124          */
  125         if (*default_savefile)
  126             my_strncpy(filename, default_savefile, filelen - 1);
  127         else {                                  /* No default either */
  128             info_message(_(txt_no_filename));
  129             return NULL;
  130         }
  131     }
  132 
  133     /*
  134      * Punt invalid expansions
  135      */
  136     if ((filename[0] == '~' || filename[0] == '+') && filename[1] == '\0') {
  137         info_message(_(txt_no_filename));
  138         return NULL;
  139     }
  140     return filename;
  141 }
  142 
  143 
  144 /*
  145  * Generate a path/filename to save to, using 'path' as input.
  146  * The pathname is stored in 'outpath', which should be PATH_LEN in size
  147  * Expand metacharacters and use defaults as needed.
  148  * Return TRUE if the path is a mailbox, or FALSE otherwise.
  149  */
  150 static t_bool
  151 expand_feed_filename(
  152     char *outpath,
  153     size_t outpath_len,
  154     const char *path)
  155 {
  156     int ret = strfpath(path, outpath, PATH_LEN, curr_group, TRUE);
  157 
  158     /*
  159      * If no path exists or the above failed in some way, use sensible defaults
  160      * Put the generic path into 'outpath'
  161      */
  162     if ((ret == 0) || !(strrchr(outpath, DIRSEP))) {
  163         char buf[PATH_LEN];
  164 
  165         if (!strfpath((cmdline.args & CMDLINE_SAVEDIR) ? cmdline.savedir : curr_group->attribute->savedir, buf, sizeof(buf), curr_group, FALSE))
  166             joinpath(buf, sizeof(buf), homedir, DEFAULT_SAVEDIR);
  167         joinpath(outpath, outpath_len, buf, path);
  168         return FALSE;
  169     }
  170     return (ret == 1);
  171 }
  172 
  173 
  174 /*
  175  * Find out what post-processing to perform.
  176  * This is not used when saving to mailboxes (we don't postprocess mailboxes)
  177  * Also not used when using the auto-save feature because a default value is
  178  * taken from the group attributes
  179  * Returns POSTPROCESS_{NO,SHAR,YES} or GLOBAL_ABORT if aborting the save process
  180  */
  181 static t_function
  182 get_post_proc_type(
  183     void)
  184 {
  185     char keyno[MAXKEYLEN], keyyes[MAXKEYLEN], keyquit[MAXKEYLEN];
  186     char keyshar[MAXKEYLEN];
  187     t_function default_func, func;
  188 
  189     switch (curr_group->attribute->post_process_type) {
  190         case POST_PROC_YES:
  191             default_func = POSTPROCESS_YES;
  192             break;
  193 
  194         case POST_PROC_SHAR:
  195             default_func = POSTPROCESS_SHAR;
  196             break;
  197 
  198         case POST_PROC_NO:
  199         default:
  200             default_func = POSTPROCESS_NO;
  201             break;
  202     }
  203 
  204     func = prompt_slk_response(default_func, feed_post_process_keys, _(txt_choose_post_process_type),
  205                 printascii(keyno, func_to_key(POSTPROCESS_NO, feed_post_process_keys)),
  206                 printascii(keyyes, func_to_key(POSTPROCESS_YES, feed_post_process_keys)),
  207                 printascii(keyshar, func_to_key(POSTPROCESS_SHAR, feed_post_process_keys)),
  208                 printascii(keyquit, func_to_key(GLOBAL_QUIT, feed_post_process_keys)));
  209 
  210     if (func == GLOBAL_QUIT || func == GLOBAL_ABORT) {          /* exit */
  211         clear_message();
  212         return GLOBAL_ABORT;
  213     }
  214     return func;
  215 }
  216 
  217 
  218 /*
  219  * Return the key mapping for what we are intending to process or
  220  * GLOBAL_ABORT if save process is being aborted
  221  * Key can be (current) article, (current) thread, tagged articles,
  222  * hot articles, or articles matching a pattern
  223  * This is automatic in the various auto-save cases, in other
  224  * cases this is prompted for based on a chosen default
  225  */
  226 static t_function
  227 get_feed_key(
  228     int function,
  229     int level,
  230     struct t_group *group,
  231     struct t_art_stat *thread,
  232     int respnum)
  233 {
  234     constext *prompt;
  235     t_function default_func, func;
  236 
  237     switch (function) {
  238         case FEED_MAIL:
  239             prompt = txt_mail;
  240             break;
  241 
  242         case FEED_MARK_READ:
  243         case FEED_MARK_UNREAD:
  244             prompt = txt_mark;
  245             break;
  246 
  247 #ifndef DONT_HAVE_PIPING
  248         case FEED_PIPE:
  249             prompt = txt_pipe;
  250             break;
  251 #endif /* !DONT_HAVE_PIPING */
  252 
  253 #ifndef DISABLE_PRINTING
  254         case FEED_PRINT:
  255             prompt = txt_print;
  256             break;
  257 #endif /* !DISABLE_PRINTING */
  258 
  259         /* FEED_AUTOSAVE doesn't prompt */
  260         case FEED_SAVE:
  261             prompt = txt_save;
  262             break;
  263 
  264         case FEED_REPOST:
  265             if (!can_post) {                /* Get this over with before asking any Q's */
  266                 info_message(_(txt_cannot_post));
  267                 return NOT_ASSIGNED;
  268             }
  269             prompt = txt_repost;
  270             break;
  271 
  272         default:
  273             prompt = "";
  274             break;
  275     }
  276 
  277     /*
  278      * Try and work out what default the user wants
  279      * thread->total = # arts in thread
  280      */
  281     default_func = (range_active ? FEED_RANGE :
  282                     num_of_tagged_arts ? FEED_TAGGED :
  283                     (arts_selected() ? FEED_HOT :
  284                     ((level == GROUP_LEVEL && thread->total > 1) ? FEED_THREAD :
  285                     (thread->selected_total ? FEED_HOT :
  286                     FEED_ARTICLE))));
  287 
  288     /*
  289      * Don't bother querying when:
  290      *  auto'S'aving and there are tagged or selected(hot) articles
  291      *  using the auto_save feature on Archive postings
  292      */
  293     if ((function == FEED_AUTOSAVE && (range_active || num_of_tagged_arts || arts_selected()))
  294             || (function == FEED_SAVE && group->attribute->auto_save && arts[respnum].archive))
  295         func = default_func;
  296     else {
  297         char buf[LEN];
  298         char keyart[MAXKEYLEN], keythread[MAXKEYLEN], keyrange[MAXKEYLEN], keyhot[MAXKEYLEN];
  299         char keypat[MAXKEYLEN], keytag[MAXKEYLEN], keyquit[MAXKEYLEN];
  300 
  301         snprintf(buf, sizeof(buf), _(txt_art_thread_regex_tag),
  302             printascii(keyart, func_to_key(FEED_ARTICLE, feed_type_keys)),
  303             printascii(keythread, func_to_key(FEED_THREAD, feed_type_keys)),
  304             printascii(keyrange, func_to_key(FEED_RANGE, feed_type_keys)),
  305             printascii(keyhot, func_to_key(FEED_HOT, feed_type_keys)),
  306             printascii(keypat, func_to_key(FEED_PATTERN, feed_type_keys)),
  307             printascii(keytag, func_to_key(FEED_TAGGED, feed_type_keys)),
  308             printascii(keyquit, func_to_key(GLOBAL_QUIT, feed_type_keys)));
  309 
  310         func = prompt_slk_response(default_func, feed_type_keys, "%s %s", _(prompt), buf);
  311     }
  312 
  313     switch (func) {
  314         case FEED_PATTERN:
  315             {
  316                 char *tmp = fmt_string(_(txt_feed_pattern), tinrc.default_pattern);
  317 
  318                 if (!(prompt_string_default(tmp, tinrc.default_pattern, _(txt_no_match), HIST_REGEX_PATTERN))) {
  319                     free(tmp);
  320                     return GLOBAL_ABORT;
  321                 }
  322                 free(tmp);
  323             }
  324             break;
  325 
  326         case FEED_RANGE:
  327             if (!range_active) {
  328                 if (set_range(level, 1, currmenu->max, currmenu->curr + 1))
  329                     range_active = TRUE;
  330                 else
  331                     return GLOBAL_ABORT;
  332             }
  333             break;
  334 
  335         case GLOBAL_QUIT:
  336         case GLOBAL_ABORT:
  337             clear_message();
  338             return GLOBAL_ABORT;
  339             /* NOTREACHED */
  340             break;
  341 
  342         default:
  343             break;
  344     }
  345 
  346     return func;
  347 }
  348 
  349 
  350 /*
  351  * Print a message like:
  352  * -- [Article|Thread|Tagged Articles] saved to [mailbox] [filenames] --
  353  * 'fed' is the number of articles we tried to save
  354  */
  355 static void
  356 print_save_summary(
  357     t_function type,
  358     int fed)
  359 {
  360     const char *first, *last;
  361     char buf[LEN];
  362     char what[LEN];
  363 
  364     if (fed != num_save)
  365         wait_message(2, _(txt_warn_not_all_arts_saved), fed, num_save);
  366 
  367     switch (type) {
  368         case FEED_HOT:
  369             snprintf(what, sizeof(what), _(txt_prefix_hot), PLURAL(fed, txt_article));
  370             break;
  371 
  372         case FEED_TAGGED:
  373             snprintf(what, sizeof(what), _(txt_prefix_tagged), PLURAL(fed, txt_article));
  374             break;
  375 
  376         case FEED_THREAD:
  377             STRCPY(what, _(txt_thread_upper));
  378             break;
  379 
  380         case FEED_ARTICLE:
  381         case FEED_PATTERN:
  382         default:
  383             snprintf(what, sizeof(what), "%s", PLURAL(fed, txt_article));
  384             break;
  385     }
  386 
  387     first = (save[0].mailbox) ? save[0].path : save[0].file;
  388     last = (save[num_save - 1].mailbox) ? save[num_save - 1].path : save[num_save - 1].file;
  389 
  390     /*
  391      * We report the range of saved-to files for regular saves of > 1 articles
  392      */
  393     if (num_save == 1 || save[0].mailbox)
  394         snprintf(buf, sizeof(buf), _(txt_saved_to),
  395             what, (save[0].mailbox ? _(txt_mailbox) : ""), first);
  396     else
  397         snprintf(buf, sizeof(buf), _(txt_saved_to_range),
  398             what, first, last);
  399 
  400     wait_message((tinrc.beginner_level) ? 4 : 2, buf);
  401 }
  402 
  403 
  404 /*
  405  * This is the handler that processes a single article for all the various
  406  * FEED_ functions.
  407  * Assumes no article is open when we enter - opens and closes the art being
  408  * processed. As a performance hack this is not done if 'use_current' is set.
  409  * Returns TRUE or FALSE
  410  * TODO: option to mail/pipe/print/repost raw vs. cooked?
  411  *       (all currently raw only) or should we feed according to what
  412  *       is currently on screen?
  413  */
  414 static t_bool
  415 feed_article(
  416     int art,                /* index in arts[] */
  417     int function,
  418     struct t_counters *counter, /* Accounting */
  419     t_bool use_current,     /* Use already open pager article */
  420     const char *data,       /* Extra data if needed, print command or save filename */
  421     struct t_group *group)
  422 {
  423     char *progress_mesg = NULL;
  424     t_bool ok = TRUE;       /* Assume success */
  425     t_openartinfo openart;
  426     t_openartinfo *openartptr = &openart;
  427 
  428     counter->total++;
  429 
  430     /*
  431      * Update the on-screen progress before art_open(), which is the bottleneck
  432      * timewise
  433      */
  434     switch (function) {
  435 #ifndef DONT_HAVE_PIPING
  436         case FEED_PIPE:
  437             progress_mesg = fmt_string("%s (%d/%d)", _(txt_piping), counter->total, counter->max);
  438             break;
  439 #endif /* !DONT_HAVE_PIPING */
  440 
  441 #ifndef DISABLE_PRINTING
  442         case FEED_PRINT:
  443             progress_mesg = fmt_string("%s (%d/%d)", _(txt_printing), counter->total, counter->max);
  444             break;
  445 #endif /* !DISABLE_PRINTING */
  446 
  447         case FEED_SAVE:
  448         case FEED_AUTOSAVE:
  449             progress_mesg = fmt_string("%s (%d/%d)", _(txt_saving), counter->total, counter->max);
  450             break;
  451     }
  452 
  453     if (progress_mesg != NULL) {
  454         if (!use_current)
  455             show_progress(progress_mesg, counter->total, counter->max);
  456         FreeAndNull(progress_mesg);
  457     }
  458 
  459     if (use_current)
  460         openartptr = &pgart;            /* Use art already open in pager */
  461     else {
  462         if (art_open(FALSE, &arts[art], group, openartptr, FALSE, NULL) < 0)
  463             /* User abort or an error */
  464             return FALSE;
  465     }
  466 
  467     switch (function) {
  468         case FEED_MAIL:
  469             switch (mail_to_someone(tinrc.default_mail_address, confirm, openartptr, group)) {
  470                 case POSTED_REDRAW:
  471                     redraw_screen = TRUE;
  472                     /* FALLTHROUGH */
  473                 case POSTED_NONE:
  474                     ok = FALSE;
  475                     break;
  476 
  477                 case POSTED_OK:
  478                     break;
  479             }
  480             confirm = bool_not(ok);     /* Only confirm the next one after a failure */
  481             break;
  482 
  483         case FEED_MARK_READ:
  484             if (arts[art].status == ART_UNREAD || arts[art].status == ART_WILL_RETURN)
  485                 art_mark(curr_group, &arts[art], ART_READ);
  486             else
  487                 ok = FALSE;
  488             break;
  489 
  490         case FEED_MARK_UNREAD:
  491             if (arts[art].status == ART_READ)
  492                 art_mark(curr_group, &arts[art], ART_WILL_RETURN);
  493             else
  494                 ok = FALSE;
  495             break;
  496 
  497 #ifndef DONT_HAVE_PIPING
  498         case FEED_PIPE:
  499             rewind(openartptr->raw);
  500             ok = copy_fp(openartptr->raw, pipe_fp);
  501             if (errno == EPIPE) /* broken pipe in copy_fp() */
  502                 got_epipe = TRUE;
  503             break;
  504 #endif /* !DONT_HAVE_PIPING */
  505 
  506 #ifndef DISABLE_PRINTING
  507         case FEED_PRINT:
  508             ok = print_file(data /*print_command*/, art, openartptr);
  509             break;
  510 #endif /* !DISABLE_PRINTING */
  511 
  512         case FEED_SAVE:
  513         case FEED_AUTOSAVE:
  514             ok = save_and_process_art(openartptr, &arts[art], is_mailbox, data /*filename*/, counter->max, (pproc_func != POSTPROCESS_NO));
  515             if (ok && curr_group->attribute->mark_saved_read)
  516                 art_mark(curr_group, &arts[art], ART_READ);
  517             break;
  518 
  519         case FEED_REPOST:
  520             if (repost_article(tinrc.default_repost_group, art, supersede, openartptr) == POSTED_NONE)
  521                 ok = FALSE;
  522             else            /* POSTED_REDRAW, POSTED_OK */
  523                 redraw_screen = TRUE;
  524             break;
  525 
  526         default:
  527             break;
  528     }
  529     if (ok)
  530         counter->success++;
  531 
  532     if (!use_current)
  533         art_close(openartptr);
  534     return ok;
  535 }
  536 
  537 
  538 /*
  539  * Single entry point for 'feed'ing article(s) to a backend
  540  * Function:
  541  *  FEED_PIPE, FEED_MAIL, FEED_PRINT, FEED_REPOST
  542  *  FEED_SAVE, FEED_AUTOSAVE, FEED_MARK_READ, FEED_MARK_UNREAD
  543  * Level:
  544  *  GROUP_LEVEL, THREAD_LEVEL, PAGE_LEVEL
  545  * Type:
  546  *  default feed_type; if NOT_ASSIGNED, query what to do
  547  * Respnum:
  548  *  Index in arts[] of starting article
  549  *
  550  * The following 'groups' of article can be processed:
  551  *  Single (current) article
  552  *  Current thread
  553  *  Range of articles
  554  *  Tagged articles
  555  *  Hot articles
  556  *  Articles matching a pattern
  557  *
  558  * The selection of Function depends on the key used to get here.
  559  * The selection of which article 'group' to process is managed
  560  * inside here, or by defaults.
  561  *
  562  * Returns:
  563  *   1  if there are no more unread arts in this group (FEED_MARK_READ)
  564  *   0  on success
  565  *  -1  on failure/abort
  566  */
  567 int
  568 feed_articles(
  569     int function,
  570     int level,
  571     t_function type,
  572     struct t_group *group,
  573     int respnum)
  574 {
  575     char *prompt;
  576     char outpath[PATH_LEN];
  577     int art;
  578     int i;
  579     int saved_curr_line = -1;
  580     int thread_base;
  581     struct t_art_stat sbuf;
  582     struct t_counters counter = { 0, 0, 0 };
  583     t_bool feed_mark_function = function == FEED_MARK_READ || function == FEED_MARK_UNREAD;
  584     t_bool mark_saved = FALSE;
  585     t_bool no_next_unread = FALSE;
  586     t_bool post_processed_ok = FALSE;
  587     t_bool use_current = FALSE;
  588     t_function feed_type;
  589 
  590 #ifdef DONT_HAVE_PIPING
  591     if (function == FEED_PIPE) {
  592         error_message(2, _(txt_piping_not_enabled));
  593         clear_message();
  594         return -1;
  595     }
  596 #endif /* DONT_HAVE_PIPING */
  597 
  598     if (function == FEED_AUTOSAVE) {
  599         if (!range_active && num_of_tagged_arts == 0 && !arts_selected()) {
  600             info_message(_(txt_no_marked_arts));
  601             return -1;
  602         }
  603     }
  604 
  605     set_xclick_off();       /* TODO: there is no corresponding set_xclick_on()? */
  606     if ((thread_base = which_thread(respnum)) >= 0)
  607         stat_thread(thread_base, &sbuf);
  608     else /* TODO: error message? */
  609         return -1;
  610 
  611     switch (type) {
  612         case FEED_ARTICLE:
  613         case FEED_THREAD:
  614         case FEED_RANGE:
  615             feed_type = type;
  616             break;
  617 
  618         default:
  619             if ((feed_type = get_feed_key(function, level, group, &sbuf, respnum)) == GLOBAL_ABORT)
  620                 return -1;
  621             break;
  622     }
  623 
  624     /*
  625      * Get whatever information is needed to proceed
  626      */
  627     switch (function) {
  628         /* Setup mail - get address to mail to */
  629         case FEED_MAIL:
  630             prompt = fmt_string(_(txt_mail_art_to), cCOLS - (strlen(_(txt_mail_art_to)) + 30), tinrc.default_mail_address);
  631             if (!(prompt_string_default(prompt, tinrc.default_mail_address, _(txt_no_mail_address), HIST_MAIL_ADDRESS))) {
  632                 free(prompt);
  633                 return -1;
  634             }
  635             free(prompt);
  636             break;
  637 
  638 #ifndef DONT_HAVE_PIPING
  639         /* Setup pipe - get pipe-to command and open the pipe */
  640         case FEED_PIPE:
  641             prompt = fmt_string(_(txt_pipe_to_command), cCOLS - (strlen(_(txt_pipe_to_command)) + 30), tinrc.default_pipe_command);
  642             if (!(prompt_string_default(prompt, tinrc.default_pipe_command, _(txt_no_command), HIST_PIPE_COMMAND))) {
  643                 free(prompt);
  644                 return -1;
  645             }
  646             free(prompt);
  647 
  648             got_epipe = FALSE;
  649             EndWin(); /* Turn off curses/windowing */
  650             Raw(FALSE);
  651             fflush(stdout);
  652             set_signal_catcher(FALSE);
  653             if ((pipe_fp = popen(tinrc.default_pipe_command, "w")) == NULL) {
  654                 perror_message(_(txt_command_failed), tinrc.default_pipe_command);
  655                 set_signal_catcher(TRUE);
  656                 Raw(TRUE);
  657                 InitWin();
  658                 return -1;
  659             }
  660             break;
  661 #endif /* !DONT_HAVE_PIPING */
  662 
  663 #ifndef DISABLE_PRINTING
  664         /* Setup printing - get print command line */
  665         case FEED_PRINT:
  666             snprintf(outpath, sizeof(outpath), "%s %s", tinrc.printer, REDIRECT_OUTPUT);
  667             break;
  668 #endif /* !DISABLE_PRINTING */
  669 
  670         /*
  671          * Setup saving, some of these are generated automatically
  672          *  Determine path/file to save to
  673          *  Determine post-processing type
  674          *  Determine if post processed file deletion required
  675          */
  676         case FEED_SAVE:
  677         case FEED_AUTOSAVE:
  678             {
  679                 char savefile[PATH_LEN];
  680 
  681                 /* This will force automatic selection unless changed by user */
  682                 savefile[0] = '\0';
  683 
  684                 if (get_save_filename(group, function, savefile, sizeof(savefile), respnum) == NULL)
  685                     return -1;
  686 
  687                 switch (curr_group->attribute->post_process_type) {
  688                     case POST_PROC_YES:
  689                         pproc_func = POSTPROCESS_YES;
  690                         break;
  691 
  692                     case POST_PROC_SHAR:
  693                         pproc_func = POSTPROCESS_SHAR;
  694                         break;
  695 
  696                     case POST_PROC_NO:
  697                     default:
  698                         pproc_func = POSTPROCESS_NO;
  699                         break;
  700                 }
  701 
  702                 /* We don't postprocess mailboxen */
  703                 if ((is_mailbox = expand_feed_filename(outpath, sizeof(outpath), savefile)) == TRUE)
  704                     pproc_func = POSTPROCESS_NO;
  705                 else {
  706                     if (function != FEED_AUTOSAVE && (pproc_func = get_post_proc_type()) == GLOBAL_ABORT)
  707                         return -1;
  708                 }
  709                 if (!create_path(outpath))
  710                     return -1;
  711             }
  712             break;
  713 
  714         /* repost (or supersede) article */
  715         case FEED_REPOST:
  716             {
  717                 char *tmp;
  718 #ifndef FORGERY
  719                 char from_name[PATH_LEN];
  720 
  721                 get_from_name(from_name, (struct t_group *) 0);
  722 
  723                 if (strstr(from_name, arts[respnum].from)) {
  724 #endif /* !FORGERY */
  725                     char *smsg;
  726                     char buf[LEN];
  727                     char keyrepost[MAXKEYLEN], keysupersede[MAXKEYLEN];
  728                     char keyquit[MAXKEYLEN];
  729                     t_function func;
  730 
  731                     /* repost or supersede? */
  732                     snprintf(buf, sizeof(buf), _(txt_supersede_article),
  733                             printascii(keyrepost, func_to_key(FEED_KEY_REPOST, feed_supersede_article_keys)),
  734                             printascii(keysupersede, func_to_key(FEED_SUPERSEDE, feed_supersede_article_keys)),
  735                             printascii(keyquit, func_to_key(GLOBAL_QUIT, feed_supersede_article_keys)));
  736                     func = prompt_slk_response(FEED_SUPERSEDE,
  737                                 feed_supersede_article_keys, "%s",
  738                                 sized_message(&smsg, buf, arts[respnum].subject));
  739                     free(smsg);
  740 
  741                     switch (func) {
  742                         case FEED_SUPERSEDE:
  743                             tmp = fmt_string(_(txt_supersede_group), tinrc.default_repost_group);
  744                             supersede = TRUE;
  745                             break;
  746 
  747                         case FEED_KEY_REPOST:
  748                             tmp = fmt_string(_(txt_repost_group), tinrc.default_repost_group);
  749                             supersede = FALSE;
  750                             break;
  751 
  752                         default:
  753                             clear_message();
  754                             return -1;
  755                     }
  756 #ifndef FORGERY
  757                 } else {
  758                     tmp = fmt_string(_(txt_repost_group), tinrc.default_repost_group);
  759                     supersede = FALSE;
  760                 }
  761 #endif /* !FORGERY */
  762                 if (!(prompt_string_default(tmp, tinrc.default_repost_group, _(txt_no_group), HIST_REPOST_GROUP))) {
  763                     free(tmp);
  764                     return -1;
  765                 }
  766                 free(tmp);
  767             }
  768             break;
  769 
  770         default:
  771             break;
  772     } /* switch (function) */
  773 
  774     confirm = TRUE;             /* Always confirm the first time */
  775     clear_message();
  776 
  777     /*
  778      * Performance hack - If we feed a single art from the pager then we can
  779      * re-use the currently open article
  780      * Also no need to fetch articles just to mark them (un)read
  781      */
  782     if (feed_mark_function || (level == PAGE_LEVEL && (feed_type == FEED_ARTICLE || feed_type == FEED_THREAD))) {
  783         saved_curr_line = curr_line;        /* Save where we were in pager */
  784         use_current = TRUE;
  785     }
  786 
  787     /*
  788      * This is the main loop
  789      * The general idea is to feed_article() for every article to be processed
  790      */
  791     switch (feed_type) {
  792         case FEED_ARTICLE:      /* article */
  793             counter.max = 1;
  794             if (!feed_article(respnum, function, &counter, use_current, outpath, group))
  795                 handle_EPIPE();
  796             break;
  797 
  798         case FEED_THREAD:       /* thread */
  799             /* Get accurate count first */
  800             for_each_art_in_thread(art, which_thread(respnum)) {
  801                 if (feed_mark_function || !(curr_group->attribute->process_only_unread && arts[art].status == ART_READ))
  802                     counter.max++;
  803             }
  804 
  805             for_each_art_in_thread(art, which_thread(respnum)) {
  806                 if (feed_mark_function || !(curr_group->attribute->process_only_unread && arts[art].status == ART_READ)) {
  807                     /* Keep going - don't abort on errors */
  808                     if (!feed_article(art, function, &counter, use_current, outpath, group))
  809                         handle_EPIPE();
  810                 }
  811             }
  812             break;
  813 
  814         case FEED_RANGE:
  815             /* Get accurate count first */
  816             for_each_art(art) {
  817                 if (arts[art].inrange)
  818                     counter.max++;
  819             }
  820 
  821             for_each_art(art) {
  822                 if (arts[art].inrange) {
  823                     arts[art].inrange = FALSE;
  824                     if (!feed_article(art, function, &counter, use_current, outpath, group))
  825                         handle_EPIPE();
  826                 }
  827             }
  828             range_active = FALSE;
  829             redraw_screen = TRUE;
  830             break;
  831 
  832         case FEED_TAGGED:       /* tagged articles */
  833             counter.max = num_of_tagged_arts;
  834             for (i = 1; i <= num_of_tagged_arts; i++) {
  835                 for_each_art(art) {
  836                     /* process_only_unread does NOT apply on tagged arts */
  837                     if (arts[art].tagged == i) {
  838                         /* Keep going - don't abort on errors */
  839                         if (!feed_article(art, function, &counter, use_current, outpath, group))
  840                             handle_EPIPE();
  841                     }
  842                 }
  843             }
  844             untag_all_articles();   /* TODO: this will untag even on partial failure */
  845             redraw_screen = TRUE;
  846             break;
  847 
  848         case FEED_HOT:      /* hot (auto-selected) articles */
  849         case FEED_PATTERN:  /* pattern matched articles */
  850             {
  851                 struct regex_cache cache = { NULL, NULL };
  852 
  853                 if ((feed_type == FEED_PATTERN) && tinrc.wildcard && !(compile_regex(tinrc.default_pattern, &cache, PCRE_CASELESS)))
  854                     break;
  855 
  856                 for_each_art(art) {
  857                     if (feed_type == FEED_PATTERN) {
  858                         if (!match_regex(arts[art].subject, tinrc.default_pattern, &cache, TRUE))
  859                             continue;
  860                     } else if (!arts[art].selected)
  861                         continue;
  862 
  863                     if (!feed_mark_function && (curr_group->attribute->process_only_unread && arts[art].status == ART_READ))
  864                         continue;
  865 
  866                     arts[art].matched = TRUE;
  867                     counter.max++;
  868                 }
  869 
  870                 if (tinrc.wildcard) {
  871                     FreeIfNeeded(cache.re);
  872                     FreeIfNeeded(cache.extra);
  873                 }
  874             }
  875 
  876             /* I think we nest like this to preserve any 'ordering' of the arts */
  877             for (i = 0; i < grpmenu.max; i++) {
  878                 for_each_art_in_thread(art, i) {
  879                     if (!arts[art].matched)
  880                         continue;
  881                     arts[art].matched = FALSE;
  882 
  883                     /* Keep going - don't abort on errors */
  884                     if (feed_article(art, function, &counter, use_current, outpath, group)) {
  885                         if (feed_type == FEED_HOT)
  886                             arts[art].selected = FALSE;
  887                     } else
  888                         handle_EPIPE();
  889                 }
  890             }
  891             redraw_screen = TRUE;
  892             break;
  893 
  894         default:            /* Should never get here */
  895             break;
  896     } /* switch (feed_type) */
  897 
  898     /*
  899      * Invoke post-processing if needed
  900      * Work out what (if anything) needs to be redrawn
  901      */
  902     if (INTERACTIVE_NONE == tinrc.interactive_mailer)
  903         redraw_screen |= mail_check();  /* in case of sending to oneself */
  904 
  905     switch (function) {
  906         case FEED_MARK_READ:
  907         case FEED_MARK_UNREAD:
  908             redraw_screen = FALSE;
  909             if (level == GROUP_LEVEL) {
  910                 no_next_unread = group_mark_postprocess(function, feed_type, respnum);
  911                 break;
  912             }
  913             if (level == THREAD_LEVEL)
  914                 no_next_unread = thread_mark_postprocess(function, feed_type, respnum);
  915             break;
  916 
  917 #ifndef DONT_HAVE_PIPING
  918         case FEED_PIPE:
  919 got_epipe_while_piping:
  920             if (got_epipe)
  921                 perror_message(_(txt_command_failed), tinrc.default_pipe_command);
  922             got_epipe = FALSE;
  923             fflush(pipe_fp);
  924             (void) pclose(pipe_fp);
  925             set_signal_catcher(TRUE);
  926             my_printf(cCRLF);
  927 #   ifdef USE_CURSES
  928             Raw(TRUE);
  929             InitWin();
  930 #   endif /* USE_CURSES */
  931             prompt_continue();
  932 #   ifndef USE_CURSES
  933             Raw(TRUE);
  934             InitWin();
  935 #   endif /* !USE_CURSES */
  936             redraw_screen = TRUE;
  937             break;
  938 #endif /* !DONT_HAVE_PIPING */
  939 
  940         case FEED_SAVE:
  941         case FEED_AUTOSAVE:
  942             if (num_save == 0) {
  943                 wait_message(1, _(txt_saved_nothing));
  944                 break;
  945             }
  946 
  947             if (redraw_screen) {
  948                 currmenu->redraw();
  949                 redraw_screen = FALSE;
  950             }
  951 
  952             print_save_summary(feed_type, counter.total);
  953             if (pproc_func != POSTPROCESS_NO) {
  954                 t_bool delete_post_proc = FALSE;
  955 
  956                 if (curr_group->attribute->delete_tmp_files)
  957                     delete_post_proc = TRUE;
  958                 else {
  959                     if (function != FEED_AUTOSAVE) {
  960                         if (prompt_yn(_(txt_delete_processed_files), TRUE) == 1)
  961                             delete_post_proc = TRUE;
  962                     }
  963                 }
  964                 post_processed_ok = post_process_files(pproc_func, delete_post_proc);
  965             }
  966             free_save_array();      /* NB: This is where num_save etc.. gets purged */
  967 
  968             if (level != PAGE_LEVEL)
  969                 mark_saved = curr_group->attribute->mark_saved_read;
  970             break;
  971 
  972         default:
  973             break;
  974     }
  975 
  976     if (mark_saved || post_processed_ok)
  977         redraw_screen = TRUE;
  978 
  979     if (level == PAGE_LEVEL && !feed_mark_function) {
  980         if (tinrc.force_screen_redraw)
  981             redraw_screen = TRUE;
  982 
  983         /*
  984          * If we were using the paged art return to our former position
  985          */
  986         if (use_current)
  987             curr_line = saved_curr_line;
  988 
  989         if (redraw_screen)
  990             draw_page(group->name, 0);
  991         else {
  992             if (function == FEED_PIPE)
  993                 clear_message();
  994         }
  995     } else {
  996         if (redraw_screen) {
  997             currmenu->redraw();
  998             redraw_screen = FALSE;
  999         }
 1000     }
 1001 
 1002     /*
 1003      * Finally print a status message
 1004      */
 1005     switch (function) {
 1006         case FEED_MAIL:
 1007             if (INTERACTIVE_NONE != tinrc.interactive_mailer)
 1008                 info_message(_(txt_external_mail_done));
 1009             else
 1010                 info_message(_(txt_articles_mailed), counter.success, PLURAL(counter.success, txt_article));
 1011             break;
 1012 
 1013         case FEED_MARK_READ:
 1014         case FEED_MARK_UNREAD:
 1015             if (no_next_unread)
 1016                 info_message(_(txt_no_next_unread_art));
 1017             else {
 1018                 if (counter.success && level != PAGE_LEVEL) {
 1019                     const char *ptr;
 1020 
 1021                     ptr = function == FEED_MARK_READ ? _(txt_marked_as_read) : _(txt_marked_as_unread);
 1022                     if (feed_type == FEED_THREAD) {
 1023                         info_message(ptr, _(txt_thread_upper));
 1024                     } else if (feed_type == FEED_ARTICLE) {
 1025                         info_message(ptr, _(txt_article_upper));
 1026                     } else {
 1027                         ptr = function == FEED_MARK_READ ? _(txt_marked_arts_as_read) : _(txt_marked_arts_as_unread);
 1028                         info_message(ptr, counter.success, counter.max, PLURAL(counter.max, txt_article));
 1029                     }
 1030                 }
 1031             }
 1032             break;
 1033 
 1034 #ifndef DONT_HAVE_PIPING
 1035         case FEED_PIPE:
 1036             info_message(_(txt_articles_piped), counter.success, PLURAL(counter.success, txt_article), tinrc.default_pipe_command);
 1037             break;
 1038 #endif /* !DONT_HAVE_PIPING */
 1039 
 1040 #ifndef DISABLE_PRINTING
 1041         case FEED_PRINT:
 1042             info_message(_(txt_articles_printed), counter.success, PLURAL(counter.success, txt_article));
 1043             break;
 1044 #endif /* !DISABLE_PRINTING */
 1045 
 1046         case FEED_SAVE:     /* Reporting done earlier */
 1047         case FEED_AUTOSAVE:
 1048         default:
 1049             break;
 1050     }
 1051     return no_next_unread ? 1 : 0;
 1052 }
 1053 
 1054 
 1055 #ifndef DISABLE_PRINTING
 1056 static t_bool
 1057 print_file(
 1058     const char *command,
 1059     int respnum,
 1060     t_openartinfo *artinfo)
 1061 {
 1062     FILE *fp;
 1063     struct t_header *hdr = &artinfo->hdr;
 1064     t_bool ok;
 1065 #   ifdef DONT_HAVE_PIPING
 1066     char cmd[PATH_LEN], file[PATH_LEN];
 1067 #   endif /* DONT_HAVE_PIPING */
 1068 
 1069 #   ifdef DONT_HAVE_PIPING
 1070     snprintf(file, sizeof(file), TIN_PRINTFILE, respnum);
 1071     if ((fp = fopen(file, "w")) == NULL) /* TODO: issue a more correct error message here */
 1072 #   else
 1073     if ((fp = popen(command, "w")) == NULL)
 1074 #   endif /* DONT_HAVE_PIPING */
 1075     {
 1076         perror_message(_(txt_command_failed), command);
 1077         return FALSE;
 1078     }
 1079 
 1080     rewind(artinfo->raw);
 1081     if (!curr_group->attribute->print_header && !(fseek(artinfo->raw, hdr->ext->offset, SEEK_SET))) {   /* -> start of body */
 1082         if (hdr->newsgroups)
 1083             fprintf(fp, "Newsgroups: %s\n", hdr->newsgroups);
 1084         if (arts[respnum].from == arts[respnum].name || arts[respnum].name == NULL)
 1085             fprintf(fp, "From: %s\n", arts[respnum].from);
 1086         else
 1087             fprintf(fp, "From: %s <%s>\n", arts[respnum].name, arts[respnum].from);
 1088         if (hdr->subj)
 1089             fprintf(fp, "Subject: %s\n", hdr->subj);
 1090         if (hdr->date)
 1091             fprintf(fp, "Date: %s\n\n", hdr->date);
 1092     }
 1093 
 1094     ok = copy_fp(artinfo->raw, fp);
 1095 
 1096 #   ifdef DONT_HAVE_PIPING
 1097     fclose(fp);
 1098     strncpy(cmd, command, sizeof(cmd) - 2);
 1099     strcat(cmd, " ");
 1100     strncat(cmd, file, sizeof(cmd) - strlen(cmd) - 1);
 1101     cmd[sizeof(cmd) - 1] = '\0';
 1102     invoke_cmd(cmd);
 1103     unlink(file);
 1104 #   else
 1105     fflush(fp);
 1106     pclose(fp);
 1107 #   endif /* DONT_HAVE_PIPING */
 1108 
 1109     return ok;
 1110 }
 1111 #endif /* !DISABLE_PRINTING */