1 /* -*- mode: C; mode: fold -*- */
2 /*
3  This file is part of SLRN.
4 
5  Copyright (c) 1994, 1999, 2007-2016 John E. Davis <jed@jedsoft.org>
6  Copyright (c) 2001-2006 Thomas Schultz <tststs@gmx.de>
7 
8  This program is free software; you can redistribute it and/or modify it
9  under the terms of the GNU General Public License as published by the Free
10  Software Foundation; either version 2 of the License, or (at your option)
11  any later version.
12 
13  This program is distributed in the hope that it will be useful, but WITHOUT
14  ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
15  FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for
16  more details.
17 
18  You should have received a copy of the GNU General Public License along
19  with this program; if not, write to the Free Software Foundation, Inc.,
20  51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
21 */
22 #include "config.h"
23 #include "slrnfeat.h"
24 
25 /*{{{ Include files */
26 
27 #include <stdio.h>
28 #include <string.h>
29 #include <time.h>
30 #ifndef VMS
31 # include <sys/types.h>
32 # include <sys/stat.h>
33 #else
34 # include "vms.h"
35 #endif
36 
37 #include <signal.h>
38 
39 #ifdef HAVE_STDLIB_H
40 # include <stdlib.h>
41 #endif
42 
43 #ifdef HAVE_UNISTD_H
44 # include <unistd.h>
45 #endif
46 
47 #include <slang.h>
48 #include "jdmacros.h"
49 
50 #include "slrn.h"
51 #include "group.h"
52 #include "art.h"
53 #include "misc.h"
54 #include "post.h"
55 #include "util.h"
56 #include "server.h"
57 #include "hash.h"
58 #include "score.h"
59 #include "menu.h"
60 #include "startup.h"
61 #include "slrndir.h"
62 #include "vfile.h"
63 #include "snprintf.h"
64 #include "hooks.h"
65 #include "common.h"
66 #include "strutil.h"
67 
68 /*}}}*/
69 
70 /*{{{ Global Variables */
71 
72 int Slrn_Query_Group_Cutoff = 1000;
73 int Slrn_Groups_Dirty;	       /* 1 == need to write newsrc */
74 int Slrn_List_Active_File = 0;
75 int Slrn_Write_Newsrc_Flags = 0;       /* if 1, do not save unsubscribed
76 					* if 2, do not save new unsubscribed.
77 					*/
78 
79 int Slrn_Display_Cursor_Bar;
80 char *Slrn_Group_Help_Line;
81 char *Slrn_Group_Status_Line;
82 
83 Slrn_Group_Type *Slrn_Group_Current_Group;
84 static SLscroll_Window_Type Group_Window;
85 static Slrn_Group_Type *Groups;
86 
87 int Slrn_No_Backups = 0;
88 int Slrn_No_Autosave = 0;
89 
90 int Slrn_Unsubscribe_New_Groups = 0;
91 int Slrn_Check_New_Groups = 1;
92 int Slrn_Drop_Bogus_Groups = 1;
93 int Slrn_Max_Queued_Groups = 20;
94 
95 SLKeyMap_List_Type *Slrn_Group_Keymap;
96 int *Slrn_Prefix_Arg_Ptr;
97 
98 /*}}}*/
99 /*{{{ Static Variables */
100 
101 #define GROUP_HASH_TABLE_SIZE 1250
102 static Slrn_Group_Type *Group_Hash_Table [GROUP_HASH_TABLE_SIZE];
103 
104 static unsigned int Last_Cursor_Row;
105 static int Groups_Hidden;	/* if 1, hide groups with no articles;
106 				 * if 2, groups are hidden by user request */
107 static int Kill_After_Max = 1;
108 
109 typedef struct Unsubscribed_Slrn_Group_Type
110 {
111    char *group_name;
112    struct Unsubscribed_Slrn_Group_Type *next;
113 }
114 Unsubscribed_Slrn_Group_Type;
115 
116 static Unsubscribed_Slrn_Group_Type *Unsubscribed_Groups;
117 
118 /*}}}*/
119 
120 /*{{{ Forward Function Declarations */
121 
122 static void group_update_screen (void);
123 static void group_quick_help (void);
124 static void save_newsrc_cmd (void);
125 static void read_and_parse_active (int);
126 static int  parse_active_line (unsigned char *, unsigned int *, int *, int *);
127 static void remove_group_entry (Slrn_Group_Type *);
128 
129 /*}}}*/
130 
131 /*{{{ Functions that deal with Group Range */
132 
count_unread(Slrn_Range_Type * r)133 static NNTP_Artnum_Type count_unread (Slrn_Range_Type *r)
134 {
135    NNTP_Artnum_Type nread = 0;
136    NNTP_Artnum_Type rmax = r->max;
137 
138    while (r->next != NULL)
139      {
140 	r = r->next;
141 	nread += r->max - r->min + 1;
142      }
143    if (nread > rmax)
144      return 0;
145 
146    return rmax - nread;
147 }
148 
slrn_group_recount_unread(Slrn_Group_Type * g)149 void slrn_group_recount_unread (Slrn_Group_Type *g)
150 {
151    /* Make sure old (unavailable) messages are marked read */
152    if (g->range.min>1)
153      g->range.next = slrn_ranges_add (g->range.next, 1, g->range.min-1);
154    g->unread = count_unread (&g->range);
155 }
156 
is_article_requested(Slrn_Group_Type * g,NNTP_Artnum_Type num)157 static int is_article_requested (Slrn_Group_Type *g, NNTP_Artnum_Type num) /*{{{*/
158 {
159 #if SLRN_HAS_SPOOL_SUPPORT
160    if (Slrn_Server_Id != SLRN_SERVER_ID_SPOOL) return 0;
161 
162    if (g->requests_loaded == 0)
163      {
164 	g->requests = slrn_spool_get_requested_ranges (g->group_name);
165 	g->requests_loaded = 1;
166      }
167 
168    return slrn_ranges_is_member (g->requests, num);
169 #else
170    (void) g; (void) num;
171    return 0;
172 #endif
173 }
174 /*}}}*/
175 
group_mark_article_as_read(Slrn_Group_Type * g,NNTP_Artnum_Type num)176 static void group_mark_article_as_read (Slrn_Group_Type *g, NNTP_Artnum_Type num) /*{{{*/
177 {
178    Slrn_Range_Type *r;
179 
180    /* Never mark articles as read if their body has been requested. */
181    if (is_article_requested (g, num)) return;
182 
183    r = &g->range;
184    if (r->max < num)  /* not at server yet so update our data */
185      {
186 	r->max = num;
187 	g->unread += 1;
188      }
189 
190    r = r->next;
191 
192    while (r != NULL)
193      {
194 	/* Already read */
195 	if ((num <= r->max) && (num >= r->min)) return;
196 	if (num < r->min) break;
197 	r = r->next;
198      }
199 
200    if (g->unread != 0) g->unread -= 1;
201    Slrn_Groups_Dirty = 1;
202    g->range.next = slrn_ranges_add (g->range.next, num, num);
203 }
204 
205 /*}}}*/
206 
slrn_mark_articles_as_read(char * group,NNTP_Artnum_Type rmin,NNTP_Artnum_Type rmax)207 void slrn_mark_articles_as_read (char *group,
208 				 NNTP_Artnum_Type rmin, NNTP_Artnum_Type rmax) /*{{{*/
209 {
210    Slrn_Group_Type *g;
211 
212    if (group == NULL)
213      g = Slrn_Group_Current_Group;
214    else
215      {
216 	unsigned long hash = slrn_compute_hash ((unsigned char *) group,
217 						(unsigned char *) group + strlen (group));
218 
219 	g = Group_Hash_Table[hash % GROUP_HASH_TABLE_SIZE];
220 
221 	while (g != NULL)
222 	  {
223 	     if ((g->hash == hash) && !strcmp (group, g->group_name))
224 	       {
225 		  /* If it looks like we have read this group, mark it read. */
226 		  if ((g->flags & GROUP_UNSUBSCRIBED)
227 		      && (g->range.next == NULL))
228 		    return;
229 
230 		  break;
231 	       }
232 	     g = g->hash_next;
233 	  }
234      }
235 
236    if (g == NULL)
237      return;
238 
239    while (rmin <= rmax)
240      {
241 	group_mark_article_as_read (g, rmin);
242 	rmin++;
243      }
244 }
245 
246 /*}}}*/
247 
group_update_range(Slrn_Group_Type * g,NNTP_Artnum_Type min,NNTP_Artnum_Type max)248 static int group_update_range (Slrn_Group_Type *g, NNTP_Artnum_Type min, NNTP_Artnum_Type max) /*{{{*/
249 {
250    NNTP_Artnum_Type n, max_available;
251    Slrn_Range_Type *r;
252 
253    if (max == 0)
254      {
255 	NNTP_Artnum_Type nmax, nmin;
256 
257 	nmax = g->range.max;
258 	nmin = g->range.min;
259 
260 	/* Server database inconsistent. */
261 	if (nmax > 0)
262 	  for (n = nmin; n <= nmax; n++)
263 	    group_mark_article_as_read (g, n);
264 
265 	/* g->unread = 0; */
266 	Slrn_Full_Screen_Update = 1;
267 	Slrn_Groups_Dirty = 1;
268 	return -1;
269      }
270 
271    g->range.min = min;
272 
273    /* This might be due to a cancel -- or to a rescan delay in inn after we
274     * read the active file; if Kill_After_Max is 0, play it safe and do not
275     * mark any articles as read */
276    if (Kill_After_Max && (max < g->range.max))
277      {
278 	NNTP_Artnum_Type nmax = g->range.max;
279 	for (n = max + 1; n <= nmax; n++)
280 	  group_mark_article_as_read (g, n);
281      }
282    else g->range.max = max;
283 
284    /* In case more articles arrived at the server between the time that
285     * slrn was first started and when the server was just queried, update
286     * the ranges of read/unread articles.
287     */
288 
289    max_available = g->range.max - g->range.min + 1;
290 
291    r = &g->range;
292    if (r->next != NULL)
293      {
294 	n = r->max;
295 	while (r->next != NULL)
296 	  {
297 	     r = r->next;
298 	     n -= r->max - r->min + 1;
299 	  }
300 	if (n < 0) n = 0;
301 	if (n > max_available) n = max_available;
302 	g->unread = n;
303 
304 	r = &g->range;
305 	if (r->next->min <= r->min)
306 	  r->next->min = 1;
307      }
308    else
309      g->unread = max_available;
310 
311    if ((g->flags & GROUP_UNSUBSCRIBED) == 0)
312      {
313 	if (g->unread > 0)
314 	  g->flags &= ~GROUP_HIDDEN;
315 	else if ((Groups_Hidden & 1) && (g != Slrn_Group_Current_Group))
316 	  g->flags |= GROUP_HIDDEN;
317      }
318 
319    return 0;
320 }
321 
322 /*}}}*/
323 
group_sync_group_with_server(Slrn_Group_Type * g,NNTP_Artnum_Type * minp,NNTP_Artnum_Type * maxp)324 static int group_sync_group_with_server (Slrn_Group_Type *g, NNTP_Artnum_Type *minp, NNTP_Artnum_Type *maxp) /*{{{*/
325 {
326    char *group;
327    int status, reselect;
328 
329    if (g == NULL) return -1;
330 
331    group = g->group_name;
332 
333    slrn_message_now (_("Selecting %s ..."), group);
334 
335    if (((reselect = Kill_After_Max) == 0) &&
336        (strcmp (group, Slrn_Server_Obj->sv_current_group ())))
337      reselect = 1;
338 
339    status = Slrn_Server_Obj->sv_select_group (group, minp, maxp);
340    if (status == -1)
341      return -1;
342 
343    if (status == ERR_NOGROUP)
344      {
345 	slrn_message_now (_("Group %s is bogus%s."), group,
346 			  Slrn_Drop_Bogus_Groups ? _(" - dropping it") : "");
347 	Slrn_Saw_Warning = 1;
348 	if (Slrn_Drop_Bogus_Groups)
349 	  remove_group_entry (g);
350 	return -1;
351      }
352    else if (status != OK_GROUP)
353      {
354 	slrn_error (_("Could not enter group %s."), group);
355 	return -1;
356      }
357 
358    Kill_After_Max = reselect;
359 
360    return group_update_range (g, *minp, *maxp);
361 }
362 
363 /*}}}*/
364 
free_newsgroup_type(Slrn_Group_Type * g)365 static void free_newsgroup_type (Slrn_Group_Type *g)
366 {
367    if (g == NULL)
368      return;
369    slrn_free (g->descript);
370    slrn_ranges_free (g->range.next);
371    slrn_ranges_free (g->requests);
372    slrn_free (g->group_name);
373    slrn_free ((char *) g);
374 }
375 
free_unsubscribed_group_type(Unsubscribed_Slrn_Group_Type * ug)376 static void free_unsubscribed_group_type (Unsubscribed_Slrn_Group_Type *ug)
377 {
378    if (ug == NULL)
379      return;
380    slrn_free (ug->group_name);
381    slrn_free ((char *) ug);
382 }
383 
slrn_catchup_group(void)384 void slrn_catchup_group (void) /*{{{*/
385 {
386    if ((Slrn_Group_Current_Group == NULL)||
387        (Slrn_Group_Current_Group->unread==0))
388      return;
389 
390    Slrn_Group_Current_Group->range.next =
391      slrn_ranges_add(Slrn_Group_Current_Group->range.next, 1,
392 		     Slrn_Group_Current_Group->range.max);
393    Slrn_Group_Current_Group->unread = 0;
394    Slrn_Groups_Dirty = 1;
395    Slrn_Group_Current_Group->flags |= GROUP_TOUCHED;
396 }
397 
398 /*}}}*/
399 
slrn_uncatchup_group(void)400 void slrn_uncatchup_group (void) /*{{{*/
401 {
402    if (Slrn_Group_Current_Group == NULL)
403      return;
404    slrn_ranges_free (Slrn_Group_Current_Group->range.next);
405    Slrn_Group_Current_Group->range.next = NULL;
406    slrn_group_recount_unread (Slrn_Group_Current_Group);
407    Slrn_Groups_Dirty = 1;
408    Slrn_Group_Current_Group->flags |= GROUP_TOUCHED;
409 }
410 
411 /*}}}*/
412 
413 /*}}}*/
414 
415 /*{{{ Misc Utility Functions */
find_line_num(void)416 static void find_line_num (void) /*{{{*/
417 {
418    Group_Window.lines = (SLscroll_Type *) Groups;
419    Group_Window.current_line = (SLscroll_Type *) Slrn_Group_Current_Group;
420    (void) SLscroll_find_line_num (&Group_Window);
421 }
422 
423 /*}}}*/
424 
find_group_entry(char * name,unsigned int len)425 static Slrn_Group_Type *find_group_entry (char *name, unsigned int len) /*{{{*/
426 {
427    int hash_index;
428    unsigned long hash;
429    Slrn_Group_Type *g;
430 
431    hash = slrn_compute_hash ((unsigned char *) name,
432 			     (unsigned char *) name + len);
433 
434    hash_index = hash % GROUP_HASH_TABLE_SIZE;
435    g = Group_Hash_Table[hash_index];
436 
437    while (g != NULL)
438      {
439 	if ((g->hash == hash) && !strncmp (name, g->group_name, len))
440 	  {
441 	     if (len == strlen (g->group_name)) break;
442 	  }
443 	g = g->hash_next;
444      }
445    return g;
446 }
447 
448 /*}}}*/
create_group_entry(char * name,unsigned int len,NNTP_Artnum_Type min,NNTP_Artnum_Type max,int query_server,int skip_find)449 static Slrn_Group_Type *create_group_entry (char *name, unsigned int len, /*{{{*/
450 					    NNTP_Artnum_Type min, NNTP_Artnum_Type max, int query_server,
451 					    int skip_find)
452 {
453    int hash_index;
454    unsigned long hash;
455    Slrn_Group_Type *g;
456 
457    if (skip_find == 0)
458      {
459 	g = find_group_entry (name, len);
460 	if (g != NULL) return g;
461      }
462 
463    g = (Slrn_Group_Type *) slrn_safe_malloc (sizeof (Slrn_Group_Type));
464    g->requests = NULL;
465    g->requests_loaded = 0;
466 
467    g->group_name = slrn_safe_malloc (len + 1);
468    strncpy (g->group_name, name, len);
469    g->group_name [len] = 0;
470 
471    if (query_server)
472      {
473 	int status;
474 
475 	status = Slrn_Server_Obj->sv_select_group (g->group_name, &min, &max);
476 	if (status == ERR_NOGROUP)
477 	  {
478 	     slrn_message_now (_("Group %s is bogus%s."), g->group_name,
479 			 Slrn_Drop_Bogus_Groups ? _(" - ignoring it") : "");
480 	     if (Slrn_Drop_Bogus_Groups)
481 	       {
482 		  Slrn_Groups_Dirty = 1;
483 		  free_newsgroup_type (g);
484 		  return NULL;
485 	       }
486 	  }
487      }
488    g->range.min = min;
489    g->range.max = max;
490    if (max > 0)
491      g->unread = max - min + 1;
492    else g->unread = 0;
493 
494    g->flags = (GROUP_UNSUBSCRIBED | GROUP_HIDDEN);
495 
496    hash = slrn_compute_hash ((unsigned char *) name,
497 			     (unsigned char *) name + len);
498    hash_index = hash % GROUP_HASH_TABLE_SIZE;
499 
500    g->hash = hash;
501    g->hash_next = Group_Hash_Table[hash_index];
502    Group_Hash_Table[hash_index] = g;
503 
504    if (Groups == NULL)
505      {
506 	Slrn_Group_Current_Group = Groups = g;
507      }
508    else
509      {
510 	if (Slrn_Group_Current_Group == NULL) /* insert on top */
511 	  {
512 	     g->prev = NULL;
513 	     g->next = Groups;
514 	     Groups->prev = g;
515 	     Groups = g;
516 	  }
517 	else /* insert after current group */
518 	  {
519 	     g->next = Slrn_Group_Current_Group->next;
520 	     if (g->next != NULL) g->next->prev = g;
521 	     Slrn_Group_Current_Group->next = g;
522 	     g->prev = Slrn_Group_Current_Group;
523 	  }
524      }
525    Slrn_Group_Current_Group = g;
526    return g;
527 }
528 
529 /*}}}*/
remove_group_entry(Slrn_Group_Type * g)530 static void remove_group_entry (Slrn_Group_Type *g) /*{{{*/
531 {
532    Slrn_Group_Type *tmp;
533    if (g == Groups)
534      Groups = g->next;
535    if (g == Slrn_Group_Current_Group)
536      Slrn_Group_Current_Group = g->next != NULL ? g->next : g->prev;
537    if (g->prev != NULL)
538      g->prev->next = g->next;
539    if (g->next != NULL)
540      g->next->prev = g->prev;
541    if (g == (tmp = Group_Hash_Table[g->hash % GROUP_HASH_TABLE_SIZE]))
542      Group_Hash_Table[g->hash % GROUP_HASH_TABLE_SIZE] = g->hash_next;
543    else while (tmp != NULL)
544      {
545 	if (g == tmp->hash_next)
546 	  {
547 	     tmp->hash_next = g->hash_next;
548 	     break;
549 	  }
550 	tmp = tmp->hash_next;
551      }
552    free_newsgroup_type (g);
553    find_line_num ();
554    Slrn_Groups_Dirty = 1;
555 }
556 /*}}}*/
add_group(char * name,unsigned int len,unsigned int subscribe_flag,int query_server,int create_flag)557 static int add_group (char *name, unsigned int len, /*{{{*/
558 		      unsigned int subscribe_flag, int query_server,
559 		      int create_flag)
560 {
561    Slrn_Group_Type *g;
562 
563    g = find_group_entry (name, len);
564    if (g == NULL)
565      {
566 	if (Slrn_List_Active_File && Slrn_Drop_Bogus_Groups)
567 	  {
568 	     char *tmp_name;
569 
570 	     if (NULL != (tmp_name = slrn_strnmalloc (name, len, 1)))
571 	       {
572 		  slrn_message_now (_("Group %s is bogus - ignoring it."), tmp_name);
573 		  Slrn_Saw_Warning = 1;
574 		  slrn_free (tmp_name);
575 	       }
576 	     return -1;
577 	  }
578 	else g = create_group_entry (name, len, -1, -1, query_server &&
579 				     !(subscribe_flag & GROUP_UNSUBSCRIBED),
580 				     0);
581 	if (g == NULL) return -1;
582      }
583    Slrn_Groups_Dirty = 1;
584 
585    /* If we have already processed this, then the group is duplicated in
586     * the newsrc file.  Throw it out now.
587     */
588    if (g->flags & GROUP_PROCESSED) return -1;
589 
590    Slrn_Group_Current_Group = g;
591    g->flags = subscribe_flag;
592    g->flags |= GROUP_PROCESSED;
593 
594    if (subscribe_flag & GROUP_UNSUBSCRIBED)
595      {
596 	g->flags |= GROUP_HIDDEN;
597 
598 	if (create_flag) return 0;
599 	g->unread = 0;
600 	/* if (Slrn_List_Active_File == 0) return 0; */
601      }
602 
603    if (create_flag) return 0;
604 
605    /* find ranges for this */
606    name += len;			       /* skip past name */
607    if (*name) name++;			       /* skip colon */
608    g->range.next = slrn_ranges_from_newsrc_line (name);
609    slrn_group_recount_unread (g);
610 
611    if ((g->range.next != NULL)
612        && (g->range.next->min < g->unread)
613        && (g->range.next->min > 1))
614      g->unread -= g->range.next->min - 1;
615 
616    return 0;
617 }
618 
619 /*}}}*/
620 
621 /* Rearrange Slrn_Group_Current_Group such that it follows last_group and
622  * return Slrn_Group_Current_Group.  If last_group is NULL, Slrn_Group_Current_Group
623  * should be put at top of list.
624  */
place_group_in_newsrc_order(Slrn_Group_Type * last_group)625 static Slrn_Group_Type *place_group_in_newsrc_order (Slrn_Group_Type *last_group) /*{{{*/
626 {
627    Slrn_Group_Type *next_group, *prev_group;
628 
629    next_group = Slrn_Group_Current_Group->next;
630    prev_group = Slrn_Group_Current_Group->prev;
631    if (next_group != NULL) next_group->prev = prev_group;
632    if (prev_group != NULL) prev_group->next = next_group;
633 
634    Slrn_Group_Current_Group->prev = last_group;
635    if (last_group != NULL)
636      {
637 	Slrn_Group_Current_Group->next = last_group->next;
638 
639 	if (Slrn_Group_Current_Group->next != NULL)
640 	  Slrn_Group_Current_Group->next->prev = Slrn_Group_Current_Group;
641 
642 	last_group->next = Slrn_Group_Current_Group;
643      }
644    else if (Slrn_Group_Current_Group != Groups)
645      {
646 	Slrn_Group_Current_Group->next = Groups;
647 	if (Groups != NULL) Groups->prev = Slrn_Group_Current_Group;
648 	Groups = Slrn_Group_Current_Group;
649      }
650    else
651      {
652 	/* correct next_group->prev since it was not set correctly above */
653 	if (next_group != NULL)
654 	  next_group->prev = Slrn_Group_Current_Group;
655      }
656 
657    return Slrn_Group_Current_Group;
658 }
659 
660 /*}}}*/
insert_new_groups(void)661 static void insert_new_groups (void) /*{{{*/
662 {
663    Slrn_Group_Type *last_group = Groups;
664 
665    while (last_group != NULL)
666      {
667 	/* unmark new groups from previous run */
668 	last_group->flags &= ~GROUP_NEW_GROUP_FLAG;
669 	last_group = last_group->next;
670      }
671 
672    if (Unsubscribed_Groups != NULL)
673      {
674 	unsigned int subscribe_flag;
675 	Unsubscribed_Slrn_Group_Type *ug = Unsubscribed_Groups, *ugnext;
676 
677 	if (Slrn_Unsubscribe_New_Groups)
678 	  subscribe_flag = GROUP_UNSUBSCRIBED | GROUP_NEW_GROUP_FLAG;
679 	else subscribe_flag = GROUP_NEW_GROUP_FLAG;
680 
681 	while (ug != NULL)
682 	  {
683 	     ugnext = ug->next;
684 
685 	     if (-1 != add_group (ug->group_name, strlen (ug->group_name), subscribe_flag, 0, 0))
686 	       last_group = place_group_in_newsrc_order (last_group);
687 
688 	     free_unsubscribed_group_type (ug);
689 	     ug = ugnext;
690 	  }
691 	Unsubscribed_Groups = NULL;
692      }
693 }
694 
695 /*}}}*/
696 
init_group_win_struct(void)697 static void init_group_win_struct (void) /*{{{*/
698 {
699    Group_Window.nrows = SLtt_Screen_Rows - 3;
700    Group_Window.hidden_mask = GROUP_HIDDEN;
701    Group_Window.current_line = (SLscroll_Type *) Slrn_Group_Current_Group;
702    Group_Window.cannot_scroll = SLtt_Term_Cannot_Scroll;
703    Group_Window.lines = (SLscroll_Type *) Groups;
704    Group_Window.border = 1;
705    if (Slrn_Scroll_By_Page)
706      {
707 	/* Slrn_Group_Window.border = 0; */
708 	Group_Window.cannot_scroll = 2;
709      }
710    find_line_num ();
711 }
712 
713 /*}}}*/
714 
find_group(char * name)715 static int find_group (char *name) /*{{{*/
716 {
717    Slrn_Group_Type *g = find_group_entry (name, strlen (name));
718    if (g == NULL) return 0;
719 
720    g->flags &= ~GROUP_HIDDEN;
721    Slrn_Group_Current_Group = g;
722    find_line_num ();
723    return 1;
724 }
725 
726 /*}}}*/
727 
728 /* origpat needs enough space for SLRL_DISPLAY_BUFFER_SIZE chars */
read_group_regexp(char * prompt,char * origpat)729 static SLRegexp_Type *read_group_regexp (char *prompt, char *origpat) /*{{{*/
730 {
731    static char pattern[SLRL_DISPLAY_BUFFER_SIZE];
732 
733    if (slrn_read_input (prompt, NULL, pattern, 1, 0) <= 0) return NULL;
734 
735    if (origpat != NULL)
736      strcpy (origpat, pattern); /* safe */
737 
738    return slrn_compile_regexp_pattern (slrn_fix_regexp (pattern));
739 }
740 
741 /*}}}*/
742 
add_unsubscribed_group(unsigned char * name)743 static void add_unsubscribed_group (unsigned char *name) /*{{{*/
744 {
745    Unsubscribed_Slrn_Group_Type *g;
746    unsigned char *p;
747 
748    g = (Unsubscribed_Slrn_Group_Type *) slrn_safe_malloc (sizeof (Unsubscribed_Slrn_Group_Type));
749 
750    g->next = Unsubscribed_Groups;
751    Unsubscribed_Groups = g;
752 
753    p = name;
754    while (*p > ' ') p++;
755    *p = 0;
756 
757    g->group_name = slrn_safe_strmalloc ((char*)name);
758 }
759 
760 /*}}}*/
761 
762 /*}}}*/
763 
764 static char *Group_Display_Formats [SLRN_MAX_DISPLAY_FORMATS];
765 static unsigned int Group_Format_Number;
766 
slrn_set_group_format(unsigned int num,char * fmt)767 int slrn_set_group_format (unsigned int num, char *fmt)
768 {
769    return slrn_set_display_format (Group_Display_Formats, num, fmt);
770 }
771 
toggle_group_formats(void)772 static void toggle_group_formats (void)
773 {
774    Group_Format_Number = slrn_toggle_format (Group_Display_Formats,
775 					     Group_Format_Number);
776 }
777 
slrn_group_search(char * str,int dir)778 int slrn_group_search (char *str, int dir) /*{{{*/
779 {
780 #if SLANG_VERSION < 20000
781    SLsearch_Type st;
782 #else
783    SLsearch_Type *st = NULL;
784    unsigned int flags;
785 #endif
786    Slrn_Group_Type *g;
787    int found = 0;
788 
789    g = Slrn_Group_Current_Group;
790    if (g == NULL) return 0;
791 
792 #if SLANG_VERSION < 20000
793    SLsearch_init (str, 1, 0, &st);
794 #else
795    flags = SLSEARCH_CASELESS;
796    if (Slrn_UTF8_Mode)
797      flags |= SLSEARCH_UTF8;
798 
799    st = SLsearch_new ((SLuchar_Type *) str, flags);
800    if (st == NULL)
801      return 0;
802 #endif
803 
804    do
805      {
806 	if (dir > 0)
807 	  g = g->next;
808 	else
809 	  g = g->prev;
810 	if (g == NULL)
811 	  {
812 	     g = Groups;
813 	     if (dir < 0)
814 	       while (g->next != NULL)
815 		 g = g->next;
816 	  }
817 
818 	if ((g->flags & GROUP_HIDDEN) == 0)
819 	  {
820 #if SLANG_VERSION < 20000
821 	     if ((NULL != SLsearch ((unsigned char *) g->group_name,
822 				    (unsigned char *) g->group_name + strlen (g->group_name),
823 				   &st))
824 		 || ((NULL != g->descript)
825 		     && (NULL != SLsearch ((unsigned char *) g->descript,
826 					   (unsigned char *) g->descript + strlen (g->descript),
827 					   &st))))
828 	       {
829 		  found = 1;
830 		  break;
831 	       }
832 #else
833 	     if ((NULL != SLsearch_forward (st, (unsigned char *) g->group_name,
834 					   (unsigned char *) g->group_name + strlen (g->group_name)))
835 		 || ((NULL != g->descript)
836 		     && (NULL != SLsearch_forward (st, (unsigned char *) g->descript,
837 						  (unsigned char *) g->descript + strlen (g->descript)))))
838 	       {
839 		  found = 1;
840 		  break;
841 	       }
842 #endif
843 	  }
844      }
845    while (g != Slrn_Group_Current_Group);
846 
847 #if SLANG_VERSION >= 20000
848    SLsearch_delete (st);
849 #endif
850 
851    Slrn_Group_Current_Group = g;
852    find_line_num ();
853    return found;
854 }
855 
856 /*}}}*/
slrn_group_up_n(unsigned int n)857 unsigned int slrn_group_up_n (unsigned int n) /*{{{*/
858 {
859    n = SLscroll_prev_n (&Group_Window, n);
860    Slrn_Group_Current_Group = (Slrn_Group_Type *) Group_Window.current_line;
861    return n;
862 }
863 
864 /*}}}*/
slrn_group_down_n(unsigned int n)865 unsigned int slrn_group_down_n (unsigned int n) /*{{{*/
866 {
867    n = SLscroll_next_n (&Group_Window, n);
868    Slrn_Group_Current_Group = (Slrn_Group_Type *) Group_Window.current_line;
869    return n;
870 }
871 
872 /*}}}*/
873 
slrn_group_select_group(void)874 int slrn_group_select_group (void) /*{{{*/
875 {
876    NNTP_Artnum_Type min, max, n, max_available, last_n;
877    int ret;
878    Slrn_Range_Type *r;
879    int prefix;
880 
881    if (Slrn_Prefix_Arg_Ptr != NULL)
882      {
883 	prefix = *Slrn_Prefix_Arg_Ptr;
884 	Slrn_Prefix_Arg_Ptr = NULL;
885      }
886    else prefix = 0;
887 
888    if (Slrn_Group_Current_Group == NULL) return -1;
889 
890    last_n = Slrn_Group_Current_Group->unread;
891 
892    if (-1 == group_sync_group_with_server (Slrn_Group_Current_Group, &min, &max))
893      {
894 	slrn_message (_("No articles to read."));
895 	return -1;
896      }
897 
898    n = Slrn_Group_Current_Group->unread;
899 
900 #if 1
901    if ((prefix == 0) && (n == 0) && (n != last_n))
902      return -1;
903 #endif
904 
905    max_available = Slrn_Group_Current_Group->range.max - Slrn_Group_Current_Group->range.min + 1;
906 
907    if ((prefix != 0) || (n == 0))
908      n = max_available;
909 
910    if ((prefix & 1)
911        || ((Slrn_Query_Group_Cutoff > 0)
912 	   && (n > (NNTP_Artnum_Type)Slrn_Query_Group_Cutoff))
913        || ((Slrn_Query_Group_Cutoff < 0)
914 	   && (n > (NNTP_Artnum_Type)(-Slrn_Query_Group_Cutoff))))
915      {
916 	char int_prompt_buf[512];
917 	if ((prefix & 1) || (Slrn_Query_Group_Cutoff > 0))
918 	  {
919 	     slrn_snprintf (int_prompt_buf, sizeof (int_prompt_buf),
920 			    _("%s: Read how many? "),
921 			    Slrn_Group_Current_Group->group_name);
922 	     if ((-1 == slrn_read_artnum_int (int_prompt_buf, &n, &n))
923 		 || (n <= 0))
924 	       {
925 		  slrn_clear_message ();
926 		  Slrn_Full_Screen_Update = 1;
927 		  return 0;
928 	       }
929 	  }
930 	else
931 	  {
932 	     slrn_message_now (_("Only downloading %d of " NNTP_FMT_ARTNUM " articles."),
933 			       -Slrn_Query_Group_Cutoff, n);
934 	     n = -Slrn_Query_Group_Cutoff;
935 	  }
936 
937 	if ((0 == prefix)
938 	    && (Slrn_Group_Current_Group->unread != 0))
939 	  {
940 	     r = Slrn_Group_Current_Group->range.next;
941 	     if (r != NULL)
942 	       {
943 		  while (r->next != NULL) r = r->next;
944 		  if (r->max + n > max)
945 		    n = -n;	       /* special treatment in article mode
946 					* because we will need to query the
947 					* server about articles in a group
948 					* that we have already read.
949 					*/
950 	       }
951 	  }
952      }
953    else if ((0 == prefix) && (Slrn_Group_Current_Group->unread != 0))
954      n = 0;
955 
956    ret = slrn_select_article_mode (Slrn_Group_Current_Group, n,
957 				   ((prefix & 2) == 0));
958 
959    if (ret == -2)
960      slrn_catchup_group ();
961 
962    return ret;
963 }
964 
965 /*}}}*/
slrn_select_next_group(void)966 void slrn_select_next_group (void) /*{{{*/
967 {
968    if (Slrn_Group_Current_Group == NULL)
969      return;
970 
971    while ((SLang_get_error () == 0) && (1 == slrn_group_down_n (1)))
972      {
973 	if (Slrn_Group_Current_Group->unread == 0)
974 	  continue;
975 
976 	if (0 == slrn_group_select_group ())
977 	  break;
978 	else if (SLang_get_error () == INTRINSIC_ERROR)
979 	  /* all articles killed by scorefile, so proceed */
980 	  {
981 	     SLang_set_error (0);
982 	     slrn_clear_message ();
983 	  }
984      }
985 }
986 
987 /*}}}*/
slrn_select_prev_group(void)988 void slrn_select_prev_group (void) /*{{{*/
989 {
990    if (Slrn_Group_Current_Group == NULL)
991      return;
992 
993    while ((SLang_get_error () == 0) && (1 == slrn_group_up_n (1)))
994      {
995        if (Slrn_Group_Current_Group->unread == 0)
996          continue;
997 
998        if (0 == slrn_group_select_group ())
999          break;
1000      }
1001 }
1002 
1003 /*}}}*/
1004 
1005 /*{{{ Interactive commands */
1006 
slrn_group_quit(void)1007 void slrn_group_quit (void) /*{{{*/
1008 {
1009    if ((Slrn_User_Wants_Confirmation & SLRN_CONFIRM_QUIT)
1010        && (Slrn_Batch == 0)
1011        && (slrn_get_yesno (1, _("Do you really want to quit")) <= 0)) return;
1012 
1013    if ((Slrn_Groups_Dirty) && (-1 == slrn_write_newsrc (0)))
1014      {
1015 	if (Slrn_Batch)
1016 	  slrn_quit (1);
1017 
1018 	slrn_smg_refresh ();
1019 	if (Slrn_Batch == 0) slrn_sleep (2);
1020 	if (slrn_get_yesno (0, _("Write to newsrc file failed.  Quit anyway")) <= 0)
1021 	  return;
1022      }
1023    slrn_quit (0);
1024 }
1025 
1026 /*}}}*/
1027 
group_pagedown(void)1028 static void group_pagedown (void) /*{{{*/
1029 {
1030    Slrn_Full_Screen_Update = 1;
1031 
1032    if (-1 == SLscroll_pagedown (&Group_Window))
1033      slrn_error (_("End of Buffer."));
1034    Slrn_Group_Current_Group = (Slrn_Group_Type *) Group_Window.current_line;
1035 }
1036 
1037 /*}}}*/
1038 
group_pageup(void)1039 static void group_pageup (void) /*{{{*/
1040 {
1041    Slrn_Full_Screen_Update = 1;
1042 
1043    if (-1 == SLscroll_pageup (&Group_Window))
1044      slrn_error (_("Top of Buffer."));
1045 
1046    Slrn_Group_Current_Group = (Slrn_Group_Type *) Group_Window.current_line;
1047 }
1048 
1049 /*}}}*/
1050 
group_up(void)1051 static void group_up (void) /*{{{*/
1052 {
1053    if (0 == slrn_group_up_n (1))
1054      {
1055 	slrn_error (_("Top of buffer."));
1056      }
1057 }
1058 
1059 /*}}}*/
1060 
set_current_group(void)1061 static void set_current_group (void) /*{{{*/
1062 {
1063    Slrn_Group_Type *g;
1064 
1065    g = Slrn_Group_Current_Group;
1066    if (g == NULL) g = Groups;
1067 
1068    while ((g != NULL) && (g->flags & GROUP_HIDDEN)) g = g->next;
1069    if ((g == NULL) && (Slrn_Group_Current_Group != NULL))
1070      {
1071 	g = Slrn_Group_Current_Group -> prev;
1072 	while ((g != NULL) && (g->flags & GROUP_HIDDEN)) g = g->prev;
1073      }
1074    Slrn_Group_Current_Group = g;
1075 
1076    /* When there are less than SCREEN_HEIGHT-2 groups, they should all get
1077     * displayed; scroll to the top of the buffer to ensure this. */
1078    while (SLscroll_prev_n (&Group_Window, 1000));
1079    (void) SLscroll_find_top (&Group_Window);
1080    find_line_num ();
1081 
1082    Slrn_Full_Screen_Update = 1;
1083 }
1084 
1085 /*}}}*/
1086 
refresh_groups(Slrn_Group_Type ** c)1087 static void refresh_groups (Slrn_Group_Type **c) /*{{{*/
1088 {
1089    Slrn_Group_Type *g = Groups;
1090    Slrn_Group_Range_Type *ranges;
1091 
1092    if (Slrn_Max_Queued_Groups <= 0)
1093      Slrn_Max_Queued_Groups = 1;
1094    ranges = (Slrn_Group_Range_Type *) slrn_safe_malloc
1095      (Slrn_Max_Queued_Groups * sizeof(Slrn_Group_Range_Type));
1096 
1097    while (g != NULL)
1098      {
1099 	Slrn_Group_Type *start = g;
1100 	int i = 0, j = 0;
1101 
1102 	while ((g != NULL) && (i < Slrn_Max_Queued_Groups))
1103 	  {
1104 	     if (!(g->flags & GROUP_UNSUBSCRIBED))
1105 	       {
1106 		  ranges[i].name = g->group_name;
1107 		  i++;
1108 	       }
1109 	     g = g->next;
1110 	  }
1111 	if (Slrn_Server_Obj->sv_refresh_groups (ranges, i))
1112 	  {
1113 	     slrn_error (_("Server connection dropped."));
1114 	     goto free_and_return;
1115 	  }
1116 	g = start;
1117 	while (j < i)
1118 	  {
1119 	     if (g->flags & GROUP_UNSUBSCRIBED)
1120 	       {
1121 		  g = g->next;
1122 		  continue;
1123 	       }
1124 	     if (ranges[j].min == -1)
1125 	       {
1126 		  slrn_message_now (_("Group %s is bogus%s."), g->group_name,
1127 			      Slrn_Drop_Bogus_Groups ? _(" - dropping it") : "");
1128 		  Slrn_Saw_Warning = 1;
1129 		  if (Slrn_Drop_Bogus_Groups)
1130 		    {
1131 		       Slrn_Group_Type *tmp = g->next;
1132 		       remove_group_entry (g);
1133 		       if (g == *c) *c = Groups;
1134 		       g = tmp;
1135 		       j++;
1136 		       continue;
1137 		    }
1138 	       }
1139 	     else
1140 	       group_update_range (g, ranges[j].min, ranges[j].max);
1141 	     g = g->next;
1142 	     j++;
1143 	  }
1144 	while ((g != NULL) && (g->flags & GROUP_UNSUBSCRIBED))
1145 	  g = g->next;
1146      }
1147    free_and_return:
1148    SLfree ((char*) ranges);
1149 }
1150 /*}}}*/
1151 
refresh_groups_cmd(void)1152 static void refresh_groups_cmd (void) /*{{{*/
1153 {
1154    Slrn_Group_Type *c = Slrn_Group_Current_Group;
1155    Slrn_Group_Current_Group = NULL;
1156 
1157    slrn_message_now (_("Checking news%s ..."),
1158 		     Slrn_List_Active_File ? _(" via active file") : "");
1159 
1160    if (Slrn_List_Active_File)
1161      {
1162 	read_and_parse_active (0);
1163 	if ((Slrn_Server_Obj->sv_id == SERVER_ID_INN) && (c != NULL))
1164 	  /* hack: avoid a problem with inn not updating the high water mark */
1165 	  {
1166 	     Slrn_Group_Type *a = c, *b;
1167 	     NNTP_Artnum_Type min, max;
1168 
1169 	     if (((NULL != (b = c->next)) || (NULL != (b = c->prev))) &&
1170 		 (0 == strcmp (c->group_name,
1171 			       Slrn_Server_Obj->sv_current_group ())))
1172 	       {
1173 		  a = b;
1174 	       }
1175 
1176 	     (void) group_sync_group_with_server (a, &min, &max);
1177 	  }
1178      }
1179 
1180    if (Slrn_Check_New_Groups)
1181      {
1182 	slrn_get_new_groups (0);
1183 	insert_new_groups ();
1184      }
1185 
1186    if (!Slrn_List_Active_File)
1187      {
1188 	refresh_groups (&c);
1189      }
1190 
1191    slrn_read_group_descriptions ();
1192 
1193    Slrn_Group_Current_Group = c;
1194    set_current_group ();
1195    group_quick_help ();
1196 }
1197 
1198 /*}}}*/
1199 
generic_group_search(int dir)1200 static void generic_group_search (int dir) /*{{{*/
1201 {
1202    static char search_str[SLRL_DISPLAY_BUFFER_SIZE];
1203    char* prompt;
1204    Slrn_Group_Type *g;
1205    unsigned int n;
1206    int ret;
1207 
1208    g = Slrn_Group_Current_Group;
1209    if (g == NULL) return;
1210 
1211    prompt = slrn_strdup_strcat ((dir > 0 ? _("Forward") : _("Backward")),
1212 				_(" Search: "), NULL);
1213    ret = slrn_read_input (prompt, search_str, NULL, 1, 0);
1214    slrn_free (prompt);
1215    if (ret <= 0) return;
1216 
1217    n = Group_Window.line_num;
1218    if (0 == slrn_group_search (search_str, dir))
1219      {
1220         slrn_error (_("Not found."));
1221 	return;
1222      }
1223 
1224    if (((dir > 0) && (n > Group_Window.line_num)) ||
1225        ((dir < 0) && (n < Group_Window.line_num)))
1226      slrn_message (_("Search wrapped."));
1227 }
1228 
1229 /*}}}*/
1230 
group_search_forward(void)1231 static void group_search_forward (void) /*{{{*/
1232 {
1233    generic_group_search (1);
1234 }
1235 /*}}}*/
1236 
group_search_backward(void)1237 static void group_search_backward (void) /*{{{*/
1238 {
1239    generic_group_search (-1);
1240 }
1241 /*}}}*/
1242 
slrn_add_group(char * group)1243 int slrn_add_group (char *group) /*{{{*/
1244 {
1245    int retval = 0;
1246    if (!find_group (group))
1247      {
1248 	if (Slrn_List_Active_File == 0)
1249 	  {
1250 	     retval = add_group (group, strlen (group), 0, 1, 0);
1251 	     slrn_read_group_descriptions ();
1252 	  }
1253 	else
1254 	  {
1255 	     slrn_error (_("Group %s does not exist."), group);
1256 	     retval = -1;
1257 	  }
1258      }
1259    Slrn_Groups_Dirty = 1;
1260    Slrn_Full_Screen_Update = 1;
1261    find_line_num ();
1262    return retval;
1263 }
1264 /*}}}*/
1265 
add_group_cmd(void)1266 static void add_group_cmd (void) /*{{{*/
1267 {
1268    char group[SLRL_DISPLAY_BUFFER_SIZE];
1269 
1270    *group = 0;
1271    if (slrn_read_input (_("Add group: "), NULL, group, 1, 0) > 0)
1272      (void) slrn_add_group (group);
1273 }
1274 
1275 /*}}}*/
1276 
group_down(void)1277 static void group_down (void) /*{{{*/
1278 {
1279    if (1 != slrn_group_down_n (1))
1280      {
1281 	slrn_error (_("End of Buffer."));
1282      }
1283 }
1284 
1285 /*}}}*/
1286 
transpose_groups(void)1287 static void transpose_groups (void) /*{{{*/
1288 {
1289    Slrn_Group_Type *g, *g1, *tmp;
1290 
1291    if (NULL == (g = Slrn_Group_Current_Group))
1292      return;
1293 
1294    if (1 != slrn_group_up_n (1))
1295      return;
1296 
1297    g1 = Slrn_Group_Current_Group;
1298    tmp = g1->next;
1299 
1300    g1->next = g->next;
1301    if (g1->next != NULL) g1->next->prev = g1;
1302    g->next = tmp;
1303    tmp->prev = g;		       /* tmp cannot be NULL but it can be
1304 					* equal to g.  This link is corrected
1305 					* below
1306 					*/
1307 
1308    tmp = g1->prev;
1309    g1->prev = g->prev;
1310    g1->prev->next = g1;		       /* g1->prev cannot be NULL */
1311    g->prev = tmp;
1312    if (tmp != NULL) tmp->next = g;
1313 
1314    if (g1 == Groups) Groups = g;
1315 
1316    find_line_num ();
1317 
1318    (void) slrn_group_down_n (1);
1319 
1320    Slrn_Full_Screen_Update = 1;
1321    Slrn_Groups_Dirty = 1;
1322 }
1323 
1324 /*}}}*/
1325 
move_group_cmd(void)1326 static void move_group_cmd (void) /*{{{*/
1327 {
1328    SLang_Key_Type *key;
1329    void (*f)(void);
1330    Slrn_Group_Type *from, *to;
1331 
1332    if (Slrn_Batch) return;
1333    if (Slrn_Group_Current_Group == NULL) return;
1334    from = Slrn_Group_Current_Group;
1335 
1336    /* Already centering the window here should avoid confusing the user */
1337    Group_Window.top_window_line = NULL;
1338    slrn_update_screen ();
1339 
1340    while (1)
1341      {
1342 	slrn_message_now (_("Moving %s. Press RETURN when finished."), Slrn_Group_Current_Group->group_name);
1343 
1344 	/* key = SLang_do_key (Slrn_Group_Keymap, (int (*)(void)) SLang_getkey); */
1345 	key = SLang_do_key (Slrn_Group_Keymap, slrn_getkey);
1346 
1347 	if ((key == NULL)
1348 	    || (key->type == SLKEY_F_INTERPRET))
1349 	  f = NULL;
1350 	else f = (void (*)(void)) key->f.f;
1351 
1352 	if ((f == group_up) || (f == group_down))
1353 	  {
1354 	     if (f == group_down)
1355 	       (void) slrn_group_down_n (1);
1356 	     else
1357 	       (void) slrn_group_up_n (1);
1358 
1359 	     to = Slrn_Group_Current_Group;
1360 	     if (from == to) break;
1361 
1362 	     Slrn_Full_Screen_Update = 1;
1363 	     Slrn_Groups_Dirty = 1;
1364 
1365 	     if (NULL != from->next)
1366 	       from->next->prev = from->prev;
1367 	     if (NULL != from->prev)
1368 	       from->prev->next = from->next;
1369 
1370 	     if (f == group_down)
1371 	       {
1372 		  if (NULL != to->next)
1373 		    to->next->prev = from;
1374 		  from->next = to->next;
1375 		  from->prev = to;
1376 		  to->next = from;
1377 		  if (from == Groups) Groups = to;
1378 	       }
1379 	     else
1380 	       {
1381 		  if (NULL != to->prev)
1382 		    to->prev->next = from;
1383 		  from->prev = to->prev;
1384 		  from->next = to;
1385 		  to->prev = from;
1386 		  if (to == Groups) Groups = from;
1387 	       }
1388 	  }
1389 	else break;
1390 
1391 	if (from != Slrn_Group_Current_Group)
1392 	  {
1393 	     Slrn_Group_Current_Group = from;
1394 	     find_line_num ();
1395 	  }
1396 
1397 	/* For a recenter if possible. */
1398 	/* if (Group_Window.top_window_line == Group_Window.current_line) */
1399 	Group_Window.top_window_line = NULL;
1400 
1401 	slrn_update_screen ();
1402      }
1403 }
1404 
1405 /*}}}*/
1406 
subscribe(void)1407 static void subscribe (void) /*{{{*/
1408 {
1409    SLRegexp_Type *re;
1410    Slrn_Group_Type *g;
1411 
1412    if (Slrn_Prefix_Arg_Ptr == NULL)
1413      {
1414 	if (Slrn_Group_Current_Group != NULL)
1415 	  {
1416 	     Slrn_Group_Current_Group->flags &= ~GROUP_UNSUBSCRIBED;
1417 	     Slrn_Group_Current_Group->flags |= GROUP_TOUCHED;
1418 	     slrn_group_down_n (1);
1419 	     Slrn_Groups_Dirty = 1;
1420 	  }
1421 	return;
1422      }
1423 
1424    Slrn_Prefix_Arg_Ptr = NULL;
1425 
1426    if (NULL == (re = read_group_regexp (_("Subscribe pattern: "), NULL)))
1427      return;
1428 
1429    g = Groups;
1430    while (g != NULL)
1431      {
1432 	if (g->flags & GROUP_UNSUBSCRIBED)
1433 	  {
1434 	     if (NULL != slrn_regexp_match (re, g->group_name))
1435 	       {
1436 		  g->flags &= ~GROUP_HIDDEN;
1437 		  g->flags &= ~GROUP_UNSUBSCRIBED;
1438 		  g->flags |= GROUP_TOUCHED;
1439 		  Slrn_Groups_Dirty = 1;
1440 	       }
1441 	  }
1442 	g = g->next;
1443      }
1444    find_line_num ();
1445    Slrn_Full_Screen_Update = 1;
1446 #if SLANG_VERSION >= 20000
1447    SLregexp_free (re);
1448 #endif
1449 }
1450 
1451 /*}}}*/
1452 
catch_up(void)1453 static void catch_up (void) /*{{{*/
1454 {
1455    if ((Slrn_Group_Current_Group == NULL)
1456        || ((Slrn_User_Wants_Confirmation & SLRN_CONFIRM_CATCHUP)
1457 	   && (Slrn_Batch == 0)
1458 	   && slrn_get_yesno(1, _("Mark %s as read"), Slrn_Group_Current_Group->group_name) <= 0))
1459      return;
1460 
1461    slrn_catchup_group ();
1462    slrn_message (_("Group marked as read."));
1463    (void) slrn_group_down_n (1);
1464 }
1465 
1466 /*}}}*/
1467 
uncatch_up(void)1468 static void uncatch_up (void) /*{{{*/
1469 {
1470    if ((Slrn_Group_Current_Group == NULL)
1471        || ((Slrn_User_Wants_Confirmation & SLRN_CONFIRM_CATCHUP)
1472 	   && (Slrn_Batch == 0)
1473 	   && slrn_get_yesno(1, _("Mark %s as un-read"), Slrn_Group_Current_Group->group_name) <= 0))
1474      return;
1475 
1476    slrn_uncatchup_group ();
1477    slrn_message (_("Group marked as un-read."));
1478    (void) slrn_group_down_n (1);
1479 }
1480 
1481 /*}}}*/
1482 
unsubscribe(void)1483 static void unsubscribe (void) /*{{{*/
1484 {
1485    SLRegexp_Type *re;
1486    Slrn_Group_Type *g;
1487 
1488    if (Slrn_Group_Current_Group == NULL) return;
1489 
1490    if (Slrn_Prefix_Arg_Ptr == NULL)
1491      {
1492 	Slrn_Group_Current_Group->flags |= GROUP_UNSUBSCRIBED | GROUP_TOUCHED;
1493 	slrn_group_down_n (1);
1494 	Slrn_Groups_Dirty = 1;
1495 	return;
1496      }
1497 
1498    Slrn_Prefix_Arg_Ptr = NULL;
1499 
1500    if (NULL == (re = read_group_regexp (_("Un-Subscribe pattern: "), NULL)))
1501      return;
1502 
1503    g = Groups;
1504    while (g != NULL)
1505      {
1506 	if ((g->flags & GROUP_UNSUBSCRIBED) == 0)
1507 	  {
1508 	     if (NULL != (slrn_regexp_match (re, g->group_name)))
1509 	       {
1510 		  g->flags &= ~GROUP_HIDDEN;
1511 		  g->flags |= (GROUP_TOUCHED | GROUP_UNSUBSCRIBED);
1512 	       }
1513 	  }
1514 	g = g->next;
1515      }
1516    find_line_num ();
1517    Slrn_Full_Screen_Update = 1;
1518 #if SLANG_VERSION >= 20000
1519    SLregexp_free (re);
1520 #endif
1521 }
1522 
1523 /*}}}*/
1524 
group_bob(void)1525 static void group_bob (void)
1526 {
1527    while (slrn_group_up_n (1000));
1528 }
1529 
group_eob(void)1530 static void group_eob (void)
1531 {
1532    while (slrn_group_down_n (1000));
1533 }
1534 
toggle_list_all_groups1(int hide_flag)1535 static void toggle_list_all_groups1 (int hide_flag) /*{{{*/
1536 {
1537    Slrn_Group_Type *g, *first_found = NULL;
1538    static int all_hidden = 1;
1539 
1540    g = Groups;
1541 
1542    if (hide_flag != -1)
1543      {
1544 	all_hidden = hide_flag;
1545      }
1546    else all_hidden = !all_hidden;
1547 
1548    if (all_hidden)
1549      {
1550 	while (g != NULL)
1551 	  {
1552 	     if (g->flags & GROUP_UNSUBSCRIBED) g->flags |= GROUP_HIDDEN;
1553 	     g = g->next;
1554 	  }
1555      }
1556    else if (hide_flag != -1)
1557      {
1558 	while (g != NULL)
1559 	  {
1560 	     if (g->flags & GROUP_UNSUBSCRIBED) g->flags &= ~GROUP_HIDDEN;
1561 	     g = g->next;
1562 	  }
1563      }
1564    else
1565      {
1566 	SLRegexp_Type *re;
1567 	char origpat[SLRL_DISPLAY_BUFFER_SIZE];
1568 
1569 	if (NULL == (re = read_group_regexp (_("List Groups (e.g., comp*unix*): "),
1570 					     origpat)))
1571 	  {
1572 	     all_hidden = 1;
1573 	     return;
1574 	  }
1575 
1576 	if ((Slrn_List_Active_File == 0)
1577 	    && (OK_GROUPS == Slrn_Server_Obj->sv_list_active (origpat)))
1578 	  {
1579 	     char buf [NNTP_BUFFER_SIZE];
1580 	     Slrn_Group_Type *save = Slrn_Group_Current_Group;
1581 	     Slrn_Group_Current_Group = NULL;
1582 
1583 	     while (Slrn_Server_Obj->sv_read_line (buf, sizeof (buf)) > 0)
1584 	       {
1585 		  unsigned int len;
1586 		  int min, max;
1587 
1588 		  parse_active_line ((unsigned char *)buf, &len, &min, &max);
1589 		  g = create_group_entry (buf, len, min, max, 0, 0);
1590 
1591 		  if (g != NULL)
1592 		    {
1593 		       g->flags &= ~GROUP_HIDDEN;
1594 		       if ((first_found == NULL) && (g->flags & GROUP_UNSUBSCRIBED))
1595 			 first_found = g;
1596 		    }
1597 	       }
1598 
1599 	     Slrn_Group_Current_Group = save;
1600 	  }
1601 	else while (g != NULL)
1602 	  {
1603 	     if (NULL != slrn_regexp_match (re, g->group_name))
1604 	       {
1605 		  if ((first_found == NULL) && (g->flags & GROUP_UNSUBSCRIBED))
1606 		    first_found = g;
1607 		  g->flags &= ~GROUP_HIDDEN;
1608 	       }
1609 	     g = g->next;
1610 	  }
1611 #if SLANG_VERSION >= 20000
1612 	SLregexp_free (re);
1613 #endif
1614      }
1615 
1616    g = Slrn_Group_Current_Group;
1617    if (first_found != NULL)
1618      g = first_found;
1619    else
1620      {
1621 	while ((g != NULL) && (g->flags & GROUP_HIDDEN)) g = g->next;
1622 	if ((g == NULL) && (Slrn_Group_Current_Group != NULL))
1623 	  {
1624 	     g = Slrn_Group_Current_Group -> prev;
1625 	     while ((g != NULL) && (g->flags & GROUP_HIDDEN)) g = g->prev;
1626 	  }
1627      }
1628    Slrn_Group_Current_Group = g;
1629 
1630    Slrn_Full_Screen_Update = 1;
1631 
1632    if ((all_hidden == 0) && (Slrn_Group_Current_Group == NULL))
1633      {
1634 	Slrn_Group_Current_Group = Groups;
1635 	if ((Slrn_Group_Current_Group != NULL)
1636 	    && (Slrn_Group_Current_Group->flags & GROUP_HIDDEN))
1637 	  {
1638 	     Slrn_Group_Current_Group = NULL;
1639 	  }
1640      }
1641 
1642    find_line_num ();
1643 }
1644 
1645 /*}}}*/
1646 
toggle_list_all_groups(void)1647 static void toggle_list_all_groups (void) /*{{{*/
1648 {
1649    int mode = -1;
1650 
1651    if (Slrn_Prefix_Arg_Ptr != NULL)
1652      {
1653 	mode = *Slrn_Prefix_Arg_Ptr;
1654 	Slrn_Prefix_Arg_Ptr = NULL;
1655 	if (mode == 2) mode = 0;
1656      }
1657 
1658    toggle_list_all_groups1 (mode);
1659 }
1660 
1661 /*}}}*/
1662 
slrn_list_all_groups(int mode)1663 void slrn_list_all_groups (int mode)
1664 {
1665    toggle_list_all_groups1 (!mode);
1666 }
1667 
slrn_hide_current_group(void)1668 void slrn_hide_current_group (void) /*{{{*/
1669 {
1670    if (Slrn_Group_Current_Group == NULL) return;
1671    Groups_Hidden |= 2;
1672    Slrn_Group_Current_Group->flags |= GROUP_HIDDEN;
1673    set_current_group ();
1674 }
1675 /*}}}*/
1676 
toggle_hide_groups(void)1677 static void toggle_hide_groups (void) /*{{{*/
1678 {
1679    Slrn_Group_Type *g;
1680 
1681    Groups_Hidden = !Groups_Hidden;
1682 
1683    g = Groups;
1684 
1685    if (Groups_Hidden)
1686      {
1687 	while (g != NULL)
1688 	  {
1689 	     if ((g->unread == 0)
1690 		 && ((g->flags & GROUP_UNSUBSCRIBED) == 0))
1691 	       g->flags |= GROUP_HIDDEN;
1692 
1693 	     g = g->next;
1694 	  }
1695      }
1696    else
1697      {
1698 	while (g != NULL)
1699 	  {
1700 	     if ((g->flags & GROUP_UNSUBSCRIBED) == 0)
1701 	       g->flags &= ~GROUP_HIDDEN;
1702 
1703 	     g = g->next;
1704 	  }
1705      }
1706 
1707    set_current_group ();
1708 }
1709 
1710 /*}}}*/
1711 
slrn_hide_groups(int mode)1712 void slrn_hide_groups (int mode)
1713 {
1714    Groups_Hidden = !mode;
1715    toggle_hide_groups ();
1716 }
1717 
select_group_cmd(void)1718 static void select_group_cmd (void)
1719 {
1720    if (-1 == slrn_group_select_group ())
1721      slrn_error (_("No unread articles."));
1722 }
1723 
slrn_post_cmd(void)1724 void slrn_post_cmd (void) /*{{{*/
1725 {
1726    char *name;
1727    char group[SLRL_DISPLAY_BUFFER_SIZE];
1728    char followupto[SLRL_DISPLAY_BUFFER_SIZE];
1729    char subj[SLRL_DISPLAY_BUFFER_SIZE];
1730 
1731    if (Slrn_Post_Obj->po_can_post == 0)
1732      {
1733 	slrn_error (_("Posting not allowed."));
1734 	return;
1735      }
1736 
1737    if ((Slrn_User_Wants_Confirmation & SLRN_CONFIRM_POST) && (Slrn_Batch == 0) &&
1738        (slrn_get_yesno (1, _("Are you sure that you want to post")) <= 0))
1739      return;
1740 
1741    slrn_run_hooks (HOOK_POST, 0);
1742    if (SLang_get_error ())
1743      return;
1744 
1745    if (Slrn_Group_Current_Group == NULL)
1746      name = "";
1747    else name = Slrn_Group_Current_Group->group_name;
1748 
1749    if (strlen (name) >= sizeof (group))
1750      name = "";
1751    slrn_strncpy (group, name, sizeof (group));
1752    if (slrn_read_input (_("Newsgroup: "), NULL, group, 1, -1) <= 0) return;
1753    if (slrn_strbyte (group, ',') != NULL)
1754      {
1755 	slrn_strncpy (followupto, name, sizeof (followupto));
1756 	(void) slrn_read_input (_("Followup-To: "), NULL, followupto, 1, -1);
1757      }
1758    else
1759      *followupto = '\0';
1760 
1761    *subj = 0; if (slrn_read_input (_("Subject: "), NULL, subj, 1, 0) <= 0) return;
1762 
1763    (void) slrn_post (group, followupto, subj);
1764 }
1765 
1766 /*}}}*/
1767 
toggle_scoring(void)1768 static void toggle_scoring (void) /*{{{*/
1769 {
1770    /* Note to translators: Here, "fF" means "full", "sS" is "simple",
1771     * "nN" means "none" and "cC" is "cancel".
1772     * As always, don't change the length of the string; you cannot use
1773     * the default characters for different fields.
1774     */
1775    char rsp, *responses = _("fFsSnNcC");
1776 
1777    if (-1 == slrn_check_batch ())
1778      return;
1779    if (strlen (responses) != 8)
1780      responses = "";
1781    rsp = slrn_get_response ("fFsSnNcC\007", responses,
1782     _("Select scoring mode: \001Full, \001Simple, \001None, \001Cancel"));
1783 
1784    if (rsp != 7)
1785      rsp = slrn_map_translated_char ("fFsSnNcC", responses, rsp) | 0x20;
1786    switch (rsp)
1787      {
1788       case 'f':
1789 	Slrn_Perform_Scoring = SLRN_XOVER_SCORING | SLRN_EXPENSIVE_SCORING;
1790 	slrn_message (_("Full Header Scoring enabled."));
1791 	break;
1792 
1793       case 's':
1794 	Slrn_Perform_Scoring = SLRN_XOVER_SCORING;
1795 	slrn_message (_("Expensive Scoring disabled."));
1796 	break;
1797 
1798       case 'n':
1799 	Slrn_Perform_Scoring = 0;
1800 	slrn_message (_("Scoring disabled."));
1801 	break;
1802 
1803       default:
1804 	slrn_clear_message ();
1805 	break;
1806      }
1807 }
1808 
1809 /*}}}*/
save_newsrc_cmd(void)1810 static void save_newsrc_cmd (void) /*{{{*/
1811 {
1812    if (Slrn_Groups_Dirty)
1813      {
1814 	slrn_write_newsrc (0);
1815      }
1816    else
1817      {
1818 	slrn_message (_("No changes need to be saved."));
1819      }
1820    slrn_smg_refresh ();
1821 }
1822 
1823 /*}}}*/
1824 
1825 /*}}}*/
1826 
1827 /*{{{ Group Mode Initialization/Keybindings */
1828 
slrn_group_hup(int sig)1829 static void slrn_group_hup (int sig)
1830 {
1831    slrn_write_newsrc (0);
1832    slrn_quit (sig);
1833 }
1834 
enter_group_mode_hook(void)1835 static void enter_group_mode_hook (void)
1836 {
1837    if (Slrn_Scroll_By_Page)
1838      Group_Window.cannot_scroll = 2;
1839    else
1840      Group_Window.cannot_scroll = SLtt_Term_Cannot_Scroll;
1841    slrn_run_hooks (HOOK_GROUP_MODE, 0);
1842 }
1843 
group_winch_sig(int old_r,int old_c)1844 static void group_winch_sig (int old_r, int old_c)
1845 {
1846    (void) old_r; (void) old_c;
1847 
1848    if (SLtt_Screen_Rows > 3)
1849      Group_Window.nrows = SLtt_Screen_Rows - 3;
1850    else
1851      Group_Window.nrows = 1;
1852 }
1853 
1854 static Slrn_Mode_Type Group_Mode_Cap =
1855 {
1856    NULL,
1857    group_update_screen,		       /* redraw */
1858    group_winch_sig,		       /* sig winch hook */
1859    slrn_group_hup,		       /* hangup hook */
1860    enter_group_mode_hook,	       /* enter_mode_hook */
1861    SLRN_GROUP_MODE
1862 };
1863 
1864 /*{{{ Group Mode Keybindings */
1865 
1866 #define A_KEY(s, f)  {s, (int (*)(void)) f}
1867 static SLKeymap_Function_Type Group_Functions [] = /*{{{*/
1868 {
1869    A_KEY("add_group", add_group_cmd),
1870    A_KEY("bob", group_bob),
1871    A_KEY("catchup", catch_up),
1872    A_KEY("digit_arg", slrn_digit_arg),
1873    A_KEY("eob", group_eob),
1874    A_KEY("evaluate_cmd", slrn_evaluate_cmd),
1875    A_KEY("group_search", group_search_forward),
1876    A_KEY("group_search_backward", group_search_backward),
1877    A_KEY("group_search_forward", group_search_forward),
1878    A_KEY("help", slrn_group_help),
1879    A_KEY("line_down", group_down),
1880    A_KEY("line_up", group_up),
1881    A_KEY("move_group", move_group_cmd),
1882    A_KEY("page_down", group_pagedown),
1883    A_KEY("page_up", group_pageup),
1884    A_KEY("post", slrn_post_cmd),
1885    A_KEY("post_postponed", slrn_post_postponed),
1886    A_KEY("quit", slrn_group_quit),
1887    A_KEY("redraw", slrn_redraw),
1888    A_KEY("refresh_groups", refresh_groups_cmd),
1889    A_KEY("repeat_last_key", slrn_repeat_last_key),
1890    A_KEY("save_newsrc", save_newsrc_cmd),
1891    A_KEY("select_group", select_group_cmd),
1892    A_KEY("subscribe", subscribe),
1893    A_KEY("suspend", slrn_suspend_cmd),
1894    A_KEY("toggle_group_formats", toggle_group_formats),
1895    A_KEY("toggle_hidden", toggle_hide_groups),
1896    A_KEY("toggle_list_all", toggle_list_all_groups),
1897    A_KEY("toggle_scoring", toggle_scoring),
1898    A_KEY("transpose_groups", transpose_groups),
1899    A_KEY("uncatchup", uncatch_up),
1900    A_KEY("unsubscribe", unsubscribe),
1901 #if 1 /* FIXME: These ones are going to be deleted before 1.0 */
1902    A_KEY("down", group_down),
1903    A_KEY("group_bob", group_bob),
1904    A_KEY("group_eob", group_eob),
1905    A_KEY("pagedown", group_pagedown),
1906    A_KEY("pageup", group_pageup),
1907    A_KEY("toggle_group_display", toggle_group_formats),
1908    A_KEY("uncatch_up", uncatch_up),
1909    A_KEY("up", group_up),
1910 #endif
1911    A_KEY(NULL, NULL)
1912 };
1913 
1914 /*}}}*/
1915 
1916 /*{{{ Mouse Functions*/
1917 
1918 /* actions for different regions:
1919  *	- top status line (help)
1920  *	- normal region
1921  *	- bottom status line
1922  */
group_mouse(void (* top_status)(void),void (* bot_status)(void),void (* normal_region)(void))1923 static void group_mouse (void (*top_status)(void),
1924 			 void (*bot_status)(void),
1925 			 void (*normal_region)(void)
1926 			 )
1927 {
1928    int r,c;
1929 
1930    slrn_get_mouse_rc (&r, &c);
1931 
1932    /* take top status line into account */
1933    if (r == 1)
1934      {
1935 	if (Slrn_Use_Mouse)
1936 	  slrn_execute_menu (c);
1937 	else
1938 	  if (NULL != top_status) (*top_status) ();
1939  	return;
1940      }
1941 
1942    if (r >= SLtt_Screen_Rows)
1943      return;
1944 
1945    /* bottom status line */
1946    if (r == SLtt_Screen_Rows - 1)
1947      {
1948 	if (NULL != bot_status) (*bot_status) ();
1949 	return;
1950      }
1951 
1952    r -= (1 + Last_Cursor_Row);
1953    if (r < 0)
1954      {
1955 	r = -r;
1956 	if (r != (int) slrn_group_up_n (r)) return;
1957      }
1958    else if (r != (int) slrn_group_down_n (r)) return;
1959 
1960    if (NULL != normal_region) (*normal_region) ();
1961 }
1962 
group_mouse_left(void)1963 static void group_mouse_left (void)
1964 {
1965    group_mouse (slrn_group_help, group_pagedown, select_group_cmd);
1966 }
1967 
group_mouse_middle(void)1968 static void group_mouse_middle (void)
1969 {
1970    group_mouse (toggle_group_formats, toggle_hide_groups, select_group_cmd);
1971 #if 1
1972    /* Make up for buggy rxvt which have problems with the middle key. */
1973    if (NULL != getenv ("COLORTERM"))
1974      {
1975 	if (SLang_input_pending (7))
1976 	  {
1977 	     while (SLang_input_pending (0))
1978 	       (void) SLang_getkey ();
1979 	  }
1980      }
1981 #endif
1982 }
1983 
group_mouse_right(void)1984 static void group_mouse_right (void)
1985 {
1986    group_mouse (slrn_group_help, group_pageup, select_group_cmd);
1987 }
1988 
1989 /*}}}*/
1990 
1991 /*}}}*/
1992 
1993 #define USE_TEST_FUNCTION 0
1994 #if USE_TEST_FUNCTION
test_function(void)1995 static void test_function (void)
1996 {
1997    char *file;
1998    file = slrn_browse_dir (".");
1999    if (file != NULL)
2000      {
2001 	slrn_message (file);
2002 	slrn_free (file);
2003      }
2004 }
2005 #endif
2006 
slrn_init_group_mode(void)2007 void slrn_init_group_mode (void) /*{{{*/
2008 {
2009    char  *err = _("Unable to create group keymap!");
2010 
2011    if (NULL == (Slrn_Group_Keymap = SLang_create_keymap ("group", NULL)))
2012      slrn_exit_error ("%s", err);
2013 
2014    Group_Mode_Cap.keymap = Slrn_Group_Keymap;
2015 
2016    Slrn_Group_Keymap->functions = Group_Functions;
2017 
2018    SLkm_define_key ("\0331", (FVOID_STAR) slrn_digit_arg, Slrn_Group_Keymap);
2019    SLkm_define_key ("\0332", (FVOID_STAR) slrn_digit_arg, Slrn_Group_Keymap);
2020    SLkm_define_key ("\0333", (FVOID_STAR) slrn_digit_arg, Slrn_Group_Keymap);
2021    SLkm_define_key ("\0334", (FVOID_STAR) slrn_digit_arg, Slrn_Group_Keymap);
2022    SLkm_define_key ("\0335", (FVOID_STAR) slrn_digit_arg, Slrn_Group_Keymap);
2023    SLkm_define_key ("\0336", (FVOID_STAR) slrn_digit_arg, Slrn_Group_Keymap);
2024    SLkm_define_key ("\0337", (FVOID_STAR) slrn_digit_arg, Slrn_Group_Keymap);
2025    SLkm_define_key ("\0338", (FVOID_STAR) slrn_digit_arg, Slrn_Group_Keymap);
2026    SLkm_define_key ("\0339", (FVOID_STAR) slrn_digit_arg, Slrn_Group_Keymap);
2027    SLkm_define_key ("\0330", (FVOID_STAR) slrn_digit_arg, Slrn_Group_Keymap);
2028    SLkm_define_key  ("^K\033[A", (FVOID_STAR) group_bob, Slrn_Group_Keymap);
2029    SLkm_define_key  ("^K\033OA", (FVOID_STAR) group_bob, Slrn_Group_Keymap);
2030    SLkm_define_key  ("^K\033[B", (FVOID_STAR) group_eob, Slrn_Group_Keymap);
2031    SLkm_define_key  ("^K\033OB", (FVOID_STAR) group_eob, Slrn_Group_Keymap);
2032    SLkm_define_key  ("\033a", (FVOID_STAR) toggle_group_formats, Slrn_Group_Keymap);
2033    SLkm_define_key  ("\033>", (FVOID_STAR) group_eob, Slrn_Group_Keymap);
2034    SLkm_define_key  ("\033<", (FVOID_STAR) group_bob, Slrn_Group_Keymap);
2035    SLkm_define_key  ("^D", (FVOID_STAR) group_pagedown, Slrn_Group_Keymap);
2036    SLkm_define_key  ("^V", (FVOID_STAR) group_pagedown, Slrn_Group_Keymap);
2037 #if defined(IBMPC_SYSTEM)
2038    SLkm_define_key  ("^@Q", (FVOID_STAR) group_pagedown, Slrn_Group_Keymap);
2039    SLkm_define_key  ("\xE0Q", (FVOID_STAR) group_pagedown, Slrn_Group_Keymap);
2040    SLkm_define_key  ("^@I", (FVOID_STAR) group_pageup, Slrn_Group_Keymap);
2041    SLkm_define_key  ("\xE0I", (FVOID_STAR) group_pageup, Slrn_Group_Keymap);
2042 #else
2043    SLkm_define_key  ("\033[6~", (FVOID_STAR) group_pagedown, Slrn_Group_Keymap);
2044    SLkm_define_key  ("\033[G", (FVOID_STAR) group_pagedown, Slrn_Group_Keymap);
2045    SLkm_define_key  ("\033[5~", (FVOID_STAR) group_pageup, Slrn_Group_Keymap);
2046    SLkm_define_key  ("\033[I", (FVOID_STAR) group_pageup, Slrn_Group_Keymap);
2047 #endif
2048    SLkm_define_key  ("m", (FVOID_STAR) move_group_cmd, Slrn_Group_Keymap);
2049    SLkm_define_key  ("^U", (FVOID_STAR) group_pageup, Slrn_Group_Keymap);
2050    SLkm_define_key  ("\033V", (FVOID_STAR) group_pageup, Slrn_Group_Keymap);
2051    SLkm_define_key  ("a", (FVOID_STAR) add_group_cmd, Slrn_Group_Keymap);
2052    SLkm_define_key  ("u", (FVOID_STAR) unsubscribe, Slrn_Group_Keymap);
2053    SLkm_define_key  ("s", (FVOID_STAR) subscribe, Slrn_Group_Keymap);
2054    SLkm_define_key  ("\033u", (FVOID_STAR) uncatch_up, Slrn_Group_Keymap);
2055    SLkm_define_key  ("c", (FVOID_STAR) catch_up, Slrn_Group_Keymap);
2056    SLkm_define_key  ("K", (FVOID_STAR) toggle_scoring, Slrn_Group_Keymap);
2057    SLkm_define_key  ("L", (FVOID_STAR) toggle_list_all_groups, Slrn_Group_Keymap);
2058    SLkm_define_key  ("l", (FVOID_STAR) toggle_hide_groups, Slrn_Group_Keymap);
2059    SLkm_define_key  ("^Z", (FVOID_STAR) slrn_suspend_cmd, Slrn_Group_Keymap);
2060    SLkm_define_key  (" ", (FVOID_STAR) select_group_cmd, Slrn_Group_Keymap);
2061    SLkm_define_key  (".", (FVOID_STAR) slrn_repeat_last_key, Slrn_Group_Keymap);
2062    SLkm_define_key  ("P", (FVOID_STAR) slrn_post_cmd, Slrn_Group_Keymap);
2063    SLkm_define_key  ("\033P", (FVOID_STAR) slrn_post_postponed, Slrn_Group_Keymap);
2064    SLkm_define_key  ("?", (FVOID_STAR) slrn_group_help, Slrn_Group_Keymap);
2065    SLkm_define_key  ("\r", (FVOID_STAR) select_group_cmd, Slrn_Group_Keymap);
2066    SLkm_define_key  ("q", (FVOID_STAR) slrn_group_quit, Slrn_Group_Keymap);
2067    SLkm_define_key  ("^X^C", (FVOID_STAR) slrn_group_quit, Slrn_Group_Keymap);
2068    SLkm_define_key  ("^X^T", (FVOID_STAR) transpose_groups, Slrn_Group_Keymap);
2069    SLkm_define_key  ("^X^[", (FVOID_STAR) slrn_evaluate_cmd, Slrn_Group_Keymap);
2070    SLkm_define_key  ("^R", (FVOID_STAR) slrn_redraw, Slrn_Group_Keymap);
2071    SLkm_define_key  ("^L", (FVOID_STAR) slrn_redraw, Slrn_Group_Keymap);
2072    SLkm_define_key  ("^P", (FVOID_STAR) group_up, Slrn_Group_Keymap);
2073 #if defined(IBMPC_SYSTEM)
2074    SLkm_define_key  ("^@H", (FVOID_STAR) group_up, Slrn_Group_Keymap);
2075    SLkm_define_key  ("\xE0H", (FVOID_STAR) group_up, Slrn_Group_Keymap);
2076    SLkm_define_key  ("^@P", (FVOID_STAR) group_down, Slrn_Group_Keymap);
2077    SLkm_define_key  ("\xE0P", (FVOID_STAR) group_down, Slrn_Group_Keymap);
2078 #else
2079    SLkm_define_key  ("\033[A", (FVOID_STAR) group_up, Slrn_Group_Keymap);
2080    SLkm_define_key  ("\033OA", (FVOID_STAR) group_up, Slrn_Group_Keymap);
2081    SLkm_define_key  ("\033[B", (FVOID_STAR) group_down, Slrn_Group_Keymap);
2082    SLkm_define_key  ("\033OB", (FVOID_STAR) group_down, Slrn_Group_Keymap);
2083 #endif
2084    SLkm_define_key  ("N", (FVOID_STAR) group_down, Slrn_Group_Keymap);
2085    SLkm_define_key  ("^N", (FVOID_STAR) group_down, Slrn_Group_Keymap);
2086    SLkm_define_key  ("/", (FVOID_STAR) group_search_forward, Slrn_Group_Keymap);
2087    SLkm_define_key  ("\\", (FVOID_STAR) group_search_backward, Slrn_Group_Keymap);
2088    SLkm_define_key  ("G", (FVOID_STAR) refresh_groups_cmd, Slrn_Group_Keymap);
2089    SLkm_define_key  ("X", (FVOID_STAR) save_newsrc_cmd, Slrn_Group_Keymap);
2090 
2091    /* mouse (left/right/middle) */
2092    SLkm_define_key  ("\033[M\040", (FVOID_STAR) group_mouse_left, Slrn_Group_Keymap);
2093    SLkm_define_key  ("\033[M\041", (FVOID_STAR) group_mouse_middle, Slrn_Group_Keymap);
2094    SLkm_define_key  ("\033[M\042", (FVOID_STAR) group_mouse_right, Slrn_Group_Keymap);
2095    SLkm_define_key  ("\033[M\043", (FVOID_STAR) group_mouse_left, Slrn_Group_Keymap);
2096    SLkm_define_key  ("\033[M\044", (FVOID_STAR) group_mouse_left, Slrn_Group_Keymap);
2097 #if USE_TEST_FUNCTION
2098    SLkm_define_key  ("y", (FVOID_STAR) test_function, Slrn_Group_Keymap);
2099 #endif
2100    if (SLang_get_error ()) slrn_exit_error ("%s", err);
2101 }
2102 
2103 /*}}}*/
2104 
2105 /*}}}*/
2106 
slrn_select_group_mode(void)2107 int slrn_select_group_mode (void) /*{{{*/
2108 {
2109    init_group_win_struct ();
2110 
2111    Last_Cursor_Row = 0;
2112    group_quick_help ();
2113    Slrn_Full_Screen_Update = 1;
2114 
2115    slrn_push_mode (&Group_Mode_Cap);
2116    return 0;
2117 }
2118 
2119 /*}}}*/
2120 
2121 /*{{{ Read/Write Newsrc, Group Descriptions */
2122 
add_group_description(unsigned char * s,unsigned char * smax,unsigned char * dsc)2123 static void add_group_description (unsigned char *s, unsigned char *smax, /*{{{*/
2124 				   unsigned char *dsc)
2125 {
2126    Slrn_Group_Type *g;
2127    unsigned long hash;
2128 
2129    hash = slrn_compute_hash (s, smax);
2130    g = Group_Hash_Table[hash % GROUP_HASH_TABLE_SIZE];
2131    while (g != NULL)
2132      {
2133 	if ((g->hash == hash) && (!strncmp (g->group_name, (char *) s, (unsigned int) (smax - s))))
2134 	  {
2135 	     /* Sometimes these get repeated --- not by slrn but on the server! */
2136 	     slrn_free (g->descript);
2137 
2138 	     g->descript = slrn_strmalloc ((char *) dsc, 0);
2139 	     /* Ok to fail. */
2140 	     return;
2141 	  }
2142 	g = g->hash_next;
2143      }
2144 }
2145 
2146 /*}}}*/
slrn_get_group_descriptions(void)2147 void slrn_get_group_descriptions (void) /*{{{*/
2148 {
2149    FILE *fp;
2150    char line[2 * SLRN_MAX_PATH_LEN];
2151    char file[SLRN_MAX_PATH_LEN];
2152    int num;
2153 
2154 #ifdef VMS
2155    slrn_snprintf (file, sizeof (file), "%s-dsc", Slrn_Newsrc_File);
2156 #else
2157 # ifdef SLRN_USE_OS2_FAT
2158    slrn_os2_make_fat (file, sizeof (file), Slrn_Newsrc_File, ".dsc");
2159 # else
2160    slrn_snprintf (file, sizeof (file), "%s.dsc", Slrn_Newsrc_File);
2161 # endif
2162 #endif
2163 
2164    if (NULL == (fp = fopen (file, "w")))
2165      {
2166 	slrn_exit_error (_("\
2167 Unable to create newsgroup description file:\n%s\n"), file);
2168      }
2169 
2170    fprintf (stdout, _("\nCreating description file %s.\n"), file);
2171    fprintf (stdout, _("Getting newsgroup descriptions from server.\n\
2172 Note: This step may take some time if you have a slow connection!!!\n"));
2173 
2174    fflush (stdout);
2175 
2176    if (OK_GROUPS != Slrn_Server_Obj->sv_list_newsgroups ())
2177      {
2178 	slrn_error (_("Server failed on list newsgroups command."));
2179 	slrn_fclose (fp);
2180 	return;
2181      }
2182 
2183    num = 0;
2184 
2185    while (1)
2186      {
2187 	unsigned char *b, *bmax, *dsc, ch;
2188 	int status;
2189 
2190 	status = Slrn_Server_Obj->sv_read_line (line, sizeof (line));
2191 	if (status == -1)
2192 	  {
2193 	     slrn_error (_("Error reading line from server"));
2194 	     slrn_fclose (fp);
2195 	     return;
2196 	  }
2197 	if (status == 0)
2198 	  break;
2199 
2200 	num = num % 25;
2201 	if (num == 0)
2202 	  {
2203 	     putc ('.', stdout);
2204 	     fflush (stdout);
2205 	  }
2206 
2207 	/* Check the syntax on this line. They are often corrupt */
2208 	b = (unsigned char *) slrn_skip_whitespace (line);
2209 
2210 	bmax = b;
2211 	while ((ch = *bmax) > ' ') bmax++;
2212 	if ((ch == 0) || (ch == '\n')) continue;
2213 	*bmax = 0;
2214 
2215 	/* News group marked off, now get the description. */
2216 	dsc = bmax + 1;
2217 	while (((ch = *dsc) <= ' ') && (ch != 0)) dsc++;
2218 	if ((ch == 0) || (ch == '?') || (ch == '\n')) continue;
2219 
2220 	/* add_group_description (b, bmax, dsc); */
2221 
2222 	fputs ((char *) b, fp);
2223 	putc(':', fp);
2224 	fputs ((char *) dsc, fp);
2225 	putc('\n', fp);
2226 
2227 	num++;
2228      }
2229    slrn_fclose (fp);
2230    putc ('\n', stdout);
2231 }
2232 
2233 /*}}}*/
slrn_read_group_descriptions(void)2234 int slrn_read_group_descriptions (void) /*{{{*/
2235 {
2236    FILE *fp;
2237    char line[2 * SLRN_MAX_PATH_LEN];
2238    char file[SLRN_MAX_PATH_LEN];
2239 
2240 #ifdef VMS
2241    slrn_snprintf (file, sizeof (file), "%s-dsc", Slrn_Newsrc_File);
2242 #else
2243 # ifdef SLRN_USE_OS2_FAT
2244    slrn_os2_make_fat (file, sizeof (file), Slrn_Newsrc_File, ".dsc");
2245 # else
2246    slrn_snprintf (file, sizeof (file), "%s.dsc", Slrn_Newsrc_File);
2247 # endif
2248 #endif
2249 
2250    if (NULL == (fp = fopen (file, "r")))
2251      {
2252 #ifdef VMS
2253 	slrn_snprintf (file, sizeof (file), "%snewsgroups.dsc",
2254 		       SLRN_LIB_DIR);
2255 	if (NULL == (fp = fopen (file, "r")))
2256 	  {
2257 	     slrn_snprintf (file, sizeof (file), "%snewsgroups-dsc",
2258 			    SLRN_LIB_DIR);
2259 	     fp = fopen (file, "r");
2260 	  }
2261 #else
2262 	slrn_snprintf (file, sizeof (file), "%s/newsgroups.dsc",
2263 		       SLRN_LIB_DIR);
2264 	fp = fopen (file, "r");
2265 #endif
2266 	if (fp == NULL) return -1;
2267      }
2268 
2269    while (NULL != fgets (line, sizeof (line) - 1, fp))
2270      {
2271 	unsigned char *bmax, *dsc, ch;
2272 
2273 	bmax = (unsigned char *) line;
2274 	while (((ch = *bmax) != ':') && ch) bmax++;
2275 	if (ch <= ' ') continue;
2276 	*bmax = 0;
2277 
2278 	dsc = bmax + 1;
2279 	add_group_description ((unsigned char *) line, bmax, dsc);
2280      }
2281    slrn_fclose (fp);
2282    return 0;
2283 }
2284 
2285 /*}}}*/
2286 
2287 /* Map 1998 --> 98, 2003 --> 3, etc.... */
rfc977_patchup_year(int year)2288 static int rfc977_patchup_year (int year)
2289 {
2290    return year - 100 * (year / 100);
2291 }
2292 
slrn_get_new_groups(int create_flag)2293 int slrn_get_new_groups (int create_flag) /*{{{*/
2294 {
2295    FILE *fp;
2296    time_t tloc;
2297    struct tm *tm_struct;
2298    char line[NNTP_BUFFER_SIZE];
2299    char file[SLRN_MAX_PATH_LEN];
2300    int num;
2301    char *p;
2302    int parse_error = 0;
2303 
2304 #ifdef VMS
2305    slrn_snprintf (file, sizeof (file), "%s-time", Slrn_Newsrc_File);
2306 #else
2307 # ifdef SLRN_USE_OS2_FAT
2308    slrn_os2_make_fat (file, sizeof (file), Slrn_Newsrc_File, ".tim");
2309 # else
2310    slrn_snprintf (file, sizeof (file), "%s.time", Slrn_Newsrc_File);
2311 # endif
2312 #endif
2313 
2314    if ((create_flag == 0)
2315        && (NULL != (fp = fopen (file, "r"))))
2316      {
2317 	char ch;
2318 	int i;
2319 	*line = 0;
2320 	fgets (line, sizeof (line), fp);
2321 	slrn_fclose (fp);
2322 
2323 	slrn_message_now (_("Checking for new groups ..."));
2324 
2325 	time (&tloc);
2326 	parse_error = 1;
2327 
2328 	/* parse this line to make sure it is ok.  If it is bad, issue a warning
2329 	 * and go on.
2330 	 */
2331 	if (strncmp ("NEWGROUPS ", line, 10)) goto parse_error_label;
2332 	p = line + 10;
2333 
2334 	p = slrn_skip_whitespace (p);
2335 
2336 	/* parse yymmdd */
2337 	for (i = 0; i < 6; i++)
2338 	  {
2339 	     ch = p[i];
2340 	     if ((ch < '0') || (ch > '9')) goto parse_error_label;
2341 	  }
2342 	if (p[6] != ' ') goto parse_error_label;
2343 
2344 	ch = p[2];
2345 	if (ch > '1') goto parse_error_label;
2346 	if ((ch == '1') && (p[3] > '2')) goto parse_error_label;
2347 	ch = p[4];
2348 	if (ch > '3') goto parse_error_label;
2349 	if ((ch == '3') && (p[5] > '1')) goto parse_error_label;
2350 
2351 	/* Now the hour: hhmmss */
2352 	p = slrn_skip_whitespace (p + 6);
2353 
2354 	for (i = 0; i < 6; i++)
2355 	  {
2356 	     ch = p[i];
2357 	     if ((ch < '0') || (ch > '9')) goto parse_error_label;
2358 	  }
2359 	ch = p[0];
2360 	if (ch > '2') goto parse_error_label;
2361 	if ((ch == '2') && (p[1] > '3')) goto parse_error_label;
2362 	if ((p[2] > '5') || (p[4] > '5')) goto parse_error_label;
2363 
2364 	p = slrn_skip_whitespace (p + 6);
2365 
2366 	if ((p[0] == 'G') && (p[1] == 'M') && (p[2] == 'T'))
2367 	  p += 3;
2368 	*p = 0;
2369 
2370 	parse_error = 0;
2371 
2372 	switch (Slrn_Server_Obj->sv_put_server_cmd (line, line, sizeof (line)))
2373 	  {
2374 	   case OK_NEWGROUPS:
2375 	     break;
2376 
2377 	   case ERR_FAULT:
2378 	     return -1;
2379 
2380 	   case ERR_COMMAND:
2381 	     slrn_message (_("Server does not implement NEWGROUPS command."));
2382 	     return 0;
2383 
2384 	   default:
2385 	     slrn_message (_("Server failed to return proper response to NEWGROUPS:\n%s\n"),
2386 			   line);
2387 	     goto parse_error_label;
2388 	  }
2389 
2390 	num = 0;
2391 
2392 	while (1)
2393 	  {
2394 	     int status = Slrn_Server_Obj->sv_read_line (line, sizeof (line));
2395 	     if (status == 0)
2396 	       break;
2397 	     if (status == -1)
2398 	       {
2399 		  slrn_error (_("Read from server failed"));
2400 		  return -1;
2401 	       }
2402 
2403 	     /* line contains new newsgroup name */
2404 	     add_unsubscribed_group ((unsigned char*)line);
2405 	     num++;
2406 	  }
2407 
2408 	if (num)
2409 	  {
2410 	     slrn_message (_("%d new newsgroup(s) found."), num);
2411 	  }
2412      }
2413    else time (&tloc);
2414 
2415    parse_error_label:
2416    if (parse_error)
2417      {
2418 	slrn_message (_("\
2419 %s appears corrupt.\n\
2420 I expected to see: NEWGROUPS yymmdd hhmmss GMT\n\
2421 I will patch the file up for you.\n"), file);
2422      }
2423 
2424 #ifdef VMS
2425    if (NULL == (fp = fopen (file, "w", "fop=cif")))
2426 #else
2427    if (NULL == (fp = fopen (file, "w")))
2428 #endif
2429      {
2430 	slrn_exit_error (_("Unable to open %s to record date."), file);
2431      }
2432 
2433    /* According to rfc977, the year must be in the form YY with the closest
2434     * century specifying the rest of the year, i.e., 99 is 1999, 30 is 2030,
2435     * etc.
2436     */
2437 #if defined(VMS) || defined(__BEOS__)
2438    /* gmtime is broken on BEOS */
2439    tm_struct = localtime (&tloc);
2440    tm_struct->tm_year = rfc977_patchup_year (tm_struct->tm_year + 1900);
2441    fprintf (fp, "NEWGROUPS %02d%02d%02d %02d%02d%02d",
2442             tm_struct->tm_year, 1 + tm_struct->tm_mon,
2443             tm_struct->tm_mday, tm_struct->tm_hour,
2444             tm_struct->tm_min, tm_struct->tm_sec);
2445 #else
2446    tm_struct = gmtime (&tloc);
2447    tm_struct->tm_year = rfc977_patchup_year (tm_struct->tm_year + 1900);
2448    fprintf (fp, "NEWGROUPS %02d%02d%02d %02d%02d%02d GMT",
2449 	    tm_struct->tm_year, 1 + tm_struct->tm_mon,
2450 	    tm_struct->tm_mday, tm_struct->tm_hour,
2451 	    tm_struct->tm_min, tm_struct->tm_sec);
2452 #endif
2453    slrn_fclose (fp);
2454    return 0;
2455 }
2456 
2457 /*}}}*/
2458 
parse_active_line(unsigned char * name,unsigned int * lenp,int * minp,int * maxp)2459 static int parse_active_line (unsigned char *name, unsigned int *lenp, /*{{{*/
2460 			      int *minp, int *maxp)
2461 {
2462    unsigned char *p;
2463 
2464    p = name;
2465    while (*p > ' ') p++;
2466    *lenp = (unsigned int) (p - name);
2467 
2468    while (*p == ' ') p++;
2469    *maxp = atoi ((char*)p);
2470    while (*p > ' ') p++;
2471    while (*p == ' ') p++;
2472    *minp = atoi((char*)p);
2473    if (*maxp < *minp) *minp = *maxp + 1;
2474    return 0;
2475 }
2476 
2477 /*}}}*/
2478 
2479 static char *Subscriptions [] =
2480 {
2481    "news.answers",
2482      "news.announce.newusers",
2483      "news.newusers.questions",
2484      "news.groups.questions",
2485      "news.software.readers",
2486      "alt.test",
2487      NULL
2488 };
2489 
read_and_parse_active(int create_flag)2490 static void read_and_parse_active (int create_flag) /*{{{*/
2491 {
2492    char line[NNTP_BUFFER_SIZE];
2493    int count = 0, ret;
2494    int initial_run = (Groups == NULL);
2495 
2496    if (OK_GROUPS != (ret = Slrn_Server_Obj->sv_list_active (NULL)))
2497      {
2498 	if (ret == ERR_NOAUTH)
2499 	  slrn_exit_error (_("Server failed LIST ACTIVE - authorization missing."));
2500 	slrn_exit_error (_("Server failed LIST ACTIVE."));
2501      }
2502 
2503    while (1)
2504      {
2505 	unsigned int len;
2506 	int min, max;
2507 	int status;
2508 
2509 	status = Slrn_Server_Obj->sv_read_line (line, sizeof(line));
2510 	if (status == -1)
2511 	  {
2512 	     /*	     Slrn_Groups_Dirty = 0;*/ /* Huh? */
2513 	     slrn_exit_error (_("Read from server failed"));
2514 	  }
2515 	if (status == 0)
2516 	  break;
2517 
2518 	parse_active_line ((unsigned char *)line, &len, &min, &max);
2519 
2520 	if (!initial_run)
2521 	  {
2522 	     Slrn_Group_Type *g = find_group_entry (line, len);
2523 	     if (g != NULL)
2524 	       {
2525 		  group_update_range (g, min, max);
2526 		  continue;
2527 	       }
2528 	  }
2529 
2530 	if (NULL == create_group_entry (line, len, min, max, 0, 0))
2531 	  continue;
2532 
2533 	if (create_flag)
2534 	  {
2535 	     count++;
2536 	     count = count % 50;
2537 	     if (count == 0)
2538 	       {
2539 		  putc ('.', stdout);
2540 		  fflush (stdout);
2541 	       }
2542 	     add_group (line, len, GROUP_UNSUBSCRIBED, 0, 1);
2543 	  }
2544      }
2545 
2546    if (create_flag)
2547      {
2548 	char *name;
2549 	unsigned int i = 0;
2550 	Slrn_Group_Type *save = Slrn_Group_Current_Group, *last = NULL;
2551 
2552 	if (OK_GROUPS == Slrn_Server_Obj->sv_list ("SUBSCRIPTIONS"))
2553 	  {
2554 	     name = line;
2555 	     if (Slrn_Server_Obj->sv_read_line (line, sizeof (line)) <= 0)
2556 	       name = NULL;
2557 	  }
2558 	else
2559 	  name = *Subscriptions;
2560 
2561 	while (name != NULL)
2562 	  {
2563 	     if (NULL != (Slrn_Group_Current_Group = find_group_entry (name, strlen (name))))
2564 	       {
2565 		  Slrn_Group_Current_Group->flags &= ~GROUP_HIDDEN;
2566 		  Slrn_Group_Current_Group->flags &= ~GROUP_UNSUBSCRIBED;
2567 		  last = place_group_in_newsrc_order (last);
2568 	       }
2569 
2570 	     if (name != line)
2571 	       name = Subscriptions[++i];
2572 	     else if (Slrn_Server_Obj->sv_read_line (line, sizeof (line)) <= 0)
2573 	       name = NULL;
2574 	  }
2575 
2576 	Slrn_Group_Current_Group = save;
2577 	Slrn_Groups_Dirty = 1;
2578 	Slrn_Write_Newsrc_Flags = 0;
2579      }
2580 
2581    Kill_After_Max = 0;
2582 }
2583 
2584 /*}}}*/
2585 
read_and_parse_newsrc_file(void)2586 static int read_and_parse_newsrc_file (void)
2587 {
2588    VFILE *vp;
2589    char *vline;
2590    unsigned int vlen;
2591    char file[SLRN_MAX_PATH_LEN];
2592    char *newsrc_filename = Slrn_Newsrc_File;
2593    Slrn_Group_Type *last_group = NULL;
2594    int ret_stat_o, ret_stat_as;
2595    struct stat st_o, st_as;
2596 
2597 #ifdef VMS
2598    slrn_snprintf (file, sizeof (file), "%s-as", Slrn_Newsrc_File);
2599 #else
2600 # ifdef SLRN_USE_OS2_FAT
2601    slrn_os2_make_fat (file, sizeof (file), Slrn_Newsrc_File, ".as");
2602 # else
2603    slrn_snprintf (file, sizeof (file), "%s.as", Slrn_Newsrc_File);
2604 # endif
2605 #endif
2606 
2607    ret_stat_o = stat (newsrc_filename, &st_o);
2608    ret_stat_as = stat (file, &st_as);
2609 
2610    if ((ret_stat_as != -1) &&
2611        (((ret_stat_o == -1) || (st_as.st_mtime > st_o.st_mtime))))
2612      {
2613 	slrn_message (_("\n* The autosave file of %s is newer than the file "
2614 		      "itself.\n"), newsrc_filename);
2615 	if (slrn_get_yesno (1, _("Do you want to restore your newsrc from "
2616 			    "the autosave version")))
2617 	  {
2618 	     newsrc_filename = file;
2619 	  }
2620      }
2621 
2622    if (NULL == (vp = vopen (newsrc_filename, 4096, 0)))
2623 #if 0
2624        && (NULL == (vp = slrn_open_home_vfile (".newsrc", file,
2625 					       sizeof (file))))
2626 #endif
2627        slrn_exit_error (_("Unable to open %s."), newsrc_filename);
2628 
2629    while (NULL != (vline = vgets (vp, &vlen)))
2630      {
2631 	char *p = vline;
2632 	char *pmax = p + vlen;
2633 	char ch = 0;
2634 
2635 	while ((p < pmax)
2636 	       && ((ch = *p) != '!') && (ch != ':'))
2637 	  p++;
2638 
2639 	if ((p == pmax) || (p == vline))
2640 	  continue;
2641 
2642 	if (vline[vlen-1] == '\n')
2643 	  vline[vlen-1] = 0;
2644 	else
2645 	  vline[vlen] = 0;
2646 
2647 	if (-1 == add_group (vline, (unsigned int) (p - vline),
2648 			     ((ch == '!') ? GROUP_UNSUBSCRIBED : 0), 0, 0))
2649 	  continue;
2650 
2651 	/* perform a re-arrangement to match arrangement in the
2652 	 * newsrc file
2653 	 */
2654 	/*if (Slrn_List_Active_File && (ch !=  '!'))*/
2655 	  last_group = place_group_in_newsrc_order (last_group);
2656      }
2657    vclose (vp);
2658    if (!Slrn_List_Active_File)
2659      refresh_groups (&Slrn_Group_Current_Group);
2660    return 0;
2661 }
2662 
slrn_read_newsrc(int create_flag)2663 int slrn_read_newsrc (int create_flag) /*{{{*/
2664 {
2665    slrn_message_now (_("Checking news%s ..."),
2666 		     Slrn_List_Active_File ? _(" via active file") : "");
2667 
2668    if (create_flag)
2669      {
2670 	FILE *fp;
2671 
2672 	/* See if we can open the file. */
2673 #ifdef VMS
2674 	if (NULL == (fp = fopen (Slrn_Newsrc_File, "w", "fop=cif")))
2675 #else
2676 	if (NULL == (fp = fopen (Slrn_Newsrc_File, "w")))
2677 #endif
2678 	  {
2679 	     slrn_exit_error (_("Unable to create %s."), Slrn_Newsrc_File);
2680 	  }
2681 	fclose (fp);
2682 	fputs (_("\n--The next step may take a while if the NNTP connection is slow.--\n\n"), stdout);
2683 	fprintf (stdout, _("Creating %s."), Slrn_Newsrc_File);
2684 	fflush (stdout);
2685      }
2686 
2687    if (create_flag || Slrn_List_Active_File)
2688      {
2689 	read_and_parse_active (create_flag);
2690      }
2691 
2692    if ((create_flag == 0)
2693        && (-1 == read_and_parse_newsrc_file ()))
2694      slrn_exit_error (_("Unable to read newsrc file"));
2695 
2696    insert_new_groups ();
2697 
2698    Slrn_Group_Current_Group = Groups;
2699 
2700    init_group_win_struct ();
2701 
2702    toggle_hide_groups ();
2703 
2704    /* Unhide the new groups.  Do it here so that if there are no unread
2705     * articles, it will be visible but also enables user to toggle them
2706     * so that they will become invisble again.
2707     */
2708    Slrn_Group_Current_Group = Groups;
2709    while ((Slrn_Group_Current_Group != NULL) &&
2710 	  (create_flag || (Slrn_Group_Current_Group->flags & GROUP_NEW_GROUP_FLAG)))
2711      {
2712 	Slrn_Group_Current_Group->flags &= ~GROUP_HIDDEN;
2713 	Slrn_Group_Current_Group = Slrn_Group_Current_Group->next;
2714      }
2715 
2716    Slrn_Group_Current_Group = Groups;
2717    while ((Slrn_Group_Current_Group != NULL)
2718 	  && (Slrn_Group_Current_Group->flags & GROUP_HIDDEN))
2719      Slrn_Group_Current_Group = Slrn_Group_Current_Group->next;
2720 
2721    find_line_num ();
2722 
2723    group_bob ();
2724    return 0;
2725 }
2726 
2727 /*}}}*/
2728 
slrn_write_newsrc(int auto_save)2729 int slrn_write_newsrc (int auto_save) /*{{{*/
2730 /* auto_save == 0: save to newsrc, == 1: save to autosave file. */
2731 {
2732    Slrn_Group_Type *g;
2733    Slrn_Range_Type *r;
2734    char autosave_file[SLRN_MAX_PATH_LEN];
2735    char *newsrc_filename;
2736    static FILE *fp;
2737    int max;
2738    struct stat filestat;
2739    int stat_worked = 0;
2740    int have_backup = 0;
2741 
2742    slrn_init_hangup_signals (0);
2743 
2744    if (Slrn_Groups_Dirty == 0)
2745      {
2746 	slrn_init_hangup_signals (1);
2747 	return 0;
2748      }
2749 
2750    /* In case of hangup and we were writing the file, make sure it is closed.
2751     * This will not hurt since we are going to do it again anyway.
2752     */
2753    if (fp != NULL)
2754      {
2755 	slrn_fclose (fp);
2756 	fp = NULL;
2757      }
2758 
2759 #ifdef VMS
2760    slrn_snprintf (autosave_file, sizeof (autosave_file), "%s-as",
2761 		  Slrn_Newsrc_File);
2762 #else
2763 # ifdef SLRN_USE_OS2_FAT
2764    slrn_os2_make_fat (autosave_file, sizeof (autosave_file), Slrn_Newsrc_File,
2765 		      ".as");
2766 # else
2767    slrn_snprintf (autosave_file, sizeof (autosave_file), "%s.as",
2768 		  Slrn_Newsrc_File);
2769 # endif
2770 #endif
2771 
2772    if ((auto_save == 1) && (Slrn_No_Autosave != 2))
2773      {
2774 	if (Slrn_No_Autosave)
2775 	  {
2776 	     slrn_init_hangup_signals (1);
2777 	     return 0;
2778 	  }
2779 
2780 	newsrc_filename = autosave_file;
2781 	stat_worked = (-1 != stat (newsrc_filename, &filestat));
2782      }
2783    else
2784      newsrc_filename = Slrn_Newsrc_File;
2785 
2786    slrn_message_now (_("Writing %s ..."), newsrc_filename);
2787    /* Try to preserve .newsrc permissions and owner/group.  This also
2788     * confirms existence of file.  If an autosave file does not (yet)
2789     * exist, use permissions of the .newsrc.
2790     */
2791    if (stat_worked == 0)
2792      stat_worked = (-1 != stat (Slrn_Newsrc_File, &filestat));
2793 
2794    if (newsrc_filename == Slrn_Newsrc_File)
2795      {
2796 	/* Create a temp backup file.  Delete it later if user
2797 	 * does not want backups.
2798 	 */
2799 	have_backup = (0 == slrn_create_backup (newsrc_filename));
2800      }
2801 
2802    if (NULL == (fp = fopen (newsrc_filename, "w")))
2803      {
2804 	slrn_error (_("Unable to open file %s for writing."), newsrc_filename);
2805 	if (have_backup) slrn_restore_backup (newsrc_filename);
2806 	slrn_init_hangup_signals (1);
2807 	return -1;
2808      }
2809 
2810 #ifdef __unix__
2811 # if !defined(IBMPC_SYSTEM)
2812    /* Try to preserve .newsrc permissions and owner/group */
2813 #  ifndef S_IRUSR
2814 #   define S_IRUSR 0400
2815 #   define S_IWUSR 0200
2816 #   define S_IXUSR 0100
2817 #  endif
2818    if (stat_worked)
2819      {
2820 	if (-1 == chmod (newsrc_filename, filestat.st_mode & (S_IRUSR | S_IWUSR | S_IXUSR)))
2821 	  (void) chmod (newsrc_filename, S_IWUSR | S_IRUSR);
2822 
2823 	(void) chown (newsrc_filename, filestat.st_uid, filestat.st_gid);
2824      }
2825 # endif
2826 #endif
2827 
2828    g = Groups;
2829    while (g != NULL)
2830      {
2831 	if ((g->flags & GROUP_UNSUBSCRIBED) &&
2832 	    ((Slrn_Write_Newsrc_Flags == 1) ||
2833 	    ((Slrn_Write_Newsrc_Flags == 2) && (g->range.next == NULL))))
2834 	  {
2835 	     g = g->next;
2836 	     continue;
2837 	  }
2838 	if ((EOF == fputs (g->group_name, fp)) ||
2839 	    (EOF == putc (((g->flags & GROUP_UNSUBSCRIBED) ? '!' : ':'), fp)))
2840 	  goto write_error;
2841 
2842 	r = g->range.next;
2843 	max = g->range.max;
2844 	if (r != NULL)
2845 	  {
2846 	     NNTP_Artnum_Type max_newsrc_number=0;
2847 	     /* Make this check because the unsubscribed group
2848 	      * range may not have been initialized from the server.
2849 	      */
2850 	     if ((max != -1) && (g->range.min != -1)
2851 		 && ((g->flags & GROUP_UNSUBSCRIBED) == 0))
2852 	       max_newsrc_number = max;
2853 
2854 	     if (EOF == putc (' ', fp))
2855 	       goto write_error;
2856 
2857 	     if (-1 == slrn_ranges_to_newsrc_file (r, max_newsrc_number, fp))
2858 	       goto write_error;
2859 	  }
2860 	else if (g->range.min == 2)
2861 	  {
2862 	     if (EOF == fputs (" 1", fp))
2863 	       goto write_error;
2864 	  }
2865 	else if (g->range.min > 2)
2866 	  {
2867 	     if (fprintf (fp, (" 1-" NNTP_FMT_ARTNUM), g->range.min - 1) < 0)
2868 	       goto write_error;
2869 	  }
2870 
2871 	if (EOF == putc ('\n', fp))
2872 	  goto write_error;
2873 	g = g->next;
2874      }
2875 
2876    if (-1 == slrn_fclose (fp))
2877      goto write_error;
2878    fp = NULL;
2879 
2880    if (Slrn_No_Backups)
2881      {
2882 	if (have_backup) slrn_delete_backup (newsrc_filename);
2883      }
2884 
2885    if (newsrc_filename == Slrn_Newsrc_File)
2886      {
2887 	Slrn_Groups_Dirty = 0;
2888 	if (Slrn_No_Autosave == 0)
2889 	  slrn_delete_file (autosave_file);
2890      }
2891    if (Slrn_TT_Initialized & SLRN_TTY_INIT)
2892      slrn_message (_("Writing %s ... done."), newsrc_filename);
2893 
2894    slrn_init_hangup_signals (1);
2895    return 0;
2896 
2897    write_error:
2898 
2899    slrn_fclose (fp); fp = NULL;
2900    slrn_error (_("Write to %s failed! Disk Full?"), newsrc_filename);
2901 
2902    /* Put back orginal file */
2903    if (have_backup) slrn_restore_backup (newsrc_filename);
2904 
2905    slrn_init_hangup_signals (1);
2906    return -1;
2907 }
2908 
2909 /*}}}*/
2910 
2911 /*}}}*/
2912 
group_quick_help(void)2913 static void group_quick_help (void) /*{{{*/
2914 {
2915    char *hlp = _("SPC:Select  p:Post  c:CatchUp  l:List  q:Quit  ^R:Redraw  (u)s:(Un)Subscribe");
2916 
2917    if (Slrn_Batch)
2918      return;
2919 
2920    if (Slrn_Group_Help_Line != NULL)
2921      hlp = Slrn_Group_Help_Line;
2922 
2923    if (0 == slrn_message ("%s", hlp))
2924      Slrn_Message_Present = 0;
2925 }
2926 
2927 /*}}}*/
2928 
group_display_format_cb(char ch,void * data,int * len,int * color)2929 static char *group_display_format_cb (char ch, void *data, int *len, int *color) /*{{{*/
2930 {
2931    Slrn_Group_Type *g = (Slrn_Group_Type*) data;
2932    static char buf[512];
2933    char *retval = buf;
2934 
2935    *retval = 0;
2936    if (g == NULL) return retval;
2937 
2938    switch (ch)
2939      {
2940       case 'F':
2941 	if (g->flags & GROUP_UNSUBSCRIBED) retval = "U";
2942 	else if (g->flags & GROUP_NEW_GROUP_FLAG) retval = "N";
2943 	else retval = " ";
2944 	*len = 1;
2945 	break;
2946       case 'd':
2947 	if (NULL == (retval = g->descript))
2948 	  retval = " ";
2949 	if (color != NULL) *color = GROUP_DESCR_COLOR;
2950 	break;
2951       case 'h':
2952 	if (g->range.max == -1)
2953 	  {
2954 	     retval = "?";
2955 	     *len = 1;
2956 	  }
2957 	else
2958 #ifdef HAVE_ANSI_SPRINTF
2959 	  *len =
2960 #endif
2961 	  sprintf (buf, NNTP_FMT_ARTNUM, g->range.max); /* safe */
2962 	break;
2963       case 'l':
2964 	if (g->range.min == -1)
2965 	  {
2966 	     retval = "?";
2967 	     *len = 1;
2968 	  }
2969 	else
2970 #ifdef HAVE_ANSI_SPRINTF
2971 	  *len =
2972 #endif
2973 	  sprintf (buf, NNTP_FMT_ARTNUM, g->range.min); /* safe */
2974 	break;
2975       case 'n':
2976 	retval = g->group_name;
2977 	if (color != NULL) *color = GROUP_COLOR;
2978 	break;
2979       case 't':
2980 	if (g->range.max == -1)
2981 	  {
2982 	     retval = "?";
2983 	     *len = 1;
2984 	  }
2985 	else
2986 #ifdef HAVE_ANSI_SPRINTF
2987 	  *len =
2988 #endif
2989 	  sprintf (buf, NNTP_FMT_ARTNUM, g->range.max - g->range.min + 1); /* safe */
2990 	break;
2991       case 'u':
2992 #ifdef HAVE_ANSI_SPRINTF
2993 	*len =
2994 #endif
2995 	sprintf (buf, NNTP_FMT_ARTNUM, g->unread); /* safe */
2996 	break;
2997      }
2998 
2999    return retval;
3000 }
3001 /*}}}*/
3002 
group_status_line_cb(char ch,void * data,int * len,int * color)3003 static char *group_status_line_cb (char ch, void *data, int *len, int *color) /*{{{*/
3004 {
3005    static char buf[66];
3006    char *retval = NULL;
3007 
3008    (void) data; (void) color; /* we currently don't use these */
3009 
3010    switch (ch)
3011      {
3012       case 'L':
3013 	retval = slrn_print_percent (buf, &Group_Window, 1);
3014 	break;
3015       case 'P':
3016 	retval = slrn_print_percent (buf, &Group_Window, 0);
3017 	break;
3018       case 'D':
3019 	retval = Slrn_Groups_Dirty ? "*" : "-";
3020 	*len = 1;
3021 	break;
3022       case 's':
3023 	retval = Slrn_Server_Obj->sv_name;
3024 	break;
3025       default:
3026 	if (Slrn_Group_Current_Group != NULL)
3027 	  retval = group_display_format_cb (ch,
3028 					    (void *) Slrn_Group_Current_Group,
3029 					    len, NULL);
3030 	break;
3031      }
3032 
3033    return retval;
3034 }
3035 /*}}}*/
3036 
group_update_screen(void)3037 static void group_update_screen (void) /*{{{*/
3038 {
3039    Slrn_Group_Type *g;
3040    char *fmt = Group_Display_Formats[Group_Format_Number];
3041    int height = (int) Group_Window.nrows;
3042    int row;
3043 
3044    /* erase last cursor */
3045    if (Last_Cursor_Row && !Slrn_Full_Screen_Update)
3046      {
3047 	SLsmg_gotorc (Last_Cursor_Row, 0);
3048 	SLsmg_write_string ("  ");
3049      }
3050 
3051    g = (Slrn_Group_Type *) Group_Window.top_window_line;
3052    (void) SLscroll_find_top (&Group_Window);
3053 
3054    if (g != (Slrn_Group_Type *) Group_Window.top_window_line)
3055      {
3056 	Slrn_Full_Screen_Update = 1;
3057 	g = (Slrn_Group_Type *) Group_Window.top_window_line;
3058      }
3059 
3060    if ((fmt == NULL) || (*fmt == 0))
3061      fmt = "  %F%-5u  %n%45g%d";
3062 
3063    for (row = 0; row < height; row++)
3064      {
3065 	while ((g != NULL) && (g->flags & GROUP_HIDDEN))
3066 	  g = g->next;
3067 
3068 	if (g != NULL)
3069 	  {
3070 	     if (Slrn_Full_Screen_Update || (g->flags & GROUP_TOUCHED))
3071 	       {
3072 		  slrn_custom_printf (fmt, group_display_format_cb,
3073 				      (void *) g, row + 1, 0);
3074 		  g->flags &= ~GROUP_TOUCHED;
3075 	       }
3076 	     g = g->next;
3077 	  }
3078 	else if (Slrn_Full_Screen_Update)
3079 	  {
3080 	     SLsmg_gotorc (row + 1, 0);
3081 	     SLsmg_erase_eol ();
3082 	  }
3083      }
3084 
3085    fmt = Slrn_Group_Status_Line;
3086    if ((fmt == NULL) || (*fmt == 0))
3087      fmt = _("-%D-News Groups: %s %-20g -- %L (%P)");
3088    slrn_custom_printf (fmt, group_status_line_cb, NULL, SLtt_Screen_Rows - 2,
3089 		       STATUS_COLOR);
3090 
3091    if (Slrn_Use_Mouse) slrn_update_group_menu ();
3092    else slrn_update_top_status_line ();
3093 
3094    if (Slrn_Message_Present == 0) group_quick_help ();
3095 
3096    Last_Cursor_Row = 1 + Group_Window.window_row;
3097    SLsmg_gotorc (Last_Cursor_Row, 0);
3098 
3099    slrn_set_color (CURSOR_COLOR);
3100 
3101 #if SLANG_VERSION > 10003
3102    if (Slrn_Display_Cursor_Bar)
3103      SLsmg_set_color_in_region (CURSOR_COLOR, Last_Cursor_Row, 0, 1, SLtt_Screen_Cols);
3104    else
3105 #endif
3106      SLsmg_write_string ("->");
3107 
3108    slrn_set_color (0);
3109    Slrn_Full_Screen_Update = 0;
3110 }
3111 
3112 /*}}}*/
3113 
3114 /* intrinsic functions */
slrn_intr_get_group_order(void)3115 void slrn_intr_get_group_order (void) /*{{{*/
3116 {
3117    Slrn_Group_Type *g;
3118    SLang_Array_Type *retval;
3119    int n = 0;
3120 
3121    g = Groups;
3122    while (g != NULL)
3123      {
3124 	n++;
3125 	g = g->next;
3126      }
3127 
3128    retval = SLang_create_array (SLANG_STRING_TYPE, 0, NULL, &n, 1);
3129    if (retval == NULL)
3130      return;
3131 
3132    n = 0;
3133    g = Groups;
3134    while (g != NULL)
3135      {
3136 	if (-1 == SLang_set_array_element (retval, &n, &g->group_name))
3137 	  {
3138 	     SLang_free_array (retval);
3139 	     return;
3140 	  }
3141 	n++;
3142 	g = g->next;
3143      }
3144 
3145    (void) SLang_push_array (retval, 1);
3146 }
3147 /*}}}*/
3148 
slrn_intr_set_group_order(void)3149 void slrn_intr_set_group_order (void) /*{{{*/
3150 {
3151    SLang_Array_Type *at;
3152    Slrn_Group_Type *last, *rest, *g;
3153    int i, rows;
3154 
3155    if (-1 == SLang_pop_array_of_type (&at, SLANG_STRING_TYPE))
3156      {
3157 	slrn_error (_("Array of string expected."));
3158 	return;
3159      }
3160 
3161    if (at->num_dims != 1)
3162      {
3163 	slrn_error (_("One-dimensional array expected."));
3164 	SLang_free_array (at);
3165 	return;
3166      }
3167 
3168    if ((Groups == NULL) || (Groups->next == NULL))
3169      {
3170 	SLang_free_array (at);
3171 	return;
3172      }
3173 
3174    rows = at->dims[0];
3175    last = NULL;
3176    rest = Groups;
3177 
3178    for (i = 0; i < rows; i++)
3179      {
3180 	char *name;
3181 
3182 	if ((-1 == SLang_get_array_element (at, &i, &name))
3183 	    || (name == NULL))
3184 	  continue;
3185 
3186 	if (NULL == (g = find_group_entry (name, strlen (name))))
3187 	  continue;
3188 
3189 	/* It is possible for name to occur multiple times in the array.
3190 	 * So we have to be careful.  There are two lists here: the part
3191 	 * that runs from Groups to last, and the rest. The first group
3192 	 * is the already sorted part, and the second is unsorted.
3193 	 * Ordinarily g will come from the second (rest), but if name appears
3194 	 * more than once in the array, it will come from the first part when
3195 	 * seen the second time.
3196 	 */
3197 	if (g == last)		       /* at tail of first part */
3198 	  continue;
3199 
3200 	if (g == Groups)	       /* at head of first part */
3201 	  Groups = g->next;
3202 
3203 	if (g == rest)		       /* at head of second part */
3204 	  rest = rest->next;
3205 
3206 	/* Remove it from its current location */
3207 	if (g->next != NULL)
3208 	  g->next->prev = g->prev;
3209 	if (g->prev != NULL)
3210 	  g->prev->next = g->next;
3211 
3212 	g->prev = last;		       /* append it to first part */
3213 
3214 	if (last == NULL)	       /* the head of the first part */
3215 	  Groups = g;
3216 	else
3217 	  last->next = g;
3218 
3219 	g->next = rest;		       /* connect up the rest */
3220 	if (rest != NULL)
3221 	  rest->prev = g;
3222 
3223 	last = g;
3224      }
3225 
3226    SLang_free_array (at);
3227 
3228    find_line_num ();
3229    Slrn_Full_Screen_Update = 1;
3230    Slrn_Groups_Dirty = 1;
3231 }
3232 /*}}}*/
3233