"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 */