ftp.nice.ch/pub/next/unix/network/news/slrn0.9.0.0.s.tar.gz#/slrn/src/group.c

This is group.c in view mode; [Download] [Up]

/*  Copyright (c) 1995 John E. Davis (davis@space.mit.edu)
 *  All rights reserved.
 */
#include "config.h"
#include "features.h"

#include <stdio.h>
#include <string.h>
#include <time.h>
#ifndef VMS
# include <sys/types.h>
# include <sys/stat.h>
#endif

#ifdef HAVE_STDLIB_H
# include <stdlib.h>
#endif

#ifdef HAVE_UNISTD_H
#include <unistd.h>
#endif


#include <slang.h>
#include "jdmacros.h"

/* #include "clientlib.h" */
#include "slrn.h"
#include "group.h"
#include "art.h"
#include "misc.h"
#include "post.h"
#include "server.h"
#include "hash.h"
#include "score.h"
#include "menu.h"

#define GROUP_HASH_TABLE_SIZE 1250
static Slrn_Group_Type *Group_Hash_Table [GROUP_HASH_TABLE_SIZE];

int Slrn_Query_Group_Cutoff = 100;
int Slrn_Groups_Dirty;	       /* greater than 0 if need to write newsrc */
int Slrn_List_Active_File = 0;
int Slrn_Use_Xgtitle = 0;
int Slrn_Write_Newsrc_Flags = 0;       /* if 1, do not save unsubscribed 
					* if 2, do not save new unsubscribed.
					*/

int Slrn_Group_Description_Column = 40;/* column where group descr start */
static Slrn_Group_Type *Groups, *Current_Group;

static Slrn_Group_Type *Top_Group;		       /* group at top of screen */
static Slrn_Group_Type *Bottom_Group;       /* group at bottom of screen */
static int Last_Cursor_Row;
static int Num_Groups;
static int Line_Num;
static int Groups_Hidden;	       /* if true, hide groups with no arts */

int Slrn_Group_Window_Size;		       /* num rows in group window */
int Slrn_Group_Display_Descriptions = 1;
int Slrn_No_Backups = 0;
int Slrn_Prompt_Next_Group = 1;

static void group_update_screen (void);

static void quick_help (void)
{
   if (0 == slrn_message (
		 "SPC:Select  p:Post  c:CatchUp  l:List  q:Quit  ^R:Redraw  (u)s:(Un)Subscribe"))
     Slrn_Message_Present = 0;
}

#if 0
static void slrn_dump_groups (void)
{
   Slrn_Group_Type *g;
   
   g = Groups;
   while (g != NULL)
     {
	fprintf (stdout, "%s %d-%d\n", g->name, g->range.min, g->range.max);
	g = g->next;
     }
   fflush (stdout);
}
#endif

/* Note: This routine is NOT very robust.  It assumes that this function
 * is called in an ordered way such that the implied range is always increasing.
 * This is why the range is re-built in art.c:update_ranges.  Yes, it is ugly.
 * See also slrn_group_mark_article_as_read for something more random.
 */
void slrn_add_group_ranges (Slrn_Group_Type *g, int min, int max)
{
   Slrn_Range_Type *r, *next;
   int unread;
   
   if ((max < min) || (g == NULL)) return;
   
   /* The first one is range of articles on server so expand max to cover
    * the range of articles nolonger available.
    */
   next = &g->range;
   if (max < next->min) 
     {
	/* If we have already expanded the range up to range currently available
	 * at server and we are now trying to add another range below available
	 * range, do not bother.
	 */
	if (next->next != NULL) return;
	max = next->min - 1;
     }
   
   /* Count number unread */
   unread = next->max;
   while (next->next != NULL)
     {
	next = next->next;
	unread -= next->max - next->min + 1;
     }
   
   /* check to see if a merge is possible */
   if ((min <= next->max + 1)
       && (next != &g->range))
     {
	next->max = max;
     }
   else
     {
	if (NULL == (r = (Slrn_Range_Type *) SLMALLOC (sizeof(Slrn_Range_Type))))
	  {
	     slrn_exit_error ("Memory allocation error.");
	  }
	
	r->next = next->next;
	next->next = r;
	r->prev = next;
	
	r->min = min;
	r->max = max;
	
	/* For this case, min should be 1 */
	if (next == &g->range)
	  {
	     min = r->min = 1;
	  }
     }
   
   unread -= max - min + 1;
   
   if (unread < 0) unread = 0;
   g->unread = unread;
   Slrn_Groups_Dirty = 1;
}

static void group_mark_article_as_read (Slrn_Group_Type *g, long num)
{
   Slrn_Range_Type *r, *r1, *newr;
   
   r1 = &g->range;
   if (r1->max < num)  /* not at server yet so update our data */
     {
	r1->max = num;
	g->unread += 1;
     }
   
   r = r1->next;
   
   while (r != NULL)
     {
	/* Already read */
	if ((num <= r->max) && (num >= r->min)) return;
	if (num < r->min) break;
	r1 = r;
	r = r->next;
     }
   
   if (g->unread > 0) g->unread -= 1;
   Slrn_Groups_Dirty = 1;
   if ((r != NULL) && (r->min == num + 1))
     {
	r->min = num;
	return;
     }
   
   if ((r1->max + 1 == num) && (r1 != &g->range))
     {
	r1->max = num;
	return;
     }
   
   if (NULL == (newr = (Slrn_Range_Type *) SLMALLOC (sizeof (Slrn_Range_Type))))
     {
	slrn_exit_error ("Memory allocation error");
     }
   
   newr->min = newr->max = num;
   newr->next = r;
   if (r != NULL) r->prev = newr;
   newr->prev = r1;
   r1->next = newr;
}

void slrn_mark_article_as_read (char *group, long num)
{
   Slrn_Group_Type *g;
   unsigned long hash;
   
   if (group == NULL)
     {
	group_mark_article_as_read (Current_Group, num);
	return;
     }
   
   hash = slrn_compute_hash ((unsigned char *) group,
			     (unsigned char *) group + strlen (group));
   
   g = Group_Hash_Table[hash % GROUP_HASH_TABLE_SIZE];
   
   while (g != NULL)
     {
	if ((g->hash == hash) && !strcmp (group, g->name))
	  {
	     /* If it looks like we have read this group, mark it read. */
	     if (((g->flags & GROUP_UNSUBSCRIBED) == 0)
		 || (g->range.next != NULL))
	       group_mark_article_as_read (g, num);
	     
	     break;
	  }
	g = g->hash_next;
     }
}

static Slrn_Group_Type *find_group_entry (char *name, unsigned int len)
{
   int hash_index;
   unsigned long hash;
   Slrn_Group_Type *g;
   
   hash = slrn_compute_hash ((unsigned char *) name,
			     (unsigned char *) name + len);
   
   hash_index = hash % GROUP_HASH_TABLE_SIZE;
   g = Group_Hash_Table[hash_index];
   
   while (g != NULL)
     {
	if ((g->hash == hash) && !strncmp (name, g->name, len))
	  {
	     if (len == strlen (g->name)) break;;
	  }
	g = g->hash_next;
     }
   return g;
}

static Slrn_Group_Type *create_group_entry (char *name, unsigned int len,
				       int min, int max, int query_server,
				       int skip_find)
{
   int hash_index;
   unsigned long hash;
   Slrn_Group_Type *g;
   
   if (skip_find == 0)
     {
	g = find_group_entry (name, len);
	if (g != NULL) return g;
     }
   
   if (NULL == (g = (Slrn_Group_Type *) SLMALLOC (sizeof (Slrn_Group_Type))))
     {
	slrn_exit_error ("Memory allocation failure.");
     }
   SLMEMSET ((char *) g, 0, sizeof (Slrn_Group_Type));
   
   if (len > MAX_GROUP_NAME_LEN) len = MAX_GROUP_NAME_LEN;
   strncpy (g->name, name, len);
   g->name [len] = 0;
   
   if (query_server)
     {
	if (0 != Slrn_Server_Obj->sv_select_group (g->name, &min, &max))
	  {
	     Slrn_Groups_Dirty = 1;
	     
	     if (Slrn_TT_Initialized == 0)
	       slrn_error ("Group %s is bogus.\r\n", g->name);
	     else
	       slrn_error ("Group %s is bogus.\n", g->name);
	     
	     SLFREE (g);
	     return NULL;
	  }
     }
   g->range.min = min;
   g->range.max = max;
   if (max > 0)
     g->unread = max - min + 1;
   else g->unread = 0;
   
   g->flags = (GROUP_UNSUBSCRIBED | GROUP_HIDDEN);
   
   hash = slrn_compute_hash ((unsigned char *) name,
			     (unsigned char *) name + len);
   hash_index = hash % GROUP_HASH_TABLE_SIZE;
   
   g->hash = hash;
   g->hash_next = Group_Hash_Table[hash_index];
   Group_Hash_Table[hash_index] = g;
   
   if (Groups == NULL)
     {
	Current_Group = Groups = g;
     }
   else
     {
	if (Current_Group == NULL) Current_Group = Groups;
	g->next = Current_Group->next;
	if (g->next != NULL) g->next->prev = g;
	Current_Group->next = g;
	g->prev = Current_Group;
     }
   Current_Group = g;
   return g;
}

static int parse_active_line (char *name, unsigned int *lenp,
			      int *minp, int *maxp)
{
   char *p;
   
   p = name;
   while (*p > ' ') p++;
   *lenp = (unsigned int) (p - name);
   
   while (*p == ' ') p++;
   *maxp = atoi (p);
   while (*p > ' ') p++;  while (*p == ' ') p++;
   *minp = atoi(p);
   if (*maxp < *minp) *minp = *maxp + 1;
   return 0;
}

static int add_group (char *name, unsigned int len,
		      unsigned int subscribe_flag, int create_flag)
{
   char ch;
   Slrn_Group_Type *g;
   
   g = find_group_entry (name, len);
   if (g == NULL)
     {
	if (Slrn_List_Active_File)
	  {
	     char namebuf[MAX_GROUP_NAME_LEN + 1];
	     if (len > MAX_GROUP_NAME_LEN) len = MAX_GROUP_NAME_LEN;
	     strncpy (namebuf, name, len);
	     namebuf[len] = 0;
	     fprintf (stderr, "Group %s is bogus,  Removing it.\r\n", namebuf);
	     return -1;
	  }
	else g = create_group_entry (name, len, -1, -1,
				     !(subscribe_flag & GROUP_UNSUBSCRIBED),
				     0);
	if (g == NULL) return -1;
     }
   Slrn_Groups_Dirty = 1;
   
   /* If we have already processed this, then the group is duplicated in
    * the newsrc file.  Throw it out now.
    */
   if (g->flags & GROUP_PROCESSED) return -1;
   
   Current_Group = g;
   g->flags = subscribe_flag;
   g->flags |= GROUP_PROCESSED;
   
   if (subscribe_flag & GROUP_UNSUBSCRIBED)
     {
	g->unread = 0;
	g->flags |= GROUP_HIDDEN;
	/* if (Slrn_List_Active_File == 0) return 0; */
     }
   
   if (create_flag) return 0;
   
   /* find ranges for this */
   name += len;			       /* skip past name */
   if (*name) name++;			       /* skip colon */
   while (1)
     {
	int min, max;
	/* skip white space and delimiters */
	while (((ch = *name) != 0) && ((ch <= ' ') || (ch == ','))) name++;
	if ((ch < '0') || (ch > '9')) break;
	min = atoi (name++);
	while (((ch = *name) != 0) && (ch >= '0') && (ch <= '9')) name++;
	if (ch == '-')
	  {
	     name++;
	     max = atoi (name);
	     while (((ch = *name) != 0) && (ch >= '0') && (ch <= '9')) name++;
	  }
	else max = min;
	
	slrn_add_group_ranges (Current_Group, min, max);
     }
   return 0;
}


static void find_line_num (void)
{
   Slrn_Group_Type *g;
   int n;
   
   n = 1;
   if (Current_Group == NULL) Current_Group = Groups;
   
   /* Move to a group that is not hidden */
   while (Current_Group != NULL)
     {
	if ((Current_Group->flags & GROUP_HIDDEN) == 0) break;
	Current_Group = Current_Group->next;
     }
   
   if (Current_Group != NULL)
     {
	g = Groups;
	while (g != Current_Group)
	  {
	     if (0 == (g->flags & GROUP_HIDDEN)) n++;
	     g = g->next;
	  }
     }
   else if (Groups_Hidden == 0)
     {
	Current_Group = Groups;
     }
   
   Line_Num = n;
   g = Current_Group;
   Num_Groups = n - 1;
   while (g != NULL)
     {
	if (0 == (g->flags & GROUP_HIDDEN)) Num_Groups++;
	g = g->next;
     }
}


static void toggle_hide_groups (void)
{
   Slrn_Group_Type *g;
   int n = 0;
   
   Groups_Hidden = !Groups_Hidden;
   
   g = Groups;
   
   if (Groups_Hidden)
     {
	while (g != NULL)
	  {
	     if ((g->unread == 0)
		 && ((g->flags & GROUP_UNSUBSCRIBED) == 0))
	       g->flags |= GROUP_HIDDEN;
	     
	     g = g->next;
	  }
     }
   else
     {
	while (g != NULL)
	  {
	     if ((g->unread == 0)
		 && ((g->flags & GROUP_UNSUBSCRIBED) == 0))
	       g->flags &= ~GROUP_HIDDEN;
	     
	     g = g->next;
	  }
     }
   
   g = Groups;
   n = 0;
   while (g != NULL)
     {
	if ((g->flags & GROUP_HIDDEN) == 0) n++;
	g = g->next;
     }
   Num_Groups = n;
   
   g = Current_Group;
   while ((g != NULL) && (g->flags & GROUP_HIDDEN)) g = g->next;
   if ((g == NULL) && (Current_Group != NULL))
     {
	g = Current_Group -> prev;
	while ((g != NULL) && (g->flags & GROUP_HIDDEN)) g = g->prev;
     }
   Current_Group = g;
   
   find_line_num ();
   
   Top_Group = NULL;
   Slrn_Full_Screen_Update = 1;
}


static SLRegexp_Type *read_group_regexp (char *prompt)
{
   static char pattern[256];
   
   if (slrn_read_input (prompt, pattern, 1) <= 0) return NULL;
   
   return slrn_compile_regexp_pattern (slrn_fix_regexp (pattern));
}

static Slrn_Group_Type *process_xgtitle_info (void)
{
   char buf [NNTP_BUFFER_SIZE];
   Slrn_Group_Type *first = NULL, *save = Current_Group;
   
   while (Slrn_Server_Obj->sv_read_line(buf, sizeof(buf)) != NULL)
     {
	char *b, ch;
	unsigned int len;
	Slrn_Group_Type *g;
	
	b = buf;
	while (((ch = *b) != 0)
	       && (ch != ' ') 
	       && (ch != '\n') 
	       && (ch != '\t'))
	  b++;
	
	len = (unsigned int) (b - buf);
	if (len == 0) continue;
	*b = 0;
	
	g = create_group_entry (buf, len,
				-1, -1, 0, 0);

	if (g != NULL)
	  {
	     g->flags &= ~GROUP_HIDDEN;
	     if ((first == NULL) && (g->flags & GROUP_UNSUBSCRIBED))
	       first = g;
	  }
     }
   if (save != Current_Group)
     {
	Current_Group = save;
	find_line_num ();
     }
   return first;
}

static void toggle_list_all_groups1 (int hide_flag)
{
   Slrn_Group_Type *g, *first_found = NULL;
   int n = 0;
   static int all_hidden = 1;
   
   g = Groups;
   
   if (hide_flag != -1)
     {
	all_hidden = hide_flag;
     }
   else all_hidden = !all_hidden;
   
   if (all_hidden)
     {
	while (g != NULL)
	  {
	     if (g->flags & GROUP_UNSUBSCRIBED) g->flags |= GROUP_HIDDEN;
	     g = g->next;
	  }
     }
#if 0
   else if (prompt == 0)
     {
	while (g != NULL)
	  {
	     if (g->flags & GROUP_UNSUBSCRIBED)
	       {
		  g->flags &= ~GROUP_HIDDEN;
	       }
	     g = g->next;
	  }
     }
#endif
   else
     {
	SLRegexp_Type *re;
	  
	if (NULL == (re = read_group_regexp ("List Groups (e.g., comp*unix*): ")))
	  {
	     all_hidden = 1;
	     return;
	  }
	
	if ((Slrn_List_Active_File == 0)
	    && Slrn_Use_Xgtitle
	    && (-1 != Slrn_Server_Obj->sv_xgtitle_cmd ((char *) re->pat)))
	  {
	     first_found = process_xgtitle_info ();
	  }
	else while (g != NULL)
	  {
	     if (g->flags & GROUP_UNSUBSCRIBED)
	       {
		  if (NULL != slrn_regexp_match (re, g->name))
		    {
		       if (first_found == NULL) first_found = g;
		       g->flags &= ~GROUP_HIDDEN;
		    }
	       }
	     g = g->next;
	  }
     }
   
   g = Groups;
   n = 0;
   while (g != NULL)
     {
	if ((g->flags & GROUP_HIDDEN) == 0) n++;
	g = g->next;
     }
   Num_Groups = n;
   
   g = Current_Group;
   if (first_found != NULL)
     {
	g = first_found;
     }
   else
     {
	while ((g != NULL) && (g->flags & GROUP_HIDDEN)) g = g->next;
	if ((g == NULL) && (Current_Group != NULL))
	  {
	     g = Current_Group -> prev;
	     while ((g != NULL) && (g->flags & GROUP_HIDDEN)) g = g->prev;
	  }
     }
   Current_Group = g;
   
   if (g != NULL)
     {
	g = Groups;
	n = 1;
	while (g != Current_Group)
	  {
	     if (0 == (g->flags & GROUP_HIDDEN)) n++;
	     g = g->next;
	  }
     }
   else n = 1;
   
   Top_Group = NULL;
   Slrn_Full_Screen_Update = 1;
   
   if ((all_hidden == 0) && (Current_Group == NULL))
     {
	Current_Group = Groups;
	if ((Current_Group != NULL)
	    && (Current_Group->flags & GROUP_HIDDEN))
	  {
	     Current_Group = NULL;
	  }
	n = 1;
     }
   Line_Num = n;
}



static int find_group (char *name)
{
   Slrn_Group_Type *g = find_group_entry (name, strlen (name));
   if (g == NULL) return 0;
   
   g->flags &= ~GROUP_HIDDEN;
   Current_Group = g;
   find_line_num ();
   return 1;
}

static void group_search (void)
{
   static char search_str[256];
   SLsearch_Type st;
   Slrn_Group_Type *g;
   int n = 0, wrapped = 0;
   
   g = Current_Group;
   if (g == NULL) return;
   if (slrn_read_input ("Search: ", search_str, 1) <= 0) return;
   
   SLsearch_init (search_str, 1, 0, &st);
   
   do
     {
	g = g->next;
	if (g == NULL)
	  {
	     g = Groups;
	     n = 0;
	     wrapped = 1;
	  }
	
	if ((g->flags & GROUP_HIDDEN) == 0)
	  {
	     n++;
	     
	     if (NULL != SLsearch ((unsigned char *) g->name,
				   (unsigned char *) g->name + strlen (g->name),
				   &st))
	       {
		  break;
	       }
	  }
     }
   while (g != Current_Group);
   
   if (g == Current_Group) slrn_error ("Not found.");
   else
     {
	Current_Group = g;
	if (wrapped)
	  {
	     Line_Num = 0;
	     slrn_message ("Search wrapped.");
	  }
	Line_Num += n;
     }
}


static void add_group_cmd (void)
{
   char group[256];
   
   *group = 0;
   if (slrn_read_input ("Add group: ", group, 1) > 0)
     {
	if (!find_group (group)
	    && (Slrn_List_Active_File == 0))
	  add_group (group, strlen (group), 0, 0);
	Slrn_Groups_Dirty = 1;
	Slrn_Full_Screen_Update = 1;
     }
}

static void catch_up_for_real (void)
{
   Slrn_Range_Type *r, *rnext;
   
   if (Current_Group == NULL) return;
   r = Current_Group->range.next;
   while (r != NULL)
     {
	rnext = r->next;
	SLFREE (r);
	r = rnext;
     }
   Current_Group->range.next = NULL;
   slrn_add_group_ranges (Current_Group, 1, Current_Group->range.max);
   Current_Group->flags |= GROUP_TOUCHED;
   slrn_message ("Group marked as read.");
}


/* TODO: check for write errors!!! */
int slrn_write_newsrc (void)
{
   Slrn_Group_Type *g;
   Slrn_Range_Type *r;
   char file[SLRN_MAX_PATH_LEN], backup_file[SLRN_MAX_PATH_LEN];
   static FILE *fp;
   int pass;
   int max;
#ifdef unix
# ifndef __os2__
   struct stat filestat;
   int stat_worked;
# endif
#endif

   slrn_init_hangup_signals (0);
   
   if (Slrn_Groups_Dirty == 0) 
     {
	slrn_init_hangup_signals (1);
	return 0;
     }

   /* In case of hangup and we were writing the file, make sure it is closed.
    * This will not hurt since we are going to do it again anyway.
    */
   if (fp != NULL) slrn_fclose (fp); fp = NULL;
   
   slrn_message ("Writing %s...", Slrn_Newsrc_File);
   slrn_smg_refresh ();
   
   slrn_make_home_filename (Slrn_Newsrc_File, file);
   
#ifdef unix
# ifndef __os2__
   /* Try to preserve .newsrc permissions and owner/group */
   stat_worked = (-1 != stat (file, &filestat));
# endif
#endif
  
   if (Slrn_No_Backups == 0)
     {
#ifdef VMS
	sprintf (backup_file, "%s-bak", file);
#else
	sprintf (backup_file, "%s~", file);
#endif
	(void) slrn_delete_file (backup_file);
	(void) rename (file, backup_file);
     }
   
   if (NULL == (fp = fopen (file, "w")))
     {
	slrn_error ("Unable to save to file %s.", file);
	if (Slrn_No_Backups == 0) (void) rename (backup_file, file);
	slrn_init_hangup_signals (1);
	return -1;
     }
   
   
   /* We are going to do this in 2 passes.  The first pass writes out just
    * the subscribed groups.  The second pass takes care of the rest.
    */
   for (pass = 0; pass < 2; pass++)
     {
	g = Groups;
	while (g != NULL)
	  {
	     if (pass == 0)
	       {
		  if (g->flags & GROUP_UNSUBSCRIBED)
		    {
		       g = g->next;
		       continue;
		    }
		  fputs (g->name, fp);
		  putc (':', fp);
	       }
	     else if (pass == 1)
	       {
		  if ((g->flags & GROUP_UNSUBSCRIBED) == 0)
		    {
		       g = g->next;
		       continue;
		    }
		  
		  if (Slrn_Write_Newsrc_Flags)
		    {
		       if ((Slrn_Write_Newsrc_Flags == 1)
			   || ((Slrn_Write_Newsrc_Flags == 2)
			       && (g->range.next == NULL)))
			 {
			    g = g->next;
			    continue;
			 }
		    }
		  fputs (g->name, fp);
		  putc ('!', fp);
	       }
	     
	     r = g->range.next;
	     max = g->range.max;
	     if (r != NULL)
	       {
		  putc (' ', fp);
		  while (1)
		    {
		       /* Make this check because the unsubscribed group
			* range may not have been initialized from the server.
			*/
		       if ((max != -1) && (g->range.min != -1)
			   && ((g->flags & GROUP_UNSUBSCRIBED) == 0))
			 {
			    if (r->min > max) break;
			    if (r->max > max) r->max = max;
			 }
		       
		       if (r->min != r->max)
			 {
			    fprintf (fp, "%d-%d", r->min, r->max);
			 }
		       else fprintf (fp, "%d", r->min);
		       r = r->next;
		       if (r == NULL) break;
		       putc (',', fp);
		    }
	       }
	     
	     putc ('\n', fp);
	     g = g->next;
	  }
     }
   slrn_fclose (fp); fp = NULL;
   
#ifdef unix
# ifndef __os2__
   /* Try to preserve .newsrc permissions and owner/group */
   if (stat_worked)
     {
 	if (-1 == chmod (file, filestat.st_mode & 0777))
	  (void) chmod (file, 0600);

 	(void) chown (file, filestat.st_uid, filestat.st_gid);
     }
   else
     (void) chmod (file, 0600 );
# endif
#endif

   Slrn_Groups_Dirty = 0;
   if (Slrn_TT_Initialized & 1)
     slrn_message ("Writing %s... done.", Slrn_Newsrc_File);
   slrn_init_hangup_signals (1);
   return 0;
}

static void free_all_groups (void)
{
   Slrn_Group_Type *g = Groups;
   Slrn_Group_Type *nextg;
   Slrn_Range_Type *r, *rnext;
   int i;
   
   while (g != NULL)
     {
	nextg = g->next;
	if (g->descript != NULL) SLFREE (g->descript);
	r = g->range.next;
	while (r != NULL)
	  {
	     rnext = r->next;
	     SLFREE (r);
	     r = rnext;
	  }
	
	SLFREE(g);
	g = nextg;
     }
   Groups = NULL;
   Current_Group = NULL;
   Top_Group = Bottom_Group = NULL;
   Num_Groups = Line_Num = 0;
   for (i = 0; i < GROUP_HASH_TABLE_SIZE; i++) Group_Hash_Table[i] = NULL;
}

static void save_newsrc (void)
{
   if (Slrn_Groups_Dirty)
     {
	slrn_write_newsrc ();
     }
   else
     {
	slrn_message ("No changes need to be saved.");
     }
   slrn_smg_refresh ();
}

static void refresh_groups (void)
{
   char name[MAX_GROUP_NAME_LEN + 1];
   
   *name = 0;
   if (Current_Group != NULL) strcpy (name, Current_Group->name);
   
   slrn_set_suspension (1);
   save_newsrc ();
   free_all_groups ();
   /*
    * Groups_Hidden gets toggled in slrn_read_newsrc, which gets called
    * in slrn_get_new_news.  Therefore, pre-toggle Groups_Hidden ahead of
    * time.
    */
   Groups_Hidden = !Groups_Hidden;
   if (-1 == slrn_get_new_news (1, 0))
     {
	slrn_quit (0);
     }
   if (*name)
     (void) find_group (name);
   slrn_set_suspension (0);
   quick_help ();
}



typedef struct Unsubscribed_Slrn_Group_Type
{
   char name[MAX_GROUP_NAME_LEN + 1];
   struct Unsubscribed_Slrn_Group_Type *next;
}
Unsubscribed_Slrn_Group_Type;

static Unsubscribed_Slrn_Group_Type *Unsubscribed_Groups;

static void add_unsubscribed_group (char *name)
{
   Unsubscribed_Slrn_Group_Type *g;
   char *p;
   unsigned int len;
   
   if (NULL == (g = (Unsubscribed_Slrn_Group_Type *) SLMALLOC (sizeof (Unsubscribed_Slrn_Group_Type))))
     {
	slrn_exit_error ("Memory allocation error.");
     }
   
   g->next = Unsubscribed_Groups;
   Unsubscribed_Groups = g;
   
   p = name;
   while (*p > ' ') p++;
   *p = 0;
   len = p - name;
   
   if (len > MAX_GROUP_NAME_LEN) len = MAX_GROUP_NAME_LEN;
   strncpy (g->name, name, len);
   g->name[len] = 0;
}





static int group_up_n (int n)
{
   Slrn_Group_Type *g;
   int i;
   
   g = Current_Group;
   
   i = 0;
   if (g != NULL) while (i < n)
     {
	g = g->prev;
	while ((g != NULL) && (g->flags & GROUP_HIDDEN))
	  {
	     g = g->prev;
	  }
	
	if (g == NULL) break;
	i++;
	Line_Num--;
	Current_Group = g;
     }
   return i;
}


static void group_up (void)
{
   if (0 == group_up_n (1))
     {
	slrn_error ("Top of buffer.");
     }
}

static int group_down_n (int n)
{
   Slrn_Group_Type *g;
   int i;
   
   g = Current_Group;
   i = 0;
   if (g != NULL) while (i < n)
     {
	g = g->next;
	while ((g != NULL) && (g->flags & GROUP_HIDDEN))
	  {
	     g = g->next;
	  }
	
	if (g == NULL) break;
	Line_Num++;
	Current_Group = g;
	i++;
     }
   return i;
}

static void group_down (void)
{
   if (1 != group_down_n (1))
     {
	slrn_error ("End of Buffer.");
     }
}

static void transpose_groups (void)
{
   Slrn_Group_Type *g = Current_Group, *g1, *tmp;
   if (g == NULL) return;
   
   if (1 != group_up_n (1))
     {
	return;
     }
   
   g1 = Current_Group;
   tmp = g1->next;
   
   /* Two cases to consider */
   if (tmp != g)
     {
	g1->next = g->next;
	if (g1->next != NULL) g1->next->prev = g1;
	g->next = tmp;
	tmp->prev = g;		       /* tmp cannot be NULL */
	
	tmp = g1->prev;
	g1->prev = g->prev;
	g1->prev->next = g1;		       /* g1->prev cannot be NULL */
	g->prev = tmp;
	if (tmp != NULL) tmp->next = g;
     }
   else				       /* g1->next == g, g->prev == g1 */
     {
	g->prev = g1->prev;
	if (g->prev != NULL) g->prev->next = g;
	g1->next = g->next;
	if (g1->next != NULL) g1->next->prev = g1;
	g->next = g1;
	g1->prev = g;
     }
   
   if (g1 == Groups) Groups = g;
   
   find_line_num ();
   
   (void) group_down_n (1);
   
   Slrn_Full_Screen_Update = 1;
   Slrn_Groups_Dirty = 1;
}


static void move_group_cmd (void)
{
   SLang_Key_Type *key;
   void (*f)(void);
   Slrn_Group_Type *g;
   
   if (Current_Group == NULL) return;
   g = Current_Group;
   
   while (1)
     {
	slrn_message ("Moving %s. Press RETURN when finished.", Current_Group->name);
	slrn_smg_refresh ();
	
	key = SLang_do_key (Slrn_Group_Keymap, (int (*)(void)) SLang_getkey);
	
	if ((key == NULL) 
	    || (key->type == SLKEY_F_INTERPRET))
	  f = NULL; 
	else f = (void (*)(void)) key->f.f;
     
	if (f == group_up)
	  {
	     transpose_groups ();
	     if (2 != group_up_n (2))
	       return;
	  }
	else if (f == group_down)
	  {
	     if (1 != group_down_n (1))
	       return;
	     
	     transpose_groups ();
	     
	     if (1 != group_up_n (1))
	       return;
	  }
	else return;
	
	if (g != Current_Group)
	  {
	     Current_Group = g;
	     find_line_num ();
	  }
	
	group_update_screen ();
	slrn_smg_refresh ();
     }
}

static void subscribe (void)
{
   SLRegexp_Type *re;
   Slrn_Group_Type *g;
   
   if (Current_Group == NULL) return;
   
   if (Slrn_Prefix_Arg_Ptr == NULL)
     {
	Current_Group->flags &= ~GROUP_UNSUBSCRIBED;
	Current_Group->flags |= GROUP_TOUCHED;
	group_down_n (1);
	Slrn_Groups_Dirty = 1;
	return;
     }
   
   Slrn_Prefix_Arg_Ptr = NULL;
   	
   if (NULL == (re = read_group_regexp ("Subscribe pattern: ")))
     return;

   g = Groups;
   while (g != NULL)
     {
	if (g->flags & GROUP_UNSUBSCRIBED)
	  {
	     if (NULL != slrn_regexp_match (re, g->name))
	       {
		  g->flags &= ~GROUP_HIDDEN;
		  g->flags &= ~GROUP_UNSUBSCRIBED;
		  g->flags |= GROUP_TOUCHED;
	       }
	  }
	g = g->next;
     }
   find_line_num ();
   Slrn_Full_Screen_Update = 1;
}

static void catch_up (void)
{
   if (Current_Group == NULL) return;
   if (Slrn_User_Wants_Confirmation &&
       slrn_get_yesno(1, "Mark %s as read", Current_Group->name) <= 0) return;
   catch_up_for_real ();
   (void) group_down_n (1);
}

static void uncatch_up (void)
{
   Slrn_Range_Type *r, *rnext;
   
   if (Current_Group == NULL) return;
   if (Slrn_User_Wants_Confirmation &&
       slrn_get_yesno(1, "Mark %s as un-read", Current_Group->name) <= 0) return;
   
   r = Current_Group->range.next;
   while (r != NULL)
     {
	rnext = r->next;
	SLFREE (r);
	r = rnext;
     }
   Current_Group->range.next = NULL;
   slrn_add_group_ranges (Current_Group, 1, 1);
   Current_Group->flags |= GROUP_TOUCHED;
   slrn_message ("Group marked as un-read.");
   (void) group_down_n (1);
}

static void unsubscribe (void)
{
   SLRegexp_Type *re;
   Slrn_Group_Type *g;

   if (Current_Group == NULL) return;
   
   if (Slrn_Prefix_Arg_Ptr == NULL)
     {
	Current_Group->flags |= GROUP_UNSUBSCRIBED | GROUP_TOUCHED;
	group_down_n (1);
	Slrn_Groups_Dirty = 1;
	return;
     }
   
   Slrn_Prefix_Arg_Ptr = NULL;
   	
   if (NULL == (re = read_group_regexp ("Un-Subscribe pattern: ")))
     return;

   g = Groups;
   while (g != NULL)
     {
	if ((g->flags & GROUP_UNSUBSCRIBED) == 0)
	  {
	     if (NULL != (slrn_regexp_match (re, g->name)))
	       {
		  g->flags &= ~GROUP_HIDDEN;
		  g->flags |= (GROUP_TOUCHED | GROUP_UNSUBSCRIBED);
	       }
	  }
	g = g->next;
     }
   find_line_num ();
   Slrn_Full_Screen_Update = 1;
}

SLKeyMap_List_Type *Slrn_Group_Keymap;

int *Slrn_Prefix_Arg_Ptr;
void slrn_set_prefix_argument (int rep)
{
   static int repeat;
   
   repeat = rep;
   Slrn_Prefix_Arg_Ptr = &repeat;
}

void slrn_digit_arg (SLKeyMap_List_Type *kmap)
{
   char buf[20];
   unsigned char key;
   int i;
   
   i = 0;
   buf[i++] = (char) SLang_Last_Key_Char;
   
   while(1)
     {
	buf[i] = 0;
	key = (unsigned char) SLang_getkey ();
	if ((key < '0') || (key > '9')) break;
	buf[i++] = (char) key;
     }
   slrn_set_prefix_argument (atoi (buf));
   SLang_ungetkey (key);
   slrn_do_key (kmap);
   Slrn_Prefix_Arg_Ptr = NULL;
}

static void digit_arg (void)
{
   slrn_digit_arg (Slrn_Group_Keymap);
}

static int group_sync_group_with_server (Slrn_Group_Type *g, int *minp, int *maxp)
{
   int min, max, n, max_available;
   char *group;
   Slrn_Range_Type *r;
   
   if (g == NULL) return -1;
   
   group = g->name;
   
   slrn_message ("Selecting %s...", group);
   slrn_smg_refresh ();
   slrn_set_suspension (1);
   if (Slrn_Server_Obj->sv_select_group (group, minp, maxp) < 0)
     {
	g->flags &= ~GROUP_UNSUBSCRIBED;
	slrn_error ("This group appears to be bogus.");
	slrn_set_suspension (0);
	return -1;
     }
   slrn_set_suspension (0);
   
   min = *minp;
   max = *maxp;
   
   if (max == 0)
     {
	int nmax, nmin;
	
	nmax = g->range.max;
	nmin = g->range.min;
	
	/* Server database inconsistent. */
	for (n = nmin; n <= nmax; n++)
	  group_mark_article_as_read (g, n);
	
	/* g->unread = 0; */
	slrn_message ("No articles to read.");
	Slrn_Full_Screen_Update = 1;
	Slrn_Groups_Dirty = 1;
	return -1;
     }
   
   g->range.min = min;
   if (max < g->range.max)
     {
	/* There is only one way for this to happen that I am aware of:
	 * an article has been cancelled-- update the ranges.
	 */
	int nmax = g->range.max;
	for (n = max + 1; n <= nmax; n++)
	  group_mark_article_as_read (g, n);
     }
   else g->range.max = max;

   /* In case more articles arrived at the server between the time that 
    * slrn was first started and when the server was just queried, update
    * the ranges of read/unread articles.
    */
   
   max_available = g->range.max - g->range.min + 1;
   
   r = &g->range;
   if (r->next != NULL)
     {
	if (r->next->min <= r->min)
	  r->next->min = 1;
	
	n = r->max;
	while (r->next != NULL)
	  {
	     r = r->next;
	     n -= r->max - r->min + 1;
	  }
	if (n < 0) n = 0;
	if (n > max_available) n = max_available;
	g->unread = n;
     }
   
   return 0;
}

static void group_select_group (void)
{
   int min, max, n, max_available;
   int ret;
   Slrn_Range_Type *r;
   int prefix;
   
   if (Slrn_Prefix_Arg_Ptr != NULL)
     {
	prefix = *Slrn_Prefix_Arg_Ptr;
	Slrn_Prefix_Arg_Ptr = NULL;
     }
   else prefix = 0;
   
   if (-1 == group_sync_group_with_server (Current_Group, &min, &max))
     return;
   
   n = Current_Group->unread;
   max_available = Current_Group->range.max - Current_Group->range.min + 1;

   if ((prefix & 1) || (n == 0))
     n = max_available;

   if ((prefix & 1) || 
       ((n > Slrn_Query_Group_Cutoff)
	&& (Slrn_Query_Group_Cutoff > 0)))
     {
	char int_prompt_buf[256];
	sprintf (int_prompt_buf, "%s: Read how many?", Current_Group->name);
	if ((-1 == slrn_read_integer (int_prompt_buf, &n, &n))
	    || (n <= 0))
	  {
	     slrn_clear_message ();
	     Slrn_Full_Screen_Update = 1;
	     return;
	  }
	
	if ((0 == (prefix & 1))
	    && (Current_Group->unread != 0))
	  {
	     r = Current_Group->range.next;
	     if (r != NULL)
	       {
		  while (r->next != NULL) r = r->next;
		  if (r->max + n > max)
		    n = -n;	       /* special treatment in article mode
					* because we will need to query the 
					* server about articles in a group 
					* that we have already read.
					*/
	       }
	  }
     }
   else if ((0 == (prefix & 1)) && (Current_Group->unread != 0))
     n = 0;
   
   ret = slrn_select_article_mode (Current_Group, n, ((prefix & 2) == 0));
   if (ret == -2) catch_up_for_real ();
}

void slrn_select_next_group (void)
{
   if (Current_Group == NULL) 
     return;
   
   while (1 == group_down_n (1))
     {
	if (Current_Group->unread == 0) 
	  continue;
	
	group_select_group ();
	break;
#if 0
	if (Slrn_Current_Mode->mode == SLRN_ARTICLE_MODE)
	  break;
#endif
     }
}

void slrn_select_prev_group (void)
{
}

void slrn_post_cmd (void)
{
   int ret;
   char *name, *dist = NULL;	       /* "world"; */
   char group[256];
   char subj[256];
   
   if (Slrn_Post_Obj->po_can_post == 0)
     {
	slrn_error ("Posting not allowed.");
	return;
     }
   if (Slrn_User_Wants_Confirmation &&
       (slrn_get_yesno (1, "Are you sure that you want to post") <= 0))
     return;
   
   if (Current_Group == NULL) name = ""; else name = Current_Group->name;
   strcpy (group, name);
   if (slrn_read_input ("Newsgroup: ", group, 1) <= 0) return;
   *subj = 0; if (slrn_read_input ("Subject: ", subj, 1) <= 0) return;
   
   ret = slrn_post (group, subj, dist);
   if (ret < 0)
     {
	slrn_error ("Posting failed.");
     }
}

void slrn_group_quit (void)
{
   if (Slrn_User_Wants_Confirmation
       && (slrn_get_yesno (1, "Do you really want to quit") <= 0)) return;
   if (Slrn_Groups_Dirty) slrn_write_newsrc ();
   slrn_quit (0);
}

#if 0
static void group_refresh (void)
{
   if (Slrn_Groups_Dirty) slrn_write_newsrc ();
   Slrn_Hangup_Hook = NULL;
   slrn_quit (2);
}
#endif

static void group_pageup (void)
{
   Slrn_Group_Type *g;
   int n;
   
   if (Top_Group != NULL)
     {
	Slrn_Full_Screen_Update = 1;
	/* Move to top of window, then go up one page */
	n = 0;
	g = Current_Group;
	while ((g != Top_Group) && (g != NULL))
	  {
	     g = g->prev;
	     if (0 == (g->flags & GROUP_HIDDEN)) n++;
	  }
	
	if (g != NULL)
	  {
	     Current_Group = g;
	     Line_Num -= n;
	     n = Line_Num;	       /* save this */
	     /* Now compute new top group */
	     group_up_n (Slrn_Group_Window_Size - 1);
	     Top_Group = Current_Group;
	     Current_Group = g; Line_Num = n;
	     return;
	  }
     }
   
   if (0 == group_up_n (Slrn_Group_Window_Size - 1))
     slrn_error ("Top of Buffer.");
}

static void group_pagedown (void)
{
   Slrn_Group_Type *g;
   int n;
   
   if (Bottom_Group != NULL)
     {
	Slrn_Full_Screen_Update = 1;
	n = 0;
	g = Current_Group;
	while ((g != Bottom_Group) && (g != NULL))
	  {
	     g = g->next;
	     if (0 == (g->flags & GROUP_HIDDEN)) n++;
	  }
	
	if (g != NULL)
	  {
	     Top_Group = Current_Group = g;
	     Line_Num += n;
	     return;
	  }
     }
   
   if (0 == group_down_n (Slrn_Group_Window_Size - 1))
     slrn_error ("End of Buffer.");
}

static void group_bob (void)
{
   while (group_up_n (1000));
}

static void group_eob (void)
{
   while (group_down_n (1000));
}


static void toggle_list_all_groups (void)
{
   int mode;
   if (Slrn_Prefix_Arg_Ptr != NULL)
     mode = 1;
   else mode = -1;
   
   toggle_list_all_groups1 (mode);
}

static void toggle_group_display (void)
{
   Slrn_Group_Display_Descriptions = !Slrn_Group_Display_Descriptions;
   Slrn_Full_Screen_Update = 1;
}

void slrn_repeat_last_key (void)
{
   SLtt_beep ();
}

static void toggle_scoring (void)
{
   switch (slrn_get_response ("FfSsNnCc",
			      "Select scoring mode: F-ull, S-imple, N-one, C-ancel"))
     {
      case 'F':
      case 'f':
	Slrn_Perform_Scoring = SLRN_XOVER_SCORING | SLRN_EXPENSIVE_SCORING;
	slrn_message ("Full Header Scoring enabled.");
	break;
	
      case 'S':
      case 's':
	Slrn_Perform_Scoring = SLRN_XOVER_SCORING;
	slrn_message ("Expensive Scoring disabled.");
	break;
	
      case 'N':
      case 'n':
	Slrn_Perform_Scoring = 0;
	slrn_message ("Scoring disabled.");
	break;
	
      default:
	slrn_clear_message ();
	break;
     }
}


#define A_KEY(s, f)  {s, (int (*)(void)) f}
static SLKeymap_Function_Type Group_Functions [] =
{
   A_KEY("move_group", move_group_cmd),
   A_KEY("uncatch_up", uncatch_up),
   A_KEY("toggle_scoring", toggle_scoring),
     A_KEY("toggle_group_display", toggle_group_display),
     A_KEY("refresh_groups", refresh_groups),
     A_KEY("save_newsrc", save_newsrc),
     A_KEY("group_search", group_search),
     A_KEY("group_search_forward", group_search),
     A_KEY("toggle_list_all", toggle_list_all_groups),
     A_KEY("add_group", add_group_cmd),
     A_KEY("bob", group_bob),
     A_KEY("catchup", catch_up),
     A_KEY("down", group_down),
     A_KEY("eob", group_eob),
     A_KEY("help", slrn_group_help),
     A_KEY("pagedown", group_pagedown),
     A_KEY("pageup", group_pageup),
     A_KEY("post", slrn_post_cmd),
     A_KEY("quit", slrn_group_quit),
     A_KEY("redraw", slrn_redraw),
     A_KEY("select_group", group_select_group),
     A_KEY("subscribe", subscribe),
     A_KEY("suspend", slrn_suspend_cmd),
     A_KEY("toggle_hidden", toggle_hide_groups),
     A_KEY("unsubscribe", unsubscribe),
     A_KEY("up", group_up),
     A_KEY("repeat_last_key", slrn_repeat_last_key),
     A_KEY("transpose_groups", transpose_groups),
     A_KEY(NULL, NULL)
};



/* actions for different regions:
 *	- top status line (help)
 *	- normal region
 *	- bottom status line
 */
static void group_mouse (void (*top_status)(void),
			 void (*bot_status)(void),
			 void (*normal_region)(void)
			 )
{
   int r,c;
   
   slrn_get_mouse_rc (&r, &c);
   
   /* take top status line into account */
   if (r == 1)
     {
	if (Slrn_Use_Mouse)
	  slrn_execute_menu (c, Group_Functions);
	else
	  if (NULL != top_status) (*top_status) ();
 	return;
     }
   
   if (r >= SLtt_Screen_Rows)
     return;
   
   /* bottom status line */
   if (r == SLtt_Screen_Rows - 1)
     {
	if (NULL != bot_status) (*bot_status) ();
	return;
     }
   
   r -= (1 + Last_Cursor_Row);
   if (r < 0)
     {
	r = -r;
	if (r != group_up_n (r)) return;
     }
   else if (r != group_down_n (r)) return;
   
   if (NULL != normal_region) (*normal_region) ();
}

static void group_mouse_left (void)
{
   group_mouse (slrn_group_help, group_pagedown, group_select_group);
}

static void group_mouse_middle (void)
{
   group_mouse (toggle_group_display, toggle_hide_groups, group_select_group);
#if 1
   /* Make up for buggy rxvt which have problems with the middle key. */
   if (NULL != getenv ("COLORTERM"))
     {
	if (SLang_input_pending (7))
	  {
	     while (SLang_input_pending (0)) SLang_getkey ();
	  }
     }
#endif
}

static void group_mouse_right (void)
{
   group_mouse (slrn_group_help, group_pageup, group_select_group);
}


static void slrn_group_hup (int sig)
{
   slrn_write_newsrc ();
   slrn_quit (sig);
}

static Slrn_Mode_Type Group_Mode_Cap =
{
   NULL,
   group_update_screen,		       /* redraw */
   NULL,			       /* sig winch hook */
   slrn_group_hup,		       /* hangup hook */
   SLRN_GROUP_MODE
};

void slrn_init_group_mode (void)
{
   char  *err = "Unable to create group keymap!";
   
   if (NULL == (Slrn_Group_Keymap = SLang_create_keymap ("group", NULL)))
     slrn_exit_error (err);
   
   Group_Mode_Cap.keymap = Slrn_Group_Keymap;
   
   Slrn_Group_Keymap->functions = Group_Functions;
   
   SLkm_define_key ("\0331", (FVOID_STAR) digit_arg, Slrn_Group_Keymap);
   SLkm_define_key ("\0332", (FVOID_STAR) digit_arg, Slrn_Group_Keymap);
   SLkm_define_key ("\0333", (FVOID_STAR) digit_arg, Slrn_Group_Keymap);
   SLkm_define_key ("\0334", (FVOID_STAR) digit_arg, Slrn_Group_Keymap);
   SLkm_define_key ("\0335", (FVOID_STAR) digit_arg, Slrn_Group_Keymap);
   SLkm_define_key ("\0336", (FVOID_STAR) digit_arg, Slrn_Group_Keymap);
   SLkm_define_key ("\0337", (FVOID_STAR) digit_arg, Slrn_Group_Keymap);
   SLkm_define_key ("\0338", (FVOID_STAR) digit_arg, Slrn_Group_Keymap);
   SLkm_define_key ("\0339", (FVOID_STAR) digit_arg, Slrn_Group_Keymap);
   SLkm_define_key ("\0330", (FVOID_STAR) digit_arg, Slrn_Group_Keymap);
   SLkm_define_key  ("^K\033[A", (FVOID_STAR) group_bob, Slrn_Group_Keymap);
   SLkm_define_key  ("^K\033OA", (FVOID_STAR) group_bob, Slrn_Group_Keymap);
   SLkm_define_key  ("^K\033[B", (FVOID_STAR) group_eob, Slrn_Group_Keymap);
   SLkm_define_key  ("^K\033OB", (FVOID_STAR) group_eob, Slrn_Group_Keymap);
   SLkm_define_key  ("\033A", (FVOID_STAR) toggle_group_display, Slrn_Group_Keymap);
   SLkm_define_key  ("\033>", (FVOID_STAR) group_eob, Slrn_Group_Keymap);
   SLkm_define_key  ("\033<", (FVOID_STAR) group_bob, Slrn_Group_Keymap);
   SLkm_define_key  ("^D", (FVOID_STAR) group_pagedown, Slrn_Group_Keymap);
   SLkm_define_key  ("^V", (FVOID_STAR) group_pagedown, Slrn_Group_Keymap);
#ifdef __os2__
   SLkm_define_key  ("^@Q", (FVOID_STAR) group_pagedown, Slrn_Group_Keymap);
   SLkm_define_key  ("\xE0Q", (FVOID_STAR) group_pagedown, Slrn_Group_Keymap);
   SLkm_define_key  ("^@I", (FVOID_STAR) group_pageup, Slrn_Group_Keymap);
   SLkm_define_key  ("\xE0I", (FVOID_STAR) group_pageup, Slrn_Group_Keymap);   
#else
   SLkm_define_key  ("\033[6~", (FVOID_STAR) group_pagedown, Slrn_Group_Keymap);
   SLkm_define_key  ("\033[5~", (FVOID_STAR) group_pageup, Slrn_Group_Keymap);
#endif
   SLkm_define_key  ("m", (FVOID_STAR) move_group_cmd, Slrn_Group_Keymap);
   SLkm_define_key  ("^U", (FVOID_STAR) group_pageup, Slrn_Group_Keymap);
   SLkm_define_key  ("\033V", (FVOID_STAR) group_pageup, Slrn_Group_Keymap);
   SLkm_define_key  ("a", (FVOID_STAR) add_group_cmd, Slrn_Group_Keymap);
   SLkm_define_key  ("u", (FVOID_STAR) unsubscribe, Slrn_Group_Keymap);
   SLkm_define_key  ("s", (FVOID_STAR) subscribe, Slrn_Group_Keymap);
   SLkm_define_key  ("\033u", (FVOID_STAR) uncatch_up, Slrn_Group_Keymap);
   SLkm_define_key  ("c", (FVOID_STAR) catch_up, Slrn_Group_Keymap);
   SLkm_define_key  ("K", (FVOID_STAR) toggle_scoring, Slrn_Group_Keymap);
   SLkm_define_key  ("L", (FVOID_STAR) toggle_list_all_groups, Slrn_Group_Keymap);
   SLkm_define_key  ("l", (FVOID_STAR) toggle_hide_groups, Slrn_Group_Keymap);
   SLkm_define_key  ("^Z", (FVOID_STAR) slrn_suspend_cmd, Slrn_Group_Keymap);
   SLkm_define_key  (" ", (FVOID_STAR) group_select_group, Slrn_Group_Keymap);
   SLkm_define_key  (".", (FVOID_STAR) slrn_repeat_last_key, Slrn_Group_Keymap);
   SLkm_define_key  ("P", (FVOID_STAR) slrn_post_cmd, Slrn_Group_Keymap);
   SLkm_define_key  ("?", (FVOID_STAR) slrn_group_help, Slrn_Group_Keymap);
   SLkm_define_key  ("\r", (FVOID_STAR) group_select_group, Slrn_Group_Keymap);
   SLkm_define_key  ("q", (FVOID_STAR) slrn_group_quit, Slrn_Group_Keymap);
   SLkm_define_key  ("^X^C", (FVOID_STAR) slrn_group_quit, Slrn_Group_Keymap);
   SLkm_define_key  ("^X^T", (FVOID_STAR) transpose_groups, Slrn_Group_Keymap);
   SLkm_define_key  ("^R", (FVOID_STAR) slrn_redraw, Slrn_Group_Keymap);
   SLkm_define_key  ("^L", (FVOID_STAR) slrn_redraw, Slrn_Group_Keymap);
   SLkm_define_key  ("^P", (FVOID_STAR) group_up, Slrn_Group_Keymap);
#ifdef __os2__
   SLkm_define_key  ("^@H", (FVOID_STAR) group_up, Slrn_Group_Keymap);
   SLkm_define_key  ("\xE0H", (FVOID_STAR) group_up, Slrn_Group_Keymap);
   SLkm_define_key  ("^@P", (FVOID_STAR) group_down, Slrn_Group_Keymap);
   SLkm_define_key  ("\xE0P", (FVOID_STAR) group_down, Slrn_Group_Keymap);
#else
   SLkm_define_key  ("\033[A", (FVOID_STAR) group_up, Slrn_Group_Keymap);
   SLkm_define_key  ("\033OA", (FVOID_STAR) group_up, Slrn_Group_Keymap);
   SLkm_define_key  ("\033[B", (FVOID_STAR) group_down, Slrn_Group_Keymap);
   SLkm_define_key  ("\033OB", (FVOID_STAR) group_down, Slrn_Group_Keymap);
#endif
   SLkm_define_key  ("N", (FVOID_STAR) group_down, Slrn_Group_Keymap);
   SLkm_define_key  ("^N", (FVOID_STAR) group_down, Slrn_Group_Keymap);
   SLkm_define_key  ("/", (FVOID_STAR) group_search, Slrn_Group_Keymap);
   SLkm_define_key  ("G", (FVOID_STAR) refresh_groups, Slrn_Group_Keymap);
   SLkm_define_key  ("X", (FVOID_STAR) save_newsrc, Slrn_Group_Keymap);
   
   /* mouse (left/right/middle) */
   SLkm_define_key  ("\033[M\040", (FVOID_STAR) group_mouse_left, Slrn_Group_Keymap);
   SLkm_define_key  ("\033[M\041", (FVOID_STAR) group_mouse_middle, Slrn_Group_Keymap);
   SLkm_define_key  ("\033[M\042", (FVOID_STAR) group_mouse_right, Slrn_Group_Keymap);
   
   if (SLang_Error) slrn_exit_error (err);
}



static void add_group_description (unsigned char *s, unsigned char *smax,
				   unsigned char *dsc)
{
   Slrn_Group_Type *g;
   unsigned long hash;
   
   hash = slrn_compute_hash (s, smax);
   g = Group_Hash_Table[hash % GROUP_HASH_TABLE_SIZE];
   while (g != NULL)
     {
	if ((g->hash == hash) && (!strncmp (g->name, (char *) s, (unsigned int) (smax - s))))
	  {
	     /* Sometimes these get repeated --- not by slrn but on the server! */
	     if (g->descript != NULL)
	       SLFREE (g->descript);
	     
	     g->descript = (char *) SLMALLOC (strlen ((char *) dsc) + 1);
	     if (g->descript != NULL)
	       {
		  strcpy (g->descript, (char *) dsc);
	       }
	     return;
	  }
	g = g->hash_next;
     }
}

void slrn_get_group_descriptions (void)
{
   FILE *fp;
   char line[2 * SLRN_MAX_PATH_LEN];
   char file[SLRN_MAX_PATH_LEN];
   int num;
   
#ifdef VMS
   sprintf (file, "%s-dsc", Slrn_Newsrc_File);
#else
# ifdef __os2__
   sprintf (file, "ds-%s", Slrn_Newsrc_File);
# else
   sprintf (file, "%s.dsc", Slrn_Newsrc_File);
# endif
#endif
   
   
   if (NULL == (fp = slrn_open_home_file (file, "w", line, 0)))
     {
	slrn_exit_error ("\
Unable to create newsgroup description file:\n%s\n", line);
     }
   
   fprintf (stdout, "Creating description file %s.\n", line);
   Slrn_Server_Obj->sv_list_newsgroups ();
   fprintf (stdout, "Getting newsgroup descriptions from server.\n\
Note: This step may take some time if you have a slow connection!!!\n");
   
   fflush (stdout);
   
   num = 0;
   while (NULL != Slrn_Server_Obj->sv_read_line (line, sizeof (line) - 1))
     {
	unsigned char *b, *bmax, *dsc, ch;
	
	num = num % 25;
	if (num == 0)
	  {
	     putc ('.', stdout);
	     fflush (stdout);
	  }
	
	/* Check the syntax on this line. They are often corrupt */
	b = (unsigned char *) slrn_skip_whitespace (line);
	
	bmax = b;
	while ((ch = *bmax) > ' ') bmax++;
	if ((ch == 0) || (ch == '\n')) continue;
	*bmax = 0;
	
	/* News group marked off, now get the description. */
	dsc = bmax + 1;
	while (((ch = *dsc) <= ' ') && (ch != 0)) dsc++;
	if ((ch == 0) || (ch == '?') || (ch == '\n')) continue;
	
	/* add_group_description (b, bmax, dsc); */
	
	fputs ((char *) b, fp);
	putc(':', fp);
	fputs ((char *) dsc, fp);
	putc('\n', fp);
	
	num++;
     }
   slrn_fclose (fp);
   putc ('\n', stdout);
}

int slrn_read_group_descriptions (void)
{
   FILE *fp;
   char line[2 * SLRN_MAX_PATH_LEN];
   char file[SLRN_MAX_PATH_LEN];
   
#ifdef VMS
   sprintf (file, "%s-dsc", Slrn_Newsrc_File);
#else
# ifdef __os2__
   sprintf (file, "ds-%s", Slrn_Newsrc_File);
# else
   sprintf (file, "%s.dsc", Slrn_Newsrc_File);
# endif
#endif
   
   
   if (NULL == (fp = slrn_open_home_file (file, "r", line, 0)))
     {
	if (Slrn_Lib_Dir != NULL)
	  {
#ifdef VMS
	     sprintf (file, "%snewsgroups.dsc", Slrn_Lib_Dir);
	     if (NULL == (fp = slrn_open_home_file (file, "r", line, 0)))
	       {
		  sprintf (file, "%snewsgroups-dsc", Slrn_Lib_Dir);
		  fp = slrn_open_home_file (file, "r", line, 0);
	       }
#else
	     sprintf (file, "%s/newsgroups.dsc", Slrn_Lib_Dir);
	     fp = slrn_open_home_file (file, "r", line, 0);
#endif
	  }
	if (fp == NULL) return -1;
     }
   
   while (NULL != fgets (line, sizeof (line) - 1, fp))
     {
	unsigned char *bmax, *dsc, ch;
	
	bmax = (unsigned char *) line;
	while (((ch = *bmax) != ':') && ch) bmax++;
	if (ch <= ' ') continue;
	*bmax = 0;
	
	dsc = bmax + 1;
	add_group_description ((unsigned char *) line, bmax, dsc);
     }
   slrn_fclose (fp);
   return 0;
}

int Slrn_Unsubscribe_New_Groups = 0;

void slrn_check_new_groups (int create_flag)
{
   FILE *fp;
   time_t tloc;
   struct tm *tm_struct;
   char line[SLRN_MAX_PATH_LEN];
   char file[SLRN_MAX_PATH_LEN];
   int num;
   char *p;
   int parse_error = 0;
   
#ifdef VMS
   sprintf (file, "%s-time", Slrn_Newsrc_File);
#else
#ifdef __os2__
   sprintf (file, "tm-%s", Slrn_Newsrc_File);
#else
   sprintf (file, "%s.time", Slrn_Newsrc_File);
#endif
#endif
   
   
   if ((create_flag == 0)
       && (NULL != (fp = slrn_open_home_file (file, "r", line, 0))))
     {
	char ch;
	int i;
	*line = 0;
	fgets (line, sizeof (line), fp);
	slrn_fclose (fp);
	
	time (&tloc);
	parse_error = 1;
	
	/* parse this line to make sure it is ok.  If it is bad, issue a warning
	 * and go on.
	 */
	if (strncmp ("NEWGROUPS ", line, 10)) goto parse_error_label;
	p = line + 10;
	
	p = slrn_skip_whitespace (p);
	
	/* parse yymmdd */
	for (i = 0; i < 6; i++)
	  {
	     ch = p[i];
	     if ((ch < '0') || (ch > '9')) goto parse_error_label;
	  }
	if (p[6] != ' ') goto parse_error_label;
	
	ch = p[2];
	if (ch > '1') goto parse_error_label;
	if ((ch == '1') && (p[3] > '2')) goto parse_error_label;
	ch = p[4];
	if (ch > '3') goto parse_error_label;
	if ((ch == '3') && (p[5] > '1')) goto parse_error_label;
	
	/* Now the hour: hhmmss */
	p = slrn_skip_whitespace (p + 6);

	for (i = 0; i < 6; i++)
	  {
	     ch = p[i];
	     if ((ch < '0') || (ch > '9')) goto parse_error_label;
	  }
	ch = p[0];
	if (ch > '2') goto parse_error_label;
	if ((ch == '2') && (p[1] > '3')) goto parse_error_label;
	if ((p[2] > '5') || (p[4] > '5')) goto parse_error_label;
	
	p = slrn_skip_whitespace (p + 6);
	
	if ((p[0] == 'G') && (p[1] == 'M') && (p[2] == 'T'))
	  p += 3;
	*p = 0;
	
	parse_error = 0;
	
	switch (Slrn_Server_Obj->sv_put_server_cmd (line, line, sizeof (line)))
	  {
	   case OK_NEWGROUPS:
	     break;
	     
	   case ERR_FAULT:
	     slrn_message ("Server does not implement NEWGROUPS command.");
	     return;
	     
	   default:
	     slrn_message ("Server failed to return proper response to NEWGROUPS:\n%s\n",
			   line);
	     goto parse_error_label;
	  }
	
	num = 0;
	while (NULL != Slrn_Server_Obj->sv_read_line (line, sizeof (line)))
	  {
	     /* line contains new newsgroup name */
	     add_unsubscribed_group (line);
	     num++;
	  }
	
	if (num)
	  {
	     if (Slrn_TT_Initialized & 1)
	       {
		  slrn_message ("%d new newsgroup(s) found.", num);
		  slrn_smg_refresh ();
	       }
	     else
	       slrn_tty_message (2, "%d new newsgroup(s) found.", num);
	  }
     }
   else time (&tloc);
   
   parse_error_label:
   if (parse_error)
     {
	slrn_message ("\
%s appears corrupt.\n\
I expected to see see: NEWGROUPS yymmdd hhmmss GMT\n\
I will patch the file up for you.\n", file);
     }
   
   
   if (NULL == (fp = slrn_open_home_file (file, "w", line, 1)))
     {
	slrn_exit_error ("Unable to open %s to record date.", line);
     }
   
#ifdef VMS
   tm_struct = localtime (&tloc);
   fprintf (fp, "NEWGROUPS %02d%02d%02d %02d%02d%02d",
            tm_struct->tm_year, 1 + tm_struct->tm_mon,
            tm_struct->tm_mday, tm_struct->tm_hour,
            tm_struct->tm_min, tm_struct->tm_sec);
#else
   tm_struct = gmtime (&tloc);
   fprintf (fp, "NEWGROUPS %02d%02d%02d %02d%02d%02d GMT",
	    tm_struct->tm_year, 1 + tm_struct->tm_mon,
	    tm_struct->tm_mday, tm_struct->tm_hour,
	    tm_struct->tm_min, tm_struct->tm_sec);
#endif
   slrn_fclose (fp);
}

static Slrn_Group_Type *place_group_in_newsrc_order (Slrn_Group_Type *last_group)
{
   Slrn_Group_Type *next_group, *prev_group;
   
   next_group = Current_Group->next;
   prev_group = Current_Group->prev;
   if (next_group != NULL) next_group->prev = prev_group;
   if (prev_group != NULL) prev_group->next = next_group;
   
   Current_Group->prev = last_group;
   if (last_group != NULL)
     {
	Current_Group->next = last_group->next;
	
	if (Current_Group->next != NULL)
	  Current_Group->next->prev = Current_Group;
	
	last_group->next = Current_Group;
     }
   else if (Current_Group != Groups)
     {
	Current_Group->next = Groups;
	if (Groups != NULL) Groups->prev = Current_Group;
	Groups = Current_Group;
     }
   return Current_Group;
}

int slrn_read_newsrc (int create_flag)
{
   char file[SLRN_MAX_PATH_LEN];
   FILE *fp = NULL;
   char line[NNTP_BUFFER_SIZE];
   register char *p, ch;
   
   Line_Num = 0;
   
   if (create_flag)
     {
	if (NULL == (fp = slrn_open_home_file (Slrn_Newsrc_File, "w", file, 1)))
	  {
	     slrn_exit_error ("Unable to create %s.", file);
	  }
	
	fputs ("\n--The next step may take a while if the NNTP connection is slow.--\n\n", stdout);
	fprintf (stdout, "Creating %s.", file);
	fflush (stdout);
     }
   
   if (create_flag || Slrn_List_Active_File)
     {
	int count = 0;
	(void) Slrn_Server_Obj->sv_list_active ();
	
	while (NULL != Slrn_Server_Obj->sv_read_line (line, sizeof(line)))
	  {
	     unsigned int len;
	     int min, max;
	     
	     parse_active_line (line, &len, &min, &max);
	     
	     if (NULL == create_group_entry (line, len, min, max, 0, 0))
	       continue;
	     
	     
	     if (create_flag)
	       {
		  count++;
		  count = count % 50;
		  if (count == 0)
		    {
		       putc ('.', stdout);
		       fflush (stdout);
		    }
		  
		  if ((*line == 'n')
		      && (!strncmp (line,    "news.newusers.questions", 23)
			  || !strncmp (line, "news.groups.questions", 21)
			  || !strncmp (line, "news.answers", 12)
			  || !strncmp (line, "news.announce.newusers", 22)
			  || !strncmp (line, "news.software.readers", 21)))
		    {
		       add_group (line, len, 0, 1);
		    }
		  else if ((*line == 'a')
			   && (!strncmp (line, "alt.test", 8)))
		    {
		       add_group (line, len, 0, 1);
		    }
		  else add_group (line, len, GROUP_UNSUBSCRIBED, 1);
	       }
	  }
     }
   
   if (create_flag == 0)
     {
	Slrn_Group_Type *last_group = NULL;
	
	if (Unsubscribed_Groups != NULL)
	  {
	     unsigned int subscribe_flag;
	     Unsubscribed_Slrn_Group_Type *ug = Unsubscribed_Groups, *ugnext;
	     
	     if (Slrn_Unsubscribe_New_Groups)
	       subscribe_flag = GROUP_UNSUBSCRIBED | GROUP_NEW_GROUP_FLAG;
	     else subscribe_flag = GROUP_NEW_GROUP_FLAG;
	     
	     while (ug != NULL)
	       {
		  ugnext = ug->next;
		  
		  if ((-1 != add_group (ug->name, strlen (ug->name), subscribe_flag, 0))
		      && Slrn_List_Active_File)
		    last_group = place_group_in_newsrc_order (last_group);
		  
		  SLFREE (ug);
		  ug = ugnext;
	       }
	     Unsubscribed_Groups = NULL;
	  }
	
	if ((NULL == (fp = slrn_open_home_file (Slrn_Newsrc_File, "r", file, 0)))
	    && (NULL == (fp = slrn_open_home_file (".newsrc", "r", file, 0))))
	  {
	     sprintf (line, "Unable to open %s.", file);
	     slrn_exit_error (line);
	  }
	
	while (fgets (line, sizeof(line) - 1, fp) != NULL)
	  {
	     p = line;
	     while (((ch = *p) != '!') && (ch != ':') && (ch != 0))
	       {
		  p++;
	       }
	     if ((ch == 0) || (p == line)) continue;
	     if (-1 == add_group (line, (unsigned int) (p - line),
				  ((ch == '!') ? GROUP_UNSUBSCRIBED : 0),
				  0))
	       continue;
	     
	     /* perform a re-arrangement to match arrangement in the
	      * newsrc file
	      */
	     if (Slrn_List_Active_File && (ch !=  '!'))
	       {
		  last_group = place_group_in_newsrc_order (last_group);
	       }
	  }
     }
   
   slrn_fclose (fp);
   Current_Group = Groups;
   find_line_num ();
   toggle_hide_groups ();
   
   /* Unhide the new groups.  Do it here so that if there are no unread 
    * articles, it will be visible but also enables user to toggle them
    * so that they will become invisble again.
    */
   Current_Group = Groups;
   while ((Current_Group != NULL)
	  && (Current_Group->flags & GROUP_NEW_GROUP_FLAG))
     {
	Current_Group->flags &= ~GROUP_HIDDEN;
	Current_Group = Current_Group->next;
     }
   Current_Group = Groups;
   find_line_num ();
   
   if (create_flag)
     {
	/* toggle_list_all_groups1 (0); */
     }
   if (create_flag) Slrn_Groups_Dirty = 1;
   group_bob ();
   return 0;
}

int slrn_select_group_mode (void)
{
   Slrn_Current_Mode = &Group_Mode_Cap;
   
   quick_help ();

   Last_Cursor_Row = 0;
   Top_Group = NULL;
   Slrn_Full_Screen_Update = 1;
   return 0;
}

/*  -----------------------  update screen for group mode ---------- */


static void group_update_screen (void)
{
   Slrn_Group_Type *g;
   int height = Slrn_Group_Window_Size;
   int row;
   int curr_row = 0;
   
   /* erase last cursor */
   if (Last_Cursor_Row && !Slrn_Full_Screen_Update)
     {
	SLsmg_gotorc (Last_Cursor_Row, 0);
	SLsmg_write_string ("  ");
     }
   
   g = Top_Group;
   
   /* Find out if the group is already  visible */
   row = 0;
   while ((g != NULL) && (g != Current_Group) && (row < height))
     {
	if (g->flags & GROUP_HIDDEN)
	  {
	     while ((g != NULL) && (g->flags & GROUP_HIDDEN))
	       {
		  g = g->next;
	       }
	  }
	else
	  {
	     g = g->next;
	     row++;
	  }
     }
   
   if (row == height) g = NULL;
   Last_Cursor_Row = row + 1;
   
   if (g == NULL)
     {
	Top_Group = g = Current_Group;
	Slrn_Full_Screen_Update = 1;
     }
   else g = Top_Group;
   
   for (row = 0; row < height; row++)
     {
	
	while ((g != NULL) && (g->flags & GROUP_HIDDEN))
	  g = g->next;
	
	if (g == Current_Group)
	  {
	     curr_row = row;
	  }
	
	if (g != NULL)
	  {
	     if (Slrn_Full_Screen_Update || (g->flags & GROUP_TOUCHED))
	       {
		  SLsmg_gotorc (row + 1, 0);
		  SLsmg_printf ("  %c%5d  ",
				((g->flags & GROUP_UNSUBSCRIBED) ? 'U'
				 : ((g->flags & GROUP_NEW_GROUP_FLAG) ? 'N' : ' ')),
				g->unread);
		  slrn_set_color (GROUP_COLOR);
		  SLsmg_printf ("%s", g->name);
		  slrn_set_color (0);
		  SLsmg_erase_eol ();
		  if (Slrn_Group_Display_Descriptions)
		    {
		       if (g->descript != NULL)
			 {
			    int dsc_row;
			    
			    dsc_row = SLsmg_get_column ();
			    
			    if (dsc_row < Slrn_Group_Description_Column)
			      dsc_row = Slrn_Group_Description_Column;
			    else dsc_row++;
			    SLsmg_gotorc (row + 1, dsc_row);
			    SLsmg_set_color (GROUP_DESCR_COLOR);
			    SLsmg_write_string (g->descript);
			    SLsmg_set_color (0);
			 }
		    }
		  else if (g->range.max != -1)
		    {
		       SLsmg_gotorc (row + 1, 63);
		       SLsmg_printf ("%7d-%-7d", g->range.min, g->range.max);
		    }
		  g->flags &= ~GROUP_TOUCHED;
		  
		  if (g == Current_Group) Last_Cursor_Row = row + 1;
	       }
	     Bottom_Group = g;
	     g = g->next;
	  }
	else if (Slrn_Full_Screen_Update)
	  {
	     SLsmg_gotorc (row + 1, 0);
	     SLsmg_erase_eol ();
	     Bottom_Group = NULL;
	  }
     }
   SLsmg_gotorc (SLtt_Screen_Rows - 2, 0);
   slrn_set_color (STATUS_COLOR);
   if (Slrn_Groups_Dirty)
     SLsmg_write_string ("-*-News Groups: ");
   else SLsmg_write_string ("---News Groups: ");
   
   if (Slrn_Server_Obj->sv_name != NULL)
     SLsmg_write_string (Slrn_Server_Obj->sv_name);

   slrn_print_percent (SLtt_Screen_Rows - 2, 60, Line_Num, Num_Groups, height - curr_row);
   
   if (Slrn_Use_Mouse) slrn_update_group_menu ();
   else update_top_status_line ();
   
   if (Slrn_Message_Present == 0) quick_help ();
   
   SLsmg_gotorc (Last_Cursor_Row, 0);
   
   slrn_set_color (CURSOR_COLOR);
   SLsmg_write_string ("->");
   slrn_set_color (0);
   Slrn_Full_Screen_Update = 0;
}



char *slrn_current_group (void)
{
   if (Current_Group == NULL)
     return "";
   return Current_Group->name;
}

These are the contents of the former NiCE NeXT User Group NeXTSTEP/OpenStep software archive, currently hosted by Netfuture.ch.