"Fossies" - the Fresh Open Source Software Archive 
Member "tin-2.6.2/src/select.c" (9 Dec 2022, 44140 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 "select.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 : select.c
4 * Author : I. Lea & R. Skrenta
5 * Created : 1991-04-01
6 * Updated : 2022-10-27
7 * Notes :
8 *
9 * Copyright (c) 1991-2023 Iain Lea <iain@bricbrac.de>, Rich Skrenta <skrenta@pbm.com>
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 struct t_fmt sel_fmt;
50
51 /*
52 * Local prototypes
53 */
54 static t_function select_left(void);
55 static t_function select_right(void);
56 static int active_comp(t_comptype p1, t_comptype p2);
57 static int reposition_group(struct t_group *group, int default_num);
58 static int save_restore_curr_group(t_bool saving);
59 static t_bool pos_next_unread_group(t_bool redraw);
60 static t_bool yanked_out = TRUE;
61 static void build_gline(int i);
62 static void catchup_group(struct t_group *group, t_bool goto_next_unread_group);
63 static void draw_group_arrow(void);
64 static void read_groups(void);
65 static void select_done(void);
66 _Noreturn static void select_quit(void);
67 static void select_read_group(void);
68 static void sort_active_file(void);
69 static void subscribe_pattern(const char *prompt, const char *message, const char *result, t_bool state);
70 static void sync_active_file(void);
71 static void yank_active_file(void);
72 #ifdef NNTP_ABLE
73 static char *lookup_msgid(char *msgid);
74 static struct t_group *get_group_from_list(char *newsgroups);
75 #endif /* NNTP_ABLE */
76
77
78 /*
79 * selmenu.curr = index (start at 0) of cursor position on menu,
80 * or -1 when no groups visible on screen
81 * selmenu.max = Total # of groups in my_group[]
82 * selmenu.first is static here
83 */
84 t_menu selmenu = { 1, 0, 0, show_selection_page, draw_group_arrow, build_gline };
85
86 static int groupname_len; /* max. group name length */
87 static int flags_offset;
88 static int ucnt_offset;
89
90
91 static t_function
92 select_left(
93 void)
94 {
95 return GLOBAL_QUIT;
96 }
97
98
99 static t_function
100 select_right(
101 void)
102 {
103 return SELECT_ENTER_GROUP;
104 }
105
106
107 void
108 selection_page(
109 int start_groupnum,
110 int num_cmd_line_groups)
111 {
112 char buf[LEN];
113 char key[MAXKEYLEN];
114 int i, n;
115 t_function func;
116 #if defined(MULTIBYTE_ABLE) && !defined(NO_LOCALE)
117 wchar_t *wtmp;
118 #endif /* MULTIBYTE_ABLE && !NO_LOCALE */
119
120 selmenu.curr = start_groupnum;
121
122 #ifdef READ_CHAR_HACK
123 setbuf(stdin, 0);
124 #endif /* READ_CHAR_HACK */
125
126 Raw(TRUE);
127 ClearScreen();
128
129 /*
130 * If user specified only 1 cmd line groupname (eg. tin -r alt.sources)
131 * then go there immediately.
132 */
133 if (num_cmd_line_groups == 1)
134 select_read_group();
135
136 cursoroff();
137 show_selection_page(); /* display group selection page */
138
139 forever {
140 if (!resync_active_file()) {
141 if (reread_active_after_posting()) /* reread active file if necessary */
142 show_selection_page();
143 } else {
144 if (!yanked_out)
145 yanked_out = TRUE; /* yank out if yanked in */
146 }
147
148 set_xclick_on();
149
150 switch ((func = handle_keypad(select_left, select_right, global_mouse_action, select_keys))) {
151 case GLOBAL_ABORT: /* Abort */
152 break;
153
154 case DIGIT_1:
155 case DIGIT_2:
156 case DIGIT_3:
157 case DIGIT_4:
158 case DIGIT_5:
159 case DIGIT_6:
160 case DIGIT_7:
161 case DIGIT_8:
162 case DIGIT_9:
163 if (selmenu.max)
164 prompt_item_num(func_to_key(func, select_keys), _(txt_select_group));
165 else
166 info_message(_(txt_no_groups));
167 break;
168
169 #ifndef NO_SHELL_ESCAPE
170 case GLOBAL_SHELL_ESCAPE:
171 do_shell_escape();
172 break;
173 #endif /* !NO_SHELL_ESCAPE */
174
175 case GLOBAL_FIRST_PAGE: /* show first page of groups */
176 top_of_list();
177 break;
178
179 case GLOBAL_LAST_PAGE: /* show last page of groups */
180 end_of_list();
181 break;
182
183 case GLOBAL_PAGE_UP:
184 page_up();
185 break;
186
187 case GLOBAL_PAGE_DOWN:
188 page_down();
189 break;
190
191 case GLOBAL_LINE_UP:
192 move_up();
193 break;
194
195 case GLOBAL_LINE_DOWN:
196 move_down();
197 break;
198
199 case GLOBAL_SCROLL_DOWN:
200 scroll_down();
201 break;
202
203 case GLOBAL_SCROLL_UP:
204 scroll_up();
205 break;
206
207 case SELECT_SORT_ACTIVE: /* sort active groups */
208 sort_active_file();
209 break;
210
211 case GLOBAL_SET_RANGE:
212 if (selmenu.max) {
213 if (set_range(SELECT_LEVEL, 1, selmenu.max, selmenu.curr + 1))
214 show_selection_page();
215 } else
216 info_message(_(txt_no_groups));
217 break;
218
219 case GLOBAL_SEARCH_SUBJECT_FORWARD:
220 case GLOBAL_SEARCH_SUBJECT_BACKWARD:
221 case GLOBAL_SEARCH_REPEAT:
222 if (func == GLOBAL_SEARCH_REPEAT && last_search != GLOBAL_SEARCH_SUBJECT_FORWARD && last_search != GLOBAL_SEARCH_SUBJECT_BACKWARD)
223 info_message(_(txt_no_prev_search));
224 else {
225 if ((i = search_active((func == GLOBAL_SEARCH_SUBJECT_FORWARD), (func == GLOBAL_SEARCH_REPEAT))) != -1) {
226 move_to_item(i);
227 clear_message();
228 }
229 }
230 break;
231
232 case SELECT_ENTER_GROUP: /* go into group */
233 select_read_group();
234 break;
235
236 case SELECT_ENTER_NEXT_UNREAD_GROUP: /* enter next group containing unread articles */
237 if (pos_next_unread_group(FALSE))
238 read_groups();
239 break; /* Nothing more to do at the moment */
240
241 case GLOBAL_REDRAW_SCREEN:
242 my_retouch(); /* TODO: not done elsewhere, maybe should be in show_selection_page */
243 set_xclick_off();
244 show_selection_page();
245 break;
246
247 case SELECT_RESET_NEWSRC: /* reset .newsrc */
248 if (no_write) {
249 info_message(_(txt_info_no_write));
250 break;
251 }
252 if (prompt_yn(_(txt_reset_newsrc), FALSE) == 1) {
253 reset_newsrc();
254 sync_active_file();
255 selmenu.curr = 0;
256 show_selection_page();
257 }
258 break;
259
260 case CATCHUP: /* catchup - mark all articles as read */
261 case CATCHUP_NEXT_UNREAD: /* and goto next unread group */
262 if (selmenu.max)
263 catchup_group(&CURR_GROUP, (func == CATCHUP_NEXT_UNREAD));
264 else
265 info_message(_(txt_no_groups));
266 break;
267
268 case GLOBAL_EDIT_FILTER:
269 if (invoke_editor(filter_file, filter_file_offset, NULL))
270 (void) read_filter_file(filter_file);
271 show_selection_page();
272 break;
273
274 case SELECT_TOGGLE_DESCRIPTIONS: /* toggle newsgroup descriptions */
275 if (sel_fmt.show_grpdesc) {
276 show_description = bool_not(show_description);
277 if (show_description)
278 read_descriptions(TRUE);
279 show_selection_page();
280 } else
281 info_message(_(txt_grpdesc_disabled));
282 break;
283
284 case SELECT_GOTO: /* prompt for a new group name */
285 {
286 int oldmax = selmenu.max;
287
288 if ((n = choose_new_group()) >= 0) {
289 /*
290 * If a new group was added and it is on the actual screen
291 * draw it. If it is off screen the redraw will handle it.
292 */
293 if (oldmax != selmenu.max && n >= selmenu.first && n < selmenu.first + NOTESLINES)
294 build_gline(n);
295 move_to_item(n);
296 }
297 }
298 break;
299
300 case GLOBAL_HELP:
301 show_help_page(SELECT_LEVEL, _(txt_group_select_com));
302 show_selection_page();
303 break;
304
305 case GLOBAL_CONNECTION_INFO:
306 show_connection_page(SELECT_LEVEL, _(txt_connection_info));
307 show_selection_page();
308 break;
309
310 #ifdef NNTP_ABLE
311 case GLOBAL_LOOKUP_MESSAGEID:
312 switch (show_article_by_msgid(NULL)) {
313 case LOOKUP_OK:
314 show_selection_page();
315 break;
316
317 case LOOKUP_UNAVAIL:
318 info_message("%s %s", _(txt_lookup_func_not_available), _(txt_lookup_func_not_nntp));
319 break;
320
321 case LOOKUP_QUIT:
322 select_quit();
323 break;
324
325 default:
326 break;
327 }
328 break;
329 #endif /* NNTP_ABLE */
330
331 case GLOBAL_TOGGLE_HELP_DISPLAY: /* toggle mini help menu */
332 toggle_mini_help(SELECT_LEVEL);
333 show_selection_page();
334 break;
335
336 case GLOBAL_TOGGLE_INVERSE_VIDEO:
337 toggle_inverse_video();
338 show_selection_page();
339 show_inverse_video_status();
340 break;
341
342 #ifdef HAVE_COLOR
343 case GLOBAL_TOGGLE_COLOR:
344 if (toggle_color()) {
345 show_selection_page();
346 show_color_status();
347 }
348 break;
349 #endif /* HAVE_COLOR */
350
351 case GLOBAL_TOGGLE_INFO_LAST_LINE: /* display group description */
352 tinrc.info_in_last_line = bool_not(tinrc.info_in_last_line);
353 show_selection_page();
354 break;
355
356 case SELECT_MOVE_GROUP: /* reposition group within group list */
357 /* TODO: move all this to reposition_group() */
358 if (!selmenu.max) {
359 info_message(_(txt_no_groups));
360 break;
361 }
362
363 if (!CURR_GROUP.subscribed) {
364 info_message(_(txt_info_not_subscribed));
365 break;
366 }
367
368 if (no_write) {
369 info_message(_(txt_info_no_write));
370 break;
371 }
372
373 n = selmenu.curr;
374 selmenu.curr = reposition_group(&active[my_group[n]], n);
375 HpGlitch(erase_arrow());
376 if (selmenu.curr < selmenu.first || selmenu.curr >= selmenu.first + NOTESLINES - 1 || selmenu.curr != n)
377 show_selection_page();
378 else {
379 i = selmenu.curr;
380 selmenu.curr = n;
381 erase_arrow();
382 selmenu.curr = i;
383 clear_message();
384 draw_group_arrow();
385 }
386 break;
387
388 case GLOBAL_OPTION_MENU:
389 config_page(selmenu.max ? CURR_GROUP.name : NULL, signal_context);
390 show_selection_page();
391 break;
392
393 case SELECT_NEXT_UNREAD_GROUP: /* goto next unread group */
394 pos_next_unread_group(TRUE);
395 break;
396
397 case GLOBAL_QUIT: /* quit */
398 select_done();
399 break;
400
401 case GLOBAL_QUIT_TIN: /* quit, no ask */
402 select_quit();
403 break;
404
405 case SELECT_QUIT_NO_WRITE: /* quit, but don't save configuration */
406 if (prompt_yn(_(txt_quit_no_write), TRUE) == 1)
407 tin_done(EXIT_SUCCESS, NULL);
408 show_selection_page();
409 break;
410
411 case SELECT_TOGGLE_READ_DISPLAY:
412 /*
413 * If in tinrc.show_only_unread_groups mode toggle all
414 * subscribed to groups and only groups that contain unread
415 * articles
416 */
417 tinrc.show_only_unread_groups = bool_not(tinrc.show_only_unread_groups);
418 /*
419 * as we effectively do a yank out on each change, set yanked_out accordingly
420 */
421 yanked_out = TRUE;
422 wait_message(0, _(txt_reading_groups), (tinrc.show_only_unread_groups) ? _("unread") : _("all"));
423
424 toggle_my_groups(NULL);
425 show_selection_page();
426 if (tinrc.show_only_unread_groups)
427 info_message(_(txt_show_unread));
428 else
429 clear_message();
430 break;
431
432 case GLOBAL_BUGREPORT:
433 bug_report();
434 break;
435
436 case SELECT_SUBSCRIBE: /* subscribe to current group */
437 if (!selmenu.max) {
438 info_message(_(txt_no_groups));
439 break;
440 }
441 if (no_write) {
442 info_message(_(txt_info_no_write));
443 break;
444 }
445 if (!CURR_GROUP.subscribed && !CURR_GROUP.bogus) {
446 subscribe(&CURR_GROUP, SUBSCRIBED, TRUE);
447 show_selection_page();
448 info_message(_(txt_subscribed_to), CURR_GROUP.name);
449 move_down();
450 }
451 break;
452
453 case SELECT_SUBSCRIBE_PATTERN: /* subscribe to groups matching pattern */
454 if (no_write) {
455 info_message(_(txt_info_no_write));
456 break;
457 }
458 subscribe_pattern(_(txt_subscribe_pattern), _(txt_subscribing), _(txt_subscribed_num_groups), TRUE);
459 break;
460
461 case SELECT_UNSUBSCRIBE: /* unsubscribe to current group */
462 if (!selmenu.max) {
463 info_message(_(txt_no_groups));
464 break;
465 }
466 if (no_write) {
467 info_message(_(txt_info_no_write));
468 break;
469 }
470 if (CURR_GROUP.subscribed) {
471 #if defined(MULTIBYTE_ABLE) && !defined(NO_LOCALE)
472 mark_screen(selmenu.curr, flags_offset, CURR_GROUP.newgroup ? L"N" : L"u");
473 #else
474 mark_screen(selmenu.curr, flags_offset, CURR_GROUP.newgroup ? "N" : "u");
475 #endif /* MULTIBYTE_ABLE && !NO_LOCALE */
476 subscribe(&CURR_GROUP, UNSUBSCRIBED, TRUE);
477 info_message(_(txt_unsubscribed_to), CURR_GROUP.name);
478 move_down();
479 } else if (CURR_GROUP.bogus && tinrc.strip_bogus == BOGUS_SHOW) {
480 /* Bogus groups aren't subscribed to avoid confusion */
481 /* Note that there is no way to remove the group from active[] */
482 snprintf(buf, sizeof(buf), _(txt_remove_bogus), CURR_GROUP.name);
483 write_newsrc(); /* save current newsrc */
484 delete_group(CURR_GROUP.name); /* remove bogus group */
485 read_newsrc(newsrc, TRUE); /* reload newsrc */
486 toggle_my_groups(NULL); /* keep current display-state */
487 show_selection_page(); /* redraw screen */
488 info_message(buf);
489 }
490 break;
491
492 case SELECT_UNSUBSCRIBE_PATTERN: /* unsubscribe to groups matching pattern */
493 if (no_write) {
494 info_message(_(txt_info_no_write));
495 break;
496 }
497 subscribe_pattern(_(txt_unsubscribe_pattern),
498 _(txt_unsubscribing), _(txt_unsubscribed_num_groups), FALSE);
499 break;
500
501 case GLOBAL_VERSION: /* show tin version */
502 info_message(cvers);
503 break;
504
505 case GLOBAL_POST: /* post a basenote */
506 if (!selmenu.max) {
507 if (!can_post) {
508 info_message(_(txt_cannot_post));
509 break;
510 }
511 snprintf(buf, sizeof(buf), _(txt_post_newsgroups), tinrc.default_post_newsgroups);
512 if (!prompt_string_default(buf, tinrc.default_post_newsgroups, _(txt_no_newsgroups), HIST_POST_NEWSGROUPS))
513 break;
514 if (group_find(tinrc.default_post_newsgroups, FALSE) == NULL) {
515 error_message(2, _(txt_not_in_active_file), tinrc.default_post_newsgroups);
516 break;
517 } else {
518 strcpy(buf, tinrc.default_post_newsgroups);
519 #if 1 /* TODO: fix the rest of the code so we don't need this anymore */
520 /*
521 * this is a gross hack to avoid a crash in the
522 * CHARSET_CONVERSION conversion case in new_part()
523 * which currently relies on CURR_GROUP
524 */
525 selmenu.curr = my_group_add(buf, FALSE);
526 /*
527 * and the next hack to avoid a grabbled selection
528 * screen after the posting
529 */
530 toggle_my_groups(NULL);
531 toggle_my_groups(NULL);
532 #endif /* 1 */
533 }
534 } else
535 STRCPY(buf, CURR_GROUP.name);
536 if (!can_post && !CURR_GROUP.bogus && !CURR_GROUP.attribute->mailing_list) {
537 info_message(_(txt_cannot_post));
538 break;
539 }
540 if (post_article(buf))
541 show_selection_page();
542 break;
543
544 case GLOBAL_POSTPONED: /* post postponed article */
545 if (can_post) {
546 if (pickup_postponed_articles(FALSE, FALSE))
547 show_selection_page();
548 } else
549 info_message(_(txt_cannot_post));
550 break;
551
552 case GLOBAL_DISPLAY_POST_HISTORY: /* display messages posted by user */
553 if (post_hist_page())
554 show_selection_page();
555 break;
556
557 case SELECT_YANK_ACTIVE: /* yank in/out rest of groups from active */
558 yank_active_file();
559 break;
560
561 case SELECT_SYNC_WITH_ACTIVE: /* Re-read active file to see if any new news */
562 sync_active_file();
563 if (!yanked_out)
564 yank_active_file(); /* yank out if yanked in */
565 break;
566
567 case SELECT_MARK_GROUP_UNREAD:
568 if (!selmenu.max) {
569 info_message(_(txt_no_groups));
570 break;
571 }
572 grp_mark_unread(&CURR_GROUP);
573 if (CURR_GROUP.newsrc.num_unread)
574 STRCPY(buf, tin_ltoa(CURR_GROUP.newsrc.num_unread, (int) sel_fmt.len_ucnt));
575 else {
576 size_t j = 0;
577
578 while (j < sel_fmt.len_ucnt)
579 buf[j++] = ' ';
580 buf[j] = '\0';
581 }
582 #if defined(MULTIBYTE_ABLE) && !defined(NO_LOCALE)
583 if ((wtmp = char2wchar_t(buf))) {
584 mark_screen(selmenu.curr, ucnt_offset, wtmp);
585 free(wtmp);
586 }
587 #else
588 mark_screen(selmenu.curr, ucnt_offset, buf);
589 #endif /* MULTIBYTE_ABLE && !NO_LOCALE */
590 break;
591
592 default:
593 info_message(_(txt_bad_command), PrintFuncKey(key, GLOBAL_HELP, select_keys));
594 }
595 }
596 }
597
598
599 void
600 show_selection_page(
601 void)
602 {
603 char buf[LEN], keyhelp[MAXKEYLEN];
604 char *title;
605 int i, keyhelplen;
606 size_t len;
607 const char *secflag = "";
608
609 signal_context = cSelect;
610 currmenu = &selmenu;
611 parse_format_string(tinrc.select_format, &sel_fmt);
612 groupname_len = 0;
613 flags_offset = 0;
614 mark_offset = 0;
615 ucnt_offset = 0;
616
617 if (use_nntps) {
618 if (insecure_nntps)
619 secflag=_("[k]");
620 else
621 secflag=_("[T]");
622 }
623
624 if (read_news_via_nntp)
625 snprintf(buf, sizeof(buf), "%s (%s%s %d%s)", _(txt_group_selection), nntp_server, secflag, selmenu.max, (tinrc.show_only_unread_groups ? _(" R") : ""));
626 else
627 snprintf(buf, sizeof(buf), "%s (%d%s)", _(txt_group_selection), selmenu.max, (tinrc.show_only_unread_groups ? _(" R") : ""));
628
629 if (selmenu.curr < 0)
630 selmenu.curr = 0;
631
632 ClearScreen();
633 set_first_screen_item();
634
635 /*
636 * determine max. length for centered title
637 */
638 if (tinrc.show_help_mail_sign != SHOW_SIGN_NONE) {
639 if (tinrc.show_help_mail_sign == SHOW_SIGN_MAIL)
640 len = cCOLS - (2 * strwidth(_(txt_you_have_mail))) - 2;
641 else {
642 PrintFuncKey(keyhelp, GLOBAL_HELP, select_keys);
643 keyhelplen = strwidth(keyhelp);
644 if (tinrc.show_help_mail_sign == SHOW_SIGN_HELP)
645 len = cCOLS - (2 * (strwidth(_(txt_type_h_for_help)) - 2 + keyhelplen)) - 2;
646 else
647 len = cCOLS - (2 * MAX(strwidth(_(txt_type_h_for_help)) - 2 + keyhelplen, strwidth(_(txt_you_have_mail)))) - 2;
648 }
649 } else
650 len = cCOLS - 2;
651
652 title = strunc(buf, len);
653 show_title(title);
654 free(title);
655
656 if (sel_fmt.len_grpname_max && !sel_fmt.len_grpname) {
657 /*
658 * calculate max length of groupname field
659 * if yanked in (yanked_out == FALSE) check all groups in active file
660 * otherwise just subscribed to groups
661 */
662 if (yanked_out) {
663 for (i = 0; i < selmenu.max; i++) {
664 if ((len = (size_t) strwidth(active[my_group[i]].name)) > sel_fmt.len_grpname)
665 sel_fmt.len_grpname = len;
666 }
667 } else {
668 for_each_group(i) {
669 if ((len = (size_t) strwidth(active[i].name)) > sel_fmt.len_grpname)
670 sel_fmt.len_grpname = len;
671 }
672 }
673 }
674
675 groupname_len = (sel_fmt.show_grpdesc && show_description) ? (int) sel_fmt.len_grpname_dsc : (int) sel_fmt.len_grpname;
676
677 if (groupname_len > (int) sel_fmt.len_grpname_max)
678 groupname_len = (int) sel_fmt.len_grpname_max;
679 if (groupname_len < 0)
680 groupname_len = 0;
681
682 if (!sel_fmt.len_grpdesc)
683 sel_fmt.len_grpdesc = (sel_fmt.len_grpname_max - (size_t) groupname_len);
684 else {
685 if (sel_fmt.len_grpdesc > (sel_fmt.len_grpname_max - (size_t) groupname_len))
686 sel_fmt.len_grpdesc = (sel_fmt.len_grpname_max - (size_t) groupname_len);
687 }
688
689 flags_offset = (int) (sel_fmt.flags_offset + (size_t) (sel_fmt.g_before_f ? groupname_len : 0) + (sel_fmt.d_before_f ? sel_fmt.len_grpdesc : 0));
690 ucnt_offset = (int) (sel_fmt.ucnt_offset + (size_t) (sel_fmt.g_before_u ? groupname_len : 0) + (sel_fmt.d_before_u ? sel_fmt.len_grpdesc : 0));
691
692 for (i = selmenu.first; i < selmenu.first + NOTESLINES && i < selmenu.max; i++)
693 build_gline(i);
694
695 show_mini_help(SELECT_LEVEL);
696
697 #ifdef NNTP_ABLE
698 did_reconnect = FALSE;
699 #endif /* NNTP_ABLE */
700
701 if (selmenu.max <= 0) {
702 info_message(_(txt_no_groups));
703 return;
704 }
705
706 draw_group_arrow();
707 }
708
709
710 static void
711 build_gline(
712 int i)
713 {
714 char *sptr, *fmt, *buf;
715 char subs;
716 int n;
717 size_t j;
718 #if defined(MULTIBYTE_ABLE) && !defined(NO_LOCALE)
719 char *name_buf = NULL;
720 char *desc_buf = NULL;
721 wchar_t *active_name, *active_name2, *active_desc, *active_desc2;
722 #else
723 char *active_name, *active_name2;
724 size_t fill, len_start;
725 #endif /* MULTIBYTE_ABLE && !NO_LOCALE */
726
727 #ifdef USE_CURSES
728 /*
729 * Allocate line buffer
730 * make it the same size like in !USE_CURSES case to simplify the code
731 */
732 # if defined(MULTIBYTE_ABLE) && !defined(NO_LOCALE)
733 sptr = my_malloc(cCOLS * MB_CUR_MAX + 2);
734 # else
735 sptr = my_malloc(cCOLS + 2);
736 # endif /* MULTIBYTE_ABLE && !NO_LOCALE */
737 #else
738 sptr = screen[INDEX2SNUM(i)].col;
739 #endif /* USE_CURSES */
740
741 sptr[0] = '\0';
742 fmt = sel_fmt.str;
743 n = my_group[i];
744
745 if (tinrc.draw_arrow)
746 strcat(sptr, " ");
747
748 for (; *fmt; fmt++) {
749 if (*fmt != '%') {
750 strncat(sptr, fmt, 1);
751 continue;
752 }
753 switch (*++fmt) {
754 case '\0':
755 break;
756
757 case '%':
758 strncat(sptr, fmt, 1);
759 break;
760
761 case 'd':
762 #if defined(MULTIBYTE_ABLE) && !defined(NO_LOCALE)
763 if (show_description && active[n].description) {
764 active_desc = char2wchar_t(active[n].description);
765 if (active_desc) {
766 if ((active_desc2 = wcspart(active_desc, (int) sel_fmt.len_grpdesc, TRUE)) != NULL) {
767 desc_buf = wchar_t2char(active_desc2);
768 free(active_desc);
769 free(active_desc2);
770 }
771 }
772 if (desc_buf) {
773 strcat(sptr, desc_buf);
774 FreeAndNull(desc_buf);
775 }
776 } else {
777 buf = sptr + strlen(sptr);
778 for (j = 0; j < sel_fmt.len_grpdesc; ++j)
779 *buf++ = ' ';
780 *buf = '\0';
781 }
782 #else
783 if (show_description && active[n].description) {
784 len_start = strwidth(sptr);
785 strncat(sptr, active[n].description, sel_fmt.len_grpdesc);
786 fill = sel_fmt.len_grpdesc - (strwidth(sptr) - len_start);
787 } else
788 fill = sel_fmt.len_grpdesc;
789 buf = sptr + strlen(sptr);
790 for (j = 0; j < fill; ++j)
791 *buf++ = ' ';
792 *buf = '\0';
793 #endif /* MULTIBYTE_ABLE && !NO_LOCALE */
794 break;
795
796 case 'f':
797 /*
798 * Display a flag for this group if needed
799 * . Bogus groups are dumped immediately 'D'
800 * . Normal subscribed groups may be
801 * ' ' normal, 'X' not postable, 'M' moderated, '=' renamed
802 * . Newgroups are 'N'
803 * . Unsubscribed groups are 'u'
804 *
805 * TODO: make flags configurable via tinrc?
806 */
807 if (active[n].bogus) /* Group is not in active list */
808 subs = 'D';
809 else if (active[n].subscribed) /* Important that this precedes Newgroup */
810 subs = group_flag(active[n].moderated);
811 else
812 subs = ((active[n].newgroup) ? 'N' : 'u'); /* New (but unsubscribed) group or unsubscribed group */
813 buf = sptr + strlen(sptr);
814 *buf++ = subs;
815 *buf = '\0';
816 break;
817
818 case 'G':
819 #if defined(MULTIBYTE_ABLE) && !defined(NO_LOCALE)
820 if ((active_name = char2wchar_t(active[n].name)) == NULL) /* If char2wchar_t() fails try again after replacing unprintable characters */
821 active_name = char2wchar_t(convert_to_printable(active[n].name, FALSE));
822
823 if (active_name && tinrc.abbreviate_groupname) {
824 active_name2 = abbr_wcsgroupname(active_name, groupname_len);
825 free(active_name);
826 } else
827 active_name2 = active_name;
828
829 if (active_name2 && (active_name = wcspart(active_name2, groupname_len, TRUE)) != NULL) {
830 free(active_name2);
831 name_buf = wchar_t2char(active_name);
832 free(active_name);
833 }
834 if (name_buf) {
835 strcat(sptr, name_buf);
836 FreeAndNull(name_buf);
837 }
838 #else
839 if (tinrc.abbreviate_groupname)
840 active_name = abbr_groupname(active[n].name, groupname_len);
841 else
842 active_name = my_strdup(active[n].name);
843
844 active_name2 = my_malloc(groupname_len + 1);
845 snprintf(active_name2, groupname_len + 1, "%-*s", groupname_len, active_name);
846 strcat(sptr, active_name2);
847 free(active_name);
848 free(active_name2);
849 #endif /* MULTIBYTE_ABLE && !NO_LOCALE */
850 break;
851
852 case 'n':
853 strcat(sptr, tin_ltoa(i + 1, (int) sel_fmt.len_linenumber));
854 break;
855
856 case 'U':
857 if (active[my_group[i]].inrange) {
858 #if defined(MULTIBYTE_ABLE) && !defined(NO_LOCALE)
859 char tmp_buf[10];
860
861 buf = sptr + strlen(sptr);
862 for (j = 1; j <= sel_fmt.len_ucnt - (art_mark_width - (art_mark_width - wcwidth(tinrc.art_marked_inrange))); ++j)
863 *buf++ = ' ';
864 snprintf(tmp_buf, sizeof(tmp_buf), "%"T_CHAR_FMT, tinrc.art_marked_inrange);
865 *buf-- = '\0';
866 strcat(buf, tmp_buf);
867 #else
868 buf = sptr + strlen(sptr);
869 for (j = 1; j < sel_fmt.len_ucnt; ++j)
870 *buf++ = ' ';
871 *buf++ = tinrc.art_marked_inrange;
872 *buf = '\0';
873 #endif /* MULTIBYTE_ABLE && !NO_LOCALE */
874 } else if (active[my_group[i]].newsrc.num_unread) {
875 int getart_limit;
876 t_artnum num_unread;
877
878 getart_limit = (cmdline.args & CMDLINE_GETART_LIMIT) ? cmdline.getart_limit : tinrc.getart_limit;
879 num_unread = active[my_group[i]].newsrc.num_unread;
880 if (getart_limit > 0 && getart_limit < num_unread)
881 num_unread = getart_limit;
882 strcat(sptr, tin_ltoa(num_unread, (int) sel_fmt.len_ucnt));
883 } else {
884 buf = sptr + strlen(sptr);
885 for (j = 0; j < sel_fmt.len_ucnt; ++j)
886 *buf++ = ' ';
887 *buf = '\0';
888 }
889 break;
890
891 default:
892 break;
893 }
894 }
895 #ifndef USE_CURSES
896 if (tinrc.strip_blanks)
897 strcat(strip_line(sptr), cCRLF);
898 #endif /* !USE_CURSES */
899
900 WriteLine(INDEX2LNUM(i), sptr);
901
902 #ifdef USE_CURSES
903 free(sptr);
904 #endif /* USE_CURSES */
905 }
906
907
908 static void
909 draw_group_arrow(
910 void)
911 {
912 if (!selmenu.max)
913 info_message(_(txt_no_groups));
914 else {
915 draw_arrow_mark(INDEX_TOP + selmenu.curr - selmenu.first);
916 if (CURR_GROUP.aliasedto)
917 info_message(_(txt_group_aliased), CURR_GROUP.aliasedto);
918 else if (tinrc.info_in_last_line)
919 info_message("%s", CURR_GROUP.description ? CURR_GROUP.description : _(txt_no_description));
920 else if (selmenu.curr == selmenu.max - 1)
921 info_message(_(txt_end_of_groups));
922 }
923 }
924
925
926 static void
927 sync_active_file(
928 void)
929 {
930 wait_message(0, _(txt_reading_news_newsrc_file));
931 force_reread_active_file = TRUE;
932 resync_active_file();
933 }
934
935
936 static void
937 yank_active_file(
938 void)
939 {
940 if (yanked_out && selmenu.max == num_active) { /* All groups currently present? */
941 info_message(_(txt_yanked_none));
942 return;
943 }
944
945 if (yanked_out) { /* Yank in */
946 int i;
947 int prevmax = selmenu.max;
948
949 save_restore_curr_group(TRUE); /* Save group position */
950
951 /*
952 * Reset counter and load all the groups in active[] into my_group[]
953 */
954 selmenu.max = 0;
955 for_each_group(i)
956 my_group[selmenu.max++] = i;
957
958 selmenu.curr = save_restore_curr_group(FALSE); /* Restore previous group position */
959 yanked_out = bool_not(yanked_out);
960 show_selection_page();
961 info_message(_(txt_yanked_groups), selmenu.max - prevmax, PLURAL(selmenu.max - prevmax, txt_group));
962 } else { /* Yank out */
963 toggle_my_groups(NULL);
964 HpGlitch(erase_arrow());
965 yanked_out = bool_not(yanked_out);
966 show_selection_page();
967 info_message(_(txt_yanked_sub_groups));
968 }
969 }
970
971
972 /*
973 * Sort active[] and associated tin_sort() helper function
974 */
975 static int
976 active_comp(
977 t_comptype p1,
978 t_comptype p2)
979 {
980 const struct t_group *s1 = (const struct t_group *) p1;
981 const struct t_group *s2 = (const struct t_group *) p2;
982
983 return strcasecmp(s1->name, s2->name);
984 }
985
986
987 /*
988 * Call with TRUE to file away the current cursor position
989 * Call again with FALSE to return a suggested value to restore the
990 * current cursor (selmenu.curr) position
991 */
992 static int
993 save_restore_curr_group(
994 t_bool saving)
995 {
996 static char *oldgroup;
997 static int oldmax = 0;
998 int ret;
999
1000 /*
1001 * Take a copy of the current groupname, if present
1002 */
1003 if (saving) {
1004 oldmax = selmenu.max;
1005 if (oldmax)
1006 oldgroup = my_strdup(CURR_GROUP.name);
1007 return 0;
1008 }
1009
1010 /*
1011 * Find & return the new screen position of the group
1012 */
1013 ret = -1;
1014
1015 if (oldmax) {
1016 ret = my_group_find(oldgroup);
1017 FreeAndNull(oldgroup);
1018 }
1019
1020 if (ret == -1) { /* Group not present, return something semi-useful */
1021 if (selmenu.max > 0)
1022 ret = selmenu.max - 1;
1023 else
1024 ret = 0;
1025 }
1026 return ret;
1027 }
1028
1029
1030 static void
1031 sort_active_file(
1032 void)
1033 {
1034 save_restore_curr_group(TRUE);
1035
1036 tin_sort(active, (size_t) num_active, sizeof(struct t_group), active_comp);
1037 group_rehash(yanked_out);
1038
1039 selmenu.curr = save_restore_curr_group(FALSE);
1040
1041 show_selection_page();
1042 }
1043
1044
1045 int
1046 choose_new_group(
1047 void)
1048 {
1049 char *prompt;
1050 int idx;
1051
1052 prompt = fmt_string(_(txt_newsgroup), tinrc.default_goto_group);
1053
1054 if (!(prompt_string_default(prompt, tinrc.default_goto_group, "", HIST_GOTO_GROUP))) {
1055 free(prompt);
1056 return -1;
1057 }
1058 free(prompt);
1059
1060 str_trim(tinrc.default_goto_group);
1061
1062 if (tinrc.default_goto_group[0] == '\0')
1063 return -1;
1064
1065 clear_message();
1066
1067 if ((idx = my_group_add(tinrc.default_goto_group, TRUE)) == -1)
1068 info_message(_(txt_not_in_active_file), tinrc.default_goto_group);
1069
1070 return idx;
1071 }
1072
1073
1074 /*
1075 * Return new value for selmenu.max, skipping any new newsgroups that have been
1076 * found
1077 */
1078 int
1079 skip_newgroups(
1080 void)
1081 {
1082 int i = 0;
1083
1084 if (selmenu.max) {
1085 while (i < selmenu.max && active[my_group[i]].newgroup)
1086 i++;
1087 }
1088
1089 return i;
1090 }
1091
1092
1093 /*
1094 * Find a group in the users selection list, my_group[].
1095 * If 'add' is TRUE, then add the supplied group return the index into
1096 * my_group[] if group is added or was already there. Return -1 if group
1097 * is not in active[]
1098 *
1099 * NOTE: can't be static due to my_group_add() macro
1100 */
1101 int
1102 add_my_group(
1103 const char *group,
1104 t_bool add,
1105 t_bool ignore_case)
1106 {
1107 int i, j;
1108
1109 if ((i = find_group_index(group, ignore_case)) < 0)
1110 return -1;
1111
1112 for (j = 0; j < selmenu.max; j++) {
1113 if (my_group[j] == i)
1114 return j;
1115 }
1116
1117 if (add) {
1118 my_group[selmenu.max++] = i;
1119 return (selmenu.max - 1);
1120 }
1121
1122 return -1;
1123 }
1124
1125
1126 static int
1127 reposition_group(
1128 struct t_group *group,
1129 int default_num)
1130 {
1131 char buf[LEN];
1132 char pos[LEN];
1133 int pos_num, newgroups;
1134
1135 /* Have already trapped no_write at this point */
1136
1137 snprintf(buf, sizeof(buf), _(txt_newsgroup_position), group->name,
1138 (tinrc.default_move_group ? tinrc.default_move_group : default_num + 1));
1139
1140 if (!prompt_string(buf, pos, HIST_MOVE_GROUP))
1141 return default_num;
1142
1143 if (*pos)
1144 pos_num = ((pos[0] == '$') ? selmenu.max : atoi(pos));
1145 else {
1146 if (tinrc.default_move_group)
1147 pos_num = tinrc.default_move_group;
1148 else
1149 return default_num;
1150 }
1151
1152 if (pos_num > selmenu.max)
1153 pos_num = selmenu.max;
1154 else if (pos_num <= 0)
1155 pos_num = 1;
1156
1157 newgroups = skip_newgroups();
1158
1159 /*
1160 * Can't move into newgroups, they aren't in .newsrc
1161 */
1162 if (pos_num <= newgroups) {
1163 error_message(2, _(txt_skipping_newgroups));
1164 return default_num;
1165 }
1166
1167 wait_message(0, _(txt_moving), group->name);
1168
1169 /*
1170 * seems to have the side effect of rearranging
1171 * my_groups, so tinrc.show_only_unread_groups has to be updated
1172 */
1173 tinrc.show_only_unread_groups = FALSE;
1174
1175 /*
1176 * New newgroups aren't in .newsrc so we need to offset to
1177 * get the right position
1178 */
1179 if (pos_group_in_newsrc(group, pos_num - newgroups)) {
1180 read_newsrc(newsrc, TRUE);
1181 tinrc.default_move_group = pos_num;
1182 return (pos_num - 1);
1183 } else {
1184 tinrc.default_move_group = default_num + 1;
1185 return default_num;
1186 }
1187 }
1188
1189
1190 static void
1191 catchup_group(
1192 struct t_group *group,
1193 t_bool goto_next_unread_group)
1194 {
1195 char *smsg = NULL;
1196 #if defined(MULTIBYTE_ABLE) && !defined(NO_LOCALE)
1197 wchar_t *wtmp;
1198 #endif /* MULTIBYTE_ABLE && !NO_LOCALE */
1199
1200 if ((!TINRC_CONFIRM_ACTION) || prompt_yn(sized_message(&smsg, _(txt_mark_group_read), group->name), TRUE) == 1) {
1201 grp_mark_read(group, NULL);
1202 {
1203 char buf[LEN];
1204 size_t i = 0;
1205
1206 while (i < sel_fmt.len_ucnt)
1207 buf[i++] = ' ';
1208 buf[i] = '\0';
1209 #if defined(MULTIBYTE_ABLE) && !defined(NO_LOCALE)
1210 if ((wtmp = char2wchar_t(buf))) {
1211 mark_screen(selmenu.curr, ucnt_offset, wtmp);
1212 free(wtmp);
1213 }
1214 #else
1215 mark_screen(selmenu.curr, ucnt_offset, buf);
1216 #endif /* MULTIBYTE_ABLE && !NO_LOCALE */
1217 }
1218
1219 if (goto_next_unread_group)
1220 pos_next_unread_group(TRUE);
1221 else
1222 move_down();
1223 }
1224 FreeIfNeeded(smsg);
1225 }
1226
1227
1228 /*
1229 * Set selmenu.curr to next group with unread arts
1230 * If the current group has unread arts, it will be found first !
1231 * If redraw is set, update the selection menu appropriately
1232 * Return FALSE if no groups left to read
1233 * TRUE at all other times
1234 */
1235 static t_bool
1236 pos_next_unread_group(
1237 t_bool redraw)
1238 {
1239 int i;
1240 t_bool all_groups_read = TRUE;
1241
1242 if (!selmenu.max)
1243 return FALSE;
1244
1245 for (i = selmenu.curr; i < selmenu.max; i++) {
1246 if (UNREAD_GROUP(i)) {
1247 all_groups_read = FALSE;
1248 break;
1249 }
1250 }
1251
1252 if (all_groups_read) {
1253 for (i = 0; i < selmenu.curr; i++) {
1254 if (UNREAD_GROUP(i)) {
1255 all_groups_read = FALSE;
1256 break;
1257 }
1258 }
1259 }
1260
1261 if (all_groups_read) {
1262 info_message(_(txt_no_groups_to_read));
1263 return FALSE;
1264 }
1265
1266 if (redraw)
1267 move_to_item(i);
1268 else
1269 selmenu.curr = i;
1270
1271 return TRUE;
1272 }
1273
1274
1275 /*
1276 * This is the main loop that cycles through, reading groups.
1277 * We keep going until we return to the selection screen or exit tin
1278 */
1279 static void
1280 read_groups(
1281 void)
1282 {
1283 t_bool done = FALSE;
1284
1285 clear_message();
1286
1287 while (!done) { /* if xmin > xmax the newsservers active is broken */
1288 switch (group_page(&CURR_GROUP)) {
1289 case GRP_QUIT:
1290 select_quit();
1291 break;
1292
1293 case GRP_NEXT:
1294 if (selmenu.curr + 1 < selmenu.max)
1295 selmenu.curr++;
1296 done = TRUE;
1297 break;
1298
1299 case GRP_NEXTUNREAD:
1300 if (!pos_next_unread_group(FALSE))
1301 done = TRUE;
1302 break;
1303
1304 case GRP_ENTER: /* group_page() has already set selmenu.curr */
1305 break;
1306
1307 case GRP_RETSELECT:
1308 case GRP_EXIT:
1309 default:
1310 done = TRUE;
1311 break;
1312 }
1313 }
1314
1315 if (!need_reread_active_file())
1316 show_selection_page();
1317 }
1318
1319
1320 /*
1321 * Toggle my_group[] between all groups / only unread groups
1322 * We make a special case for Newgroups (always appear, at the top)
1323 * and Bogus groups if tinrc.strip_bogus = BOGUS_SHOW
1324 */
1325 void
1326 toggle_my_groups(
1327 const char *group)
1328 {
1329 #if 1
1330 FILE *fp;
1331 char buf[NEWSRC_LINE];
1332 char *ptr;
1333 #endif /* 1 */
1334 int i;
1335
1336 /*
1337 * Save current or next group with unread arts for later use
1338 */
1339 if (selmenu.max) {
1340 int old_curr_group_idx = 0;
1341
1342 if (group != NULL) {
1343 if ((i = my_group_find(group)) >= 0)
1344 old_curr_group_idx = i;
1345 } else
1346 old_curr_group_idx = (selmenu.curr == -1) ? 0 : selmenu.curr;
1347
1348 if (tinrc.show_only_unread_groups) {
1349 for (i = old_curr_group_idx; i < selmenu.max; i++) {
1350 if (UNREAD_GROUP(i) || active[my_group[i]].newgroup) {
1351 old_curr_group_idx = i;
1352 break;
1353 }
1354 }
1355 }
1356 selmenu.curr = old_curr_group_idx; /* Set current group to save */
1357 } else
1358 selmenu.curr = 0;
1359
1360 save_restore_curr_group(TRUE);
1361
1362 selmenu.max = skip_newgroups(); /* Reposition after any newgroups */
1363
1364 /* TODO: why re-read .newsrc here, instead of something like this... */
1365 #if 0
1366 for_each_group(i) {
1367 if (active[i].subscribed) {
1368 if (tinrc.show_only_unread_groups) {
1369 if (active[i].newsrc.num_unread > 0 || (active[i].bogus && tinrc.strip_bogus == BOGUS_SHOW))
1370 my_group[selmenu.max++] = i;
1371 } else
1372 my_group[selmenu.max++] = i;
1373 }
1374 }
1375 #else
1376 /* preserve group ordering based on newsrc */
1377 if ((fp = fopen(newsrc, "r")) == NULL)
1378 return;
1379
1380 while (fgets(buf, (int) sizeof(buf), fp) != NULL) {
1381 if ((ptr = strchr(buf, SUBSCRIBED)) != NULL) {
1382 *ptr = '\0';
1383
1384 if ((i = find_group_index(buf, FALSE)) < 0)
1385 continue;
1386
1387 if (tinrc.show_only_unread_groups) {
1388 if (active[i].newsrc.num_unread || (active[i].bogus && tinrc.strip_bogus == BOGUS_SHOW))
1389 my_group_add(buf, FALSE);
1390 } else
1391 my_group_add(buf, FALSE);
1392 }
1393 }
1394 fclose(fp);
1395 #endif /* 0 */
1396 selmenu.curr = save_restore_curr_group(FALSE); /* Restore saved group position */
1397 }
1398
1399
1400 /*
1401 * Subscribe or unsubscribe from a list of groups. List can be full list as
1402 * supported by match_group_list()
1403 */
1404 static void
1405 subscribe_pattern(
1406 const char *prompt,
1407 const char *message,
1408 const char *result,
1409 t_bool state)
1410 {
1411 char buf[LEN];
1412 int i, subscribe_num = 0;
1413
1414 if (!num_active || no_write)
1415 return;
1416
1417 if (!prompt_string(prompt, buf, HIST_OTHER) || !*buf) {
1418 clear_message();
1419 return;
1420 }
1421
1422 wait_message(0, "%s", message);
1423
1424 for_each_group(i) {
1425 if (match_group_list(active[i].name, buf)) {
1426 if (active[i].subscribed != (state != FALSE)) {
1427 spin_cursor();
1428 /* If found and group is not subscribed add it to end of my_group[]. */
1429 subscribe(&active[i], SUB_CHAR(state), TRUE);
1430 if (state) {
1431 my_group_add(active[i].name, FALSE);
1432 grp_mark_unread(&active[i]);
1433 }
1434 subscribe_num++;
1435 }
1436 }
1437 }
1438
1439 if (subscribe_num) {
1440 toggle_my_groups(NULL);
1441 show_selection_page();
1442 info_message(result, subscribe_num);
1443 } else
1444 info_message(_(txt_no_match));
1445 }
1446
1447
1448 /*
1449 * Does NOT return
1450 */
1451 _Noreturn static void
1452 select_quit(
1453 void)
1454 {
1455 write_config_file(local_config_file);
1456 ClearScreen();
1457 tin_done(EXIT_SUCCESS, NULL); /* Tin END */
1458 }
1459
1460
1461 static void
1462 select_done(
1463 void)
1464 {
1465 if (!TINRC_CONFIRM_TO_QUIT || prompt_yn(_(txt_quit), TRUE) == 1)
1466 select_quit();
1467 if (!no_write && prompt_yn(_(txt_save_config), TRUE) == 1) {
1468 write_config_file(local_config_file);
1469 write_newsrc();
1470 }
1471 show_selection_page();
1472 }
1473
1474
1475 static void
1476 select_read_group(
1477 void)
1478 {
1479 struct t_group *currgrp;
1480
1481 if (!selmenu.max || selmenu.curr == -1) {
1482 info_message(_(txt_no_groups));
1483 return;
1484 }
1485
1486 currgrp = &CURR_GROUP;
1487
1488 if (currgrp->bogus) {
1489 info_message(_(txt_not_exist));
1490 return;
1491 }
1492
1493 if (currgrp->xmax > 0 && (currgrp->xmin <= currgrp->xmax))
1494 read_groups();
1495 else
1496 info_message(_(txt_no_arts));
1497 }
1498
1499
1500 #ifdef NNTP_ABLE
1501 /*
1502 * Try to fetch articles Nesgroups-header via [X]HDR or XPAT.
1503 */
1504 static char *
1505 lookup_msgid(
1506 char *msgid)
1507 {
1508 if (read_news_via_nntp && !read_saved_news) {
1509 if (!nntp_caps.hdr_cmd && !nntp_caps.xpat) {
1510 info_message(_(txt_lookup_func_not_available));
1511 return NULL;
1512 }
1513 if (msgid) {
1514 char *ptr, *r = NULL;
1515 static char *x;
1516 char buf[NNTP_STRLEN];
1517 int ret;
1518
1519 if (nntp_caps.hdr_cmd) {
1520 snprintf(buf, sizeof(buf), "%s Newsgroups %s", nntp_caps.hdr_cmd, msgid);
1521 ret = new_nntp_command(buf, (nntp_caps.type == CAPABILITIES) ? OK_HDR : OK_HEAD, NULL, 0);
1522
1523 switch (ret) {
1524 case OK_HEAD:
1525 case OK_HDR:
1526 x = NULL;
1527 while ((ptr = tin_fgets(FAKE_NNTP_FP, FALSE)) != NULL) {
1528 # ifdef DEBUG
1529 if (debug & DEBUG_NNTP)
1530 debug_print_file("NNTP", "<<<%s%s", logtime(), ptr);
1531 # endif /* DEBUG */
1532
1533 if (ret == OK_HEAD) { /* RFC 2980 ("%s %s", id, grp) */
1534 if (!strncmp(ptr, msgid, strlen(msgid))) { /* INN, MPNews, Leafnode, Cnews nntpd */
1535 r = ptr + strlen(msgid) + 1;
1536 } else { /* DNEWS ("%d %s", num, grp) */
1537 r = ptr;
1538 while (*r && *r != ' ' && *r != '\t')
1539 r++;
1540 while (*r && (*r == ' ' || *r == '\t'))
1541 r++;
1542 }
1543 }
1544
1545 if (ret == OK_HDR) { /* RFC 3977 ("0 %s", grp) */
1546 if (*ptr == '0' && (*(ptr + 1) == ' ' || *(ptr + 1) == '\t'))
1547 r = ptr + 2;
1548 }
1549
1550 if (r) {
1551 FreeIfNeeded(x); /* only required on bogus multi responses, just to be safe */
1552 x = my_strdup(r);
1553 }
1554 }
1555
1556 if (x)
1557 return x;
1558
1559 if (!r) {
1560 # ifdef DEBUG
1561 if ((debug & DEBUG_NNTP) && verbose > 1)
1562 debug_print_file("NNTP", "lookup_msgid(%s) response empty or not recognized", buf);
1563 # endif /* DEBUG */
1564 if (!nntp_caps.xpat)
1565 info_message(_(txt_lookup_func_not_available));
1566 }
1567 if (r || !nntp_caps.xpat)
1568 return NULL;
1569 break;
1570
1571 case ERR_NOART:
1572 info_message(_(txt_art_unavailable));
1573 return NULL;
1574
1575 default:
1576 if (!nntp_caps.xpat) { /* try only once */
1577 info_message(_(txt_lookup_func_not_available));
1578 return NULL;
1579 }
1580 break;
1581 }
1582 }
1583
1584 if (nntp_caps.xpat) {
1585 snprintf(buf, sizeof(buf), "XPAT Newsgroups %s *", msgid);
1586 ret = new_nntp_command(buf, OK_HEAD, NULL, 0);
1587 x = NULL;
1588 switch (ret) {
1589 case OK_HEAD:
1590 while ((ptr = tin_fgets(FAKE_NNTP_FP, FALSE)) != NULL) {
1591 # ifdef DEBUG
1592 if (debug & DEBUG_NNTP)
1593 debug_print_file("NNTP", "<<<%s%s", logtime(), ptr);
1594 # endif /* DEBUG */
1595 if (!strncmp(ptr, msgid, strlen(msgid)))
1596 r = ptr + strlen(msgid) + 1;
1597
1598 if (r) {
1599 FreeIfNeeded(x); /* only required on bogus multi responses, just to be safe */
1600 x = my_strdup(r);
1601 }
1602 }
1603
1604 if (x)
1605 return x;
1606
1607 if (!r) {
1608 # ifdef DEBUG
1609 if ((debug & DEBUG_NNTP) && verbose > 1)
1610 debug_print_file("NNTP", "lookup_msgid(%s) response empty or not recognized", buf);
1611 # endif /* DEBUG */
1612 info_message(_(txt_lookup_func_not_available));
1613 /* nntp_caps.xpat = FALSE; */ /* ? */
1614 }
1615 return NULL;
1616
1617 case ERR_NOART:
1618 info_message(_(txt_art_unavailable));
1619 return NULL;
1620
1621 default:
1622 nntp_caps.xpat = FALSE;
1623 break;
1624 }
1625 }
1626 info_message(_(txt_lookup_func_not_available));
1627 }
1628 } else
1629 info_message("%s %s", _(txt_lookup_func_not_available), _(txt_lookup_func_not_nntp));
1630
1631 return NULL;
1632 }
1633
1634
1635 /*
1636 * Get a message ID for the 'L' command. Add <> if needed.
1637 * Try to enter an appropriate group and display the referenced article.
1638 * If no group from the Newsgroups:-header is available, display the
1639 * contents of the header.
1640 */
1641 int
1642 show_article_by_msgid(
1643 char *messageid)
1644 {
1645 char id[NNTP_STRLEN]; /* still way too big; RFC 3977 3.6 & RFC 5536 3.1.3 limit Message-ID to max 250 octets */
1646 char *idptr = NULL;
1647 char *newsgroups = NULL;
1648 int i, ret = 0;
1649 struct t_article *art;
1650 struct t_group *group = NULL;
1651 struct t_group *tmp_group = NULL;
1652 struct t_msgid *msgid = NULL;
1653 t_bool tmp_cache_overview_files;
1654 t_bool tmp_show_only_unread_arts;
1655
1656 if (!(read_news_via_nntp && !read_saved_news)) {
1657 return LOOKUP_UNAVAIL;
1658 }
1659
1660 if (messageid) {
1661 idptr = messageid;
1662 newsgroups = lookup_msgid(idptr);
1663 } else {
1664 if (prompt_string(_(txt_enter_message_id), id + 1, HIST_MESSAGE_ID) && id[1]) {
1665 idptr = str_trim(id + 1);
1666 if (id[1] != '<') {
1667 id[0] = '<';
1668 strcat(id, ">");
1669 idptr = id;
1670 }
1671 newsgroups = lookup_msgid(idptr);
1672 }
1673 }
1674
1675 if (!newsgroups)
1676 return LOOKUP_ART_UNAVAIL;
1677
1678 if ((group = get_group_from_list(newsgroups)) == NULL) {
1679 info_message(strchr(newsgroups, ',') ? _(txt_lookup_show_groups) : _(txt_lookup_show_group), newsgroups);
1680 free(newsgroups);
1681 return LOOKUP_FAILED;
1682 }
1683
1684 if (curr_group)
1685 tmp_group = curr_group;
1686 curr_group = group;
1687 num_of_tagged_arts = 0;
1688 range_active = FALSE;
1689 last_resp = -1;
1690 this_resp = -1;
1691 tmp_cache_overview_files = tinrc.cache_overview_files;
1692 tinrc.cache_overview_files = FALSE;
1693 tmp_show_only_unread_arts = curr_group->attribute->show_only_unread_arts;
1694 curr_group->attribute->show_only_unread_arts = FALSE;
1695
1696 if (!index_group(group)) {
1697 for_each_art(i) {
1698 art = &arts[i];
1699 FreeAndNull(art->refs);
1700 FreeAndNull(art->msgid);
1701 }
1702 tin_errno = 0;
1703 ret = LOOKUP_FAILED;
1704 }
1705
1706 if (!ret) {
1707 grpmenu.first = 0;
1708
1709 if (*idptr == '\0')
1710 ret = LOOKUP_ART_UNAVAIL;
1711
1712 if ((msgid = find_msgid(idptr)) == NULL)
1713 ret = LOOKUP_ART_UNAVAIL;
1714
1715 if (!ret && msgid->article == ART_UNAVAILABLE)
1716 ret = LOOKUP_ART_UNAVAIL;
1717
1718 if (!ret && which_thread(msgid->article) == -1)
1719 ret = LOOKUP_NO_LAST;
1720 }
1721
1722 if (!ret) {
1723 switch (show_page(group, msgid->article, NULL)) {
1724 case GRP_QUIT:
1725 ret = LOOKUP_QUIT;
1726 break;
1727
1728 default:
1729 break;
1730 }
1731 }
1732
1733 free(newsgroups);
1734 art_close(&pgart);
1735 tinrc.cache_overview_files = tmp_cache_overview_files;
1736 curr_group->attribute->show_only_unread_arts = CAST_BOOL(tmp_show_only_unread_arts);
1737 if (tmp_group) {
1738 curr_group = tmp_group;
1739 if (!index_group(curr_group)) {
1740 for_each_art(i) {
1741 art = &arts[i];
1742 FreeAndNull(art->refs);
1743 FreeAndNull(art->msgid);
1744 }
1745 curr_group = NULL;
1746 tin_errno = 0;
1747 ret = LOOKUP_FAILED;
1748 }
1749 } else
1750 curr_group = NULL;
1751
1752 this_resp = last_resp = -1;
1753
1754 return ret;
1755 }
1756
1757
1758 /*
1759 * Takes a list of newsgroups and determines if one of them is available.
1760 */
1761 static struct t_group *
1762 get_group_from_list(
1763 char *newsgroups)
1764 {
1765 char *ptr, *tr;
1766 t_bool found = FALSE;
1767 struct t_group *group = NULL;
1768
1769 if (!newsgroups || (ptr = strtok(newsgroups, ",")) == NULL)
1770 return NULL;
1771
1772 /* find first available group of type news */
1773 do {
1774 tr = str_trim(ptr);
1775 group = group_find(tr, TRUE);
1776 if (group && group->type == GROUP_TYPE_NEWS)
1777 found = TRUE;
1778 } while (!found && (ptr = strtok(NULL, ",")) != NULL);
1779
1780 return found ? group : NULL;
1781 }
1782 #endif /* NNTP_ABLE */