"Fossies" - the Fresh Open Source Software Archive

Member "tin-2.6.1/src/feed.c" (22 Dec 2021, 30497 Bytes) of package /linux/misc/tin-2.6.1.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.0_vs_2.6.1.

    1 /*
    2  *  Project   : tin - a Usenet reader
    3  *  Module    : feed.c
    4  *  Author    : I. Lea
    5  *  Created   : 1991-08-31
    6  *  Updated   : 2021-02-25
    7  *  Notes     : provides same interface to mail,pipe,print,save & repost commands
    8  *
    9  * Copyright (c) 1991-2022 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 = { NULL, NULL };
  850 
  851                 if ((feed_type == FEED_PATTERN) && tinrc.wildcard && !(compile_regex(tinrc.default_pattern, &cache, PCRE_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                     FreeIfNeeded(cache.re);
  870                     FreeIfNeeded(cache.extra);
  871                 }
  872             }
  873 
  874             /* I think we nest like this to preserve any 'ordering' of the arts */
  875             for (i = 0; i < grpmenu.max; i++) {
  876                 for_each_art_in_thread(art, i) {
  877                     if (!arts[art].matched)
  878                         continue;
  879                     arts[art].matched = FALSE;
  880 
  881                     /* Keep going - don't abort on errors */
  882                     if (feed_article(art, function, &counter, use_current, outpath, group)) {
  883                         if (feed_type == FEED_HOT)
  884                             arts[art].selected = FALSE;
  885                     } else
  886                         handle_EPIPE();
  887                 }
  888             }
  889             redraw_screen = TRUE;
  890             break;
  891 
  892         default:            /* Should never get here */
  893             break;
  894     } /* switch (feed_type) */
  895 
  896     /*
  897      * Invoke post-processing if needed
  898      * Work out what (if anything) needs to be redrawn
  899      */
  900     if (tinrc.interactive_mailer == INTERACTIVE_NONE)
  901         redraw_screen |= mail_check(mailbox);   /* in case of sending to oneself */
  902 
  903     switch (function) {
  904         case FEED_MARK_READ:
  905         case FEED_MARK_UNREAD:
  906             redraw_screen = FALSE;
  907             if (level == GROUP_LEVEL) {
  908                 no_next_unread = group_mark_postprocess(function, feed_type, respnum);
  909                 break;
  910             }
  911             if (level == THREAD_LEVEL)
  912                 no_next_unread = thread_mark_postprocess(function, feed_type, respnum);
  913             break;
  914 
  915 #ifndef DONT_HAVE_PIPING
  916         case FEED_PIPE:
  917 got_epipe_while_piping:
  918             if (got_epipe)
  919                 perror_message(_(txt_command_failed), tinrc.default_pipe_command);
  920             got_epipe = FALSE;
  921             fflush(pipe_fp);
  922             (void) pclose(pipe_fp);
  923             set_signal_catcher(TRUE);
  924             my_printf(cCRLF);
  925 #   ifdef USE_CURSES
  926             Raw(TRUE);
  927             InitWin();
  928 #   endif /* USE_CURSES */
  929             prompt_continue();
  930 #   ifndef USE_CURSES
  931             Raw(TRUE);
  932             InitWin();
  933 #   endif /* !USE_CURSES */
  934             redraw_screen = TRUE;
  935             break;
  936 #endif /* !DONT_HAVE_PIPING */
  937 
  938         case FEED_SAVE:
  939         case FEED_AUTOSAVE:
  940             if (num_save == 0) {
  941                 wait_message(1, _(txt_saved_nothing));
  942                 break;
  943             }
  944 
  945             if (redraw_screen) {
  946                 currmenu->redraw();
  947                 redraw_screen = FALSE;
  948             }
  949 
  950             print_save_summary(feed_type, counter.total);
  951             if (pproc_func != POSTPROCESS_NO) {
  952                 t_bool delete_post_proc = FALSE;
  953 
  954                 if (curr_group->attribute->delete_tmp_files)
  955                     delete_post_proc = TRUE;
  956                 else {
  957                     if (function != FEED_AUTOSAVE) {
  958                         if (prompt_yn(_(txt_delete_processed_files), TRUE) == 1)
  959                             delete_post_proc = TRUE;
  960                     }
  961                 }
  962                 post_processed_ok = post_process_files(pproc_func, delete_post_proc);
  963             }
  964             free_save_array();      /* NB: This is where num_save etc.. gets purged */
  965 
  966             if (level != PAGE_LEVEL)
  967                 mark_saved = curr_group->attribute->mark_saved_read;
  968             break;
  969 
  970         default:
  971             break;
  972     }
  973 
  974     if (mark_saved || post_processed_ok)
  975         redraw_screen = TRUE;
  976 
  977     if (level == PAGE_LEVEL && !feed_mark_function) {
  978         if (tinrc.force_screen_redraw)
  979             redraw_screen = TRUE;
  980 
  981         /*
  982          * If we were using the paged art return to our former position
  983          */
  984         if (use_current)
  985             curr_line = saved_curr_line;
  986 
  987         if (redraw_screen)
  988             draw_page(group->name, 0);
  989         else {
  990             if (function == FEED_PIPE)
  991                 clear_message();
  992         }
  993     } else {
  994         if (redraw_screen) {
  995             currmenu->redraw();
  996             redraw_screen = FALSE;
  997         }
  998     }
  999 
 1000     /*
 1001      * Finally print a status message
 1002      */
 1003     switch (function) {
 1004         case FEED_MAIL:
 1005             if (tinrc.interactive_mailer != INTERACTIVE_NONE)
 1006                 info_message(_(txt_external_mail_done));
 1007             else
 1008                 info_message(_(txt_articles_mailed), counter.success, PLURAL(counter.success, txt_article));
 1009             break;
 1010 
 1011         case FEED_MARK_READ:
 1012         case FEED_MARK_UNREAD:
 1013             if (no_next_unread)
 1014                 info_message(_(txt_no_next_unread_art));
 1015             else {
 1016                 if (counter.success && level != PAGE_LEVEL) {
 1017                     const char *ptr;
 1018 
 1019                     ptr = function == FEED_MARK_READ ? _(txt_marked_as_read) : _(txt_marked_as_unread);
 1020                     if (feed_type == FEED_THREAD) {
 1021                         info_message(ptr, _(txt_thread_upper));
 1022                     } else if (feed_type == FEED_ARTICLE) {
 1023                         info_message(ptr, _(txt_article_upper));
 1024                     } else {
 1025                         ptr = function == FEED_MARK_READ ? _(txt_marked_arts_as_read) : _(txt_marked_arts_as_unread);
 1026                         info_message(ptr, counter.success, counter.max, PLURAL(counter.max, txt_article));
 1027                     }
 1028                 }
 1029             }
 1030             break;
 1031 
 1032 #ifndef DONT_HAVE_PIPING
 1033         case FEED_PIPE:
 1034             info_message(_(txt_articles_piped), counter.success, PLURAL(counter.success, txt_article), tinrc.default_pipe_command);
 1035             break;
 1036 #endif /* !DONT_HAVE_PIPING */
 1037 
 1038 #ifndef DISABLE_PRINTING
 1039         case FEED_PRINT:
 1040             info_message(_(txt_articles_printed), counter.success, PLURAL(counter.success, txt_article));
 1041             break;
 1042 #endif /* !DISABLE_PRINTING */
 1043 
 1044         case FEED_SAVE:     /* Reporting done earlier */
 1045         case FEED_AUTOSAVE:
 1046         default:
 1047             break;
 1048     }
 1049     return no_next_unread ? 1 : 0;
 1050 }
 1051 
 1052 
 1053 #ifndef DISABLE_PRINTING
 1054 static t_bool
 1055 print_file(
 1056     const char *command,
 1057     int respnum,
 1058     t_openartinfo *artinfo)
 1059 {
 1060     FILE *fp;
 1061     struct t_header *hdr = &artinfo->hdr;
 1062     t_bool ok;
 1063 #   ifdef DONT_HAVE_PIPING
 1064     char cmd[PATH_LEN], file[PATH_LEN];
 1065     int i;
 1066 #   endif /* DONT_HAVE_PIPING */
 1067 
 1068 #   ifdef DONT_HAVE_PIPING
 1069     snprintf(file, sizeof(file), TIN_PRINTFILE, respnum);
 1070     if ((fp = fopen(file, "w")) == NULL) /* TODO: issue a more correct error message here */
 1071 #   else
 1072     if ((fp = popen(command, "w")) == NULL)
 1073 #   endif /* DONT_HAVE_PIPING */
 1074     {
 1075         perror_message(_(txt_command_failed), command);
 1076         return FALSE;
 1077     }
 1078 
 1079     rewind(artinfo->raw);
 1080     if (!curr_group->attribute->print_header && !(fseek(artinfo->raw, hdr->ext->offset, SEEK_SET))) {   /* -> start of body */
 1081         if (hdr->newsgroups)
 1082             fprintf(fp, "Newsgroups: %s\n", hdr->newsgroups);
 1083         if (arts[respnum].from == arts[respnum].name || arts[respnum].name == NULL)
 1084             fprintf(fp, "From: %s\n", arts[respnum].from);
 1085         else
 1086             fprintf(fp, "From: %s <%s>\n", arts[respnum].name, arts[respnum].from);
 1087         if (hdr->subj)
 1088             fprintf(fp, "Subject: %s\n", hdr->subj);
 1089         if (hdr->date)
 1090             fprintf(fp, "Date: %s\n\n", hdr->date);
 1091     }
 1092 
 1093     ok = copy_fp(artinfo->raw, fp);
 1094 
 1095 #   ifdef DONT_HAVE_PIPING
 1096     fclose(fp);
 1097     i = snprintf(cmd, sizeof(cmd), "%s %s", command, file);
 1098     if (i > 0 && i < (int) sizeof(cmd))
 1099         invoke_cmd(cmd);
 1100     else {
 1101         perror_message(_(txt_command_failed), cmd);
 1102         unlink(file);
 1103         return FALSE;
 1104     }
 1105     unlink(file);
 1106 #   else
 1107     fflush(fp);
 1108     pclose(fp);
 1109 #   endif /* DONT_HAVE_PIPING */
 1110 
 1111     return ok;
 1112 }
 1113 #endif /* !DISABLE_PRINTING */