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