"Fossies" - the Fresh Open Source Software Archive

Member "tin-2.6.2/src/feed.c" (9 Dec 2022, 30480 Bytes) of package /linux/misc/tin-2.6.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.6.1_vs_2.6.2.

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