"Fossies" - the Fresh Open Source Software Archive

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


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

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