/*
 *  Project   : tin - a threaded Netnews reader
 *  Module    : art.c
 *  Author    : I.Lea & R.Skrenta
 *  Created   : 01-04-91
 *  Updated   : 12-05-92
 *  Notes     :
 *  Copyright : (c) Copyright 1991-92 by Iain Lea & Rich Skrenta
 *              You may  freely  copy or  redistribute  this software,
 *              so  long as there is no profit made from its use, sale
 *              trade or  reproduction.  You may not change this copy-
 *              right notice, and it must be included in any copy made
 */

#include	"tin.h"

char index_file[PATH_LEN];
char *glob_art_group;
static long last_read_article;


/*
 *  Construct the pointers to the basenotes of each thread
 *  arts[] contains every article in the group.  inthread is
 *  set on each article that is after the first article in the
 *  thread.  Articles which have been expired have their thread
 *  set to -2 (ART_EXPIRED).
 */

void find_base (only_unread)
	int only_unread;
{
	register int i;
	register int j;

	top_base = 0;

	debug_print_arts ();

	if (only_unread) {
		for (i = 0; i < top; i++) {
			if (IGNORE_ART(i) || arts[i].inthread != FALSE) {
				continue;
			}	
			if (top_base >= max_art) {
				expand_art ();
			}
			if (arts[i].unread == ART_UNREAD) {
				base[top_base++] = i;
			} else {
				for (j = i ; j >= 0 ; j = arts[j].thread) {
					if (arts[j].unread) {
						base[top_base++] = i;
						break;
					}
				}
			}
		}
	} else {
		for (i = 0; i < top; i++) {
			if (IGNORE_ART(i) || arts[i].inthread != FALSE) {
				continue;
			}	
			if (top_base >= max_art) {
				expand_art ();
			}
			base[top_base++] = i;
		}
	}
}

/* 
 *  Count the number of non-expired articles in arts[]
 */

int num_of_arts ()
{
	int sum = 0;
	register int i;

	for (i = 0; i < top; i++) {
		if (arts[i].thread != ART_EXPIRED) {
			sum++;
		}
	}

	return sum;
}

/*
 *  Do we have an entry for article art?
 */

int valid_artnum (art)
	long art;
{
	register int i;

	for (i = 0; i < top; i++)
		if (arts[i].artnum == art)
			return i;

	return -1;
}

/*
 *  Return TRUE if arts[] contains any expired articles
 *  (articles we have an entry for which don't have a corresponding
 *   article file in the spool directory)
 */

int purge_needed ()
{
	register int i;

	for (i = 0; i < top; i++)
		if (arts[i].thread == ART_EXPIRED)
			return TRUE;

	return FALSE;
}

/*
 *  Main group indexing routine.  Group should be the name of the
 *  newsgroup, i.e. "comp.unix.amiga".  group_path should be the
 *  same but with the .'s turned into /'s: "comp/unix/amiga"
 *
 *  Will read any existing index, create or incrementally update
 *  the index by looking at the articles in the spool directory,
 *  and attempt to write a new index if necessary.
 */

void index_group (group, group_path)
	char *group;
	char *group_path;
{
	int killed = FALSE;
	int modified = FALSE;
	glob_art_group = group;

	set_alarm_clock_off ();

	set_signals_art ();
	
	if (! update) {
		sprintf (msg, txt_group, group);
		wait_message (msg);
	}
	hash_reclaim ();
	free_art_array ();

	/*
	 *  load articles from index file if it exists
	 */
	read_index_file (group);

	/*
	 *  add any articles to arts[] that are new or were killed
	 */
	modified = read_group (group, group_path);

	if (modified || purge_needed ()) {
		write_index_file (group);
	}
	read_newsrc_line (group);
	killed = kill_any_articles (group); /* do after read_newsrc_line() */
	make_threads (FALSE);
	find_base (show_only_unread);
	
	if ((modified || killed) && ! update) {
		clear_message ();
	}
	
	set_alarm_clock_on ();
}

/*
 *  Index a group.  Assumes any existing index has already been
 *  loaded.
 */

int read_group (group, group_path)
	char *group;
	char *group_path;
{
	FILE *fp;
	int count = 0;
	int modified = FALSE;
	int respnum;
	long art;
	register int i;

	setup_base (group, group_path);	/* load article numbers into base[] */

	for (i = 0; i < top_base; i++) {	/* for each article # */
		art = base[i];

/*
 *  Do we already have this article in our index?  Change thread from
 *  (ART_EXPIRED) to (ART_NORMAL) if so and skip the header eating.
 */

		if ((respnum = valid_artnum (art)) >= 0 || art <= last_read_article) {
			if (respnum >= 0) {
				arts[respnum].thread = ART_NORMAL;
				arts[respnum].unread = ART_UNREAD;
			}	
			continue;
		}

		if (! modified) {
			modified = TRUE;   /* we've modified the index */
							   /* it will need to be re-written */
		}

		if ((fp = open_header_fp (group_path, art)) == (FILE *) 0) {
			continue;
		}
		
		/*
		 *  Add article to arts[]
		 */
		if (top >= max_art)
			expand_art();

		arts[top].artnum = art;
		arts[top].thread = ART_NORMAL;

		set_article (&arts[top]);

		if (! parse_headers (fp, &arts[top])) {
			debug_nntp ("read_group", "FAILED parse_header()");
			continue;
		}

		fclose (fp);
		last_read_article = arts[top].artnum;	/* used if arts are killed */
		top++;

		if (++count % MODULO_COUNT_NUM == 0 && ! update) {
#ifndef SLOW_SCREEN_UPDATE
			sprintf (msg, txt_indexing_num, group, count);
#else
			sprintf (msg, txt_indexing, group);
#endif
			wait_message (msg);
		}
	}

	return modified;
}


/*
 *  Go through the articles in arts[] and use .thread to snake threads
 *  through them.  Use the subject line to construct threads.  The
 *  first article in a thread should have .inthread set to FALSE, the
 *  rest TRUE.  Only do unexprired articles we haven't visited yet
 *  (arts[].thread == -1 ART_NORMAL).
 */

void make_threads (rethread)
	int rethread;
{
	extern int cur_groupnum;
	register int i;
	register int j;

	if (!cmd_line) {
		if (thread_arts) {
			wait_message (txt_threading_arts);
		} else {
			wait_message (txt_unthreading_arts);
		}
	}

	/*
	 *  .thread & .inthread need to be reset if re-threading arts[]
	 */
	if (rethread && active[my_group[cur_groupnum]].attribute.thread) {
		for (i=0 ; i < top ; i++) {
			arts[i].thread = ART_NORMAL;
			arts[i].inthread = FALSE;
		}
	}

	switch (sort_art_type) {
		case SORT_BY_NOTHING:		/* don't sort at all */
			qsort ((char *) arts, top, sizeof (struct article_t), artnum_comp);
			break;
		case SORT_BY_SUBJ_DESCEND:
		case SORT_BY_SUBJ_ASCEND:
			qsort ((char *) arts, top, sizeof (struct article_t), subj_comp);
			break;
		case SORT_BY_FROM_DESCEND:
		case SORT_BY_FROM_ASCEND:
			qsort ((char *) arts, top, sizeof (struct article_t), from_comp);
			break;
		case SORT_BY_DATE_DESCEND:
		case SORT_BY_DATE_ASCEND:
			qsort ((char *) arts, top, sizeof (struct article_t), date_comp);
			break;
		default:
			break;
	}

	if (thread_arts == 0 || active[my_group[cur_groupnum]].attribute.thread == 0) {
		return;
	}

	for (i = 0; i < top; i++) {
		if (arts[i].thread != ART_NORMAL || IGNORE_ART(i)) {
			continue;
		}	
		for (j = i+1; j < top; j++) {
			if (! IGNORE_ART(j) && 
			   ((arts[i].subject == arts[j].subject) ||
			   ((arts[i].part || arts[i].patch) &&
			   arts[i].archive == arts[j].archive))) {
				arts[i].thread = j;
				arts[j].inthread = TRUE;
				break;
			}
		}
	}
}


int parse_headers (fp, h)
	FILE *fp;
	struct article_t *h;
{
	char buf[HEADER_LEN];
	char buf2[HEADER_LEN];
	char art_from_addr[LEN];
	char art_full_name[LEN];
	char *ptr, *ptrline, *s;
	int flag, n = 0;
	int len = 0, lineno = 0;
	int got_subject = FALSE;
	int got_from = FALSE;
	int got_date = FALSE;
	int got_archive = FALSE;
	extern int errno;

	while ((n = fread (buf, 1, sizeof (buf)-1, fp)) == 0) {
		if (feof (fp)) {
			break;
		}
  
		if (ferror (fp) && errno != EINTR) {
			break;
		}
 
		clearerr (fp);
	}

	if (n == 0) {
		return FALSE;
	}

	buf[n-1] = '\0';
  	
	ptr = buf;

	while (1) {
		for (ptrline = ptr; *ptr && *ptr != '\n'; ptr++) {
			if (((*ptr) & 0xFF) < ' ') {
				*ptr = ' ';
			}
		}
		flag = *ptr;
		*ptr++ = '\0';
		lineno++;

		if (! got_from && match_header (ptrline, "From", buf2, HEADER_LEN)) {
			parse_from (buf2, art_from_addr, art_full_name); 
			h->from = hash_str (art_from_addr);
			h->name = hash_str (art_full_name);
			got_from = TRUE;
		} else if (! got_subject && match_header (ptrline, "Subject", buf2, HEADER_LEN)) {
			s = eat_re (buf2);
			h->subject = hash_str (eat_re (s));
			got_subject = TRUE;
		} else if (! got_date && match_header (ptrline, "Date", buf2, HEADER_LEN)) {
			parse_date (buf2, h->date);
			got_date = TRUE;
		} else if (match_header (ptrline, "Archive-name", buf2, HEADER_LEN) ||
					match_header (ptrline, "Archive-Name", buf2, HEADER_LEN)) {
			if ((s = (char *) strchr (buf2, '/')) != NULL) {
				if (strncmp (s+1, "part", 4) == 0 ||
				    strncmp (s+1, "Part", 4) == 0) {
					h->part = str_dup (s+5);
					len = (int) strlen (h->part);
					if (h->part[len-1] == '\n') {
						h->part[len-1] = '\0';
					}
				} else {
					if (strncmp (s+1,"patch",5) == 0 ||
					    strncmp (s+1,"Patch",5) == 0) {
						h->patch = str_dup (s+6);
						len = (int) strlen (h->patch);
						if (h->patch[len-1] == '\n') {
							h->patch[len-1] = '\0';
						}
					}
				}
				if (h->part || h->patch) {
					s = buf2;
					while (*s && *s != '/')
						s++;
					*s = '\0';	
					s = buf2;
					h->archive = hash_str (s);
					got_archive = TRUE;
				}
			}
		}

		if (! flag || lineno > 25 || got_archive) {
			if (got_subject && got_from && got_date) {
				debug_print_header (h);
				return TRUE;
			} else {
				return FALSE;
			}	
		}
	}
	/* NOTREACHED */
}

/* 
 *  Write out  an index file.  Write the group name first so if
 *  local indexing is done so we can disambiguate between group
 *  name hash collisions by looking at the index file.
 */

void write_index_file (group)
	char *group;
{
	char nam[LEN];
	FILE *fp;
	int *iptr;
	int realnum;
	register int i;

	set_tin_uid_gid();

        sprintf (nam, "%s.%d", index_file, process_id);
	if ((fp = fopen (nam, "w")) == NULL) {
		perror_message (txt_cannot_open, nam);
		set_real_uid_gid ();
		return;
	}

	/*
	 *  dump group header info.
	 */
	if (sort_art_type != SORT_BY_NOTHING) {
		qsort ((char *) arts, top, sizeof (struct article_t), artnum_comp);
	}
	fprintf (fp, "%s\n", group);
	fprintf (fp, "%d\n", num_of_arts ());
	if (top <= 0) {
		fprintf (fp, "0\n");
	} else {
		if (last_read_article > arts[top-1].artnum) {
			fprintf (fp, "%ld\n", last_read_article);
		} else {
			fprintf (fp, "%ld\n", arts[top-1].artnum);
		}
	}

	/*
	 *  dump articles
	 */
	realnum = 0; 
	for (i = 0; i < top; i++) {
		if (arts[i].thread == ART_EXPIRED) { 
			continue;
		}
#ifdef DEBUG			
		debug_print_header (&arts[i]);
#endif
		fprintf(fp, "%ld\n", arts[i].artnum);

		iptr = (int *) arts[i].subject;
		iptr--;

		if (! arts[i].subject) {
			fprintf(fp, " \n");
		} else if (*iptr < 0 || *iptr > top) {
			fprintf(fp, " %s\n", arts[i].subject);
			*iptr = realnum;
		} else if (*iptr == i) {
			fprintf(fp, " %s\n", arts[i].subject);
		} else {
			fprintf(fp, "%%%d\n", *iptr);
		}
	
		iptr = (int *) arts[i].from;
		iptr--;

		if (! arts[i].from) {
			fprintf (fp, " \n");
		} else if (*iptr < 0 || *iptr > top) {
			fprintf (fp, " %s\n", arts[i].from);
			*iptr = realnum;
		} else if (*iptr == i) {
			fprintf(fp, " %s\n", arts[i].from);
		} else {
			fprintf(fp, "%%%d\n", *iptr);
		}

		iptr = (int *) arts[i].name;
		iptr--;

		if (! arts[i].name) {
			fprintf (fp, " \n");
		} else if (*iptr < 0 || *iptr > top) {
			fprintf (fp, " %s\n", arts[i].name);
			*iptr = realnum;
		} else if (*iptr == i) {
			fprintf(fp, " %s\n", arts[i].name);
		} else {
			fprintf(fp, "%%%d\n", *iptr);
		}

		fprintf (fp, "%s\n", arts[i].date);
			
		iptr = (int *) arts[i].archive;
		iptr--;

		if (! arts[i].archive) {
			fprintf (fp, "\n");
		} else if (*iptr < 0 || *iptr > top) {
			fprintf (fp, " %s\n", arts[i].archive);
			*iptr = realnum;
		} else if (arts[i].part || arts[i].patch) {
			if (*iptr == i) {
				fprintf(fp, " %s\n", arts[i].archive);
			} else {
				fprintf (fp, "%%%d\n", *iptr);
			}
		} else {
			fprintf (fp, "\n");
		}
			
		if (! arts[i].part) {
			fprintf (fp, " \n");
		} else {
			fprintf (fp, "%s\n", arts[i].part);
		}

		if (! arts[i].patch) {
			fprintf (fp, " \n");
		} else {
			fprintf (fp, "%s\n", arts[i].patch);
		}

		realnum++;
	}
	fclose (fp);
	rename_file (nam, index_file);
	chmod (index_file, 0644);
	set_real_uid_gid();
	if (debug == 2) {
		sprintf (msg, "cp %s INDEX", index_file);
		system (msg);
	}
}

/*
 *  Read in an index file.
 *
 *  index file header 
 *    1.  newsgroup name (ie. alt.sources)
 *    2.  number of articles (ie. 26)
 *    3.  number of last read article (ie. 210)
 *    4.  Is this a complete/killed index file (ie. COMPLETE/KILLED)
 *
 *  index file record
 *    1.  article number    (ie. 183)               [mandatory]
 *    2.  Subject: line     (ie. Which newsreader?) [mandatory]
 *    3.  From: line (addr) (ie. iain@norisc)       [mandatory]
 *    4.  From: line (name) (ie. Iain Lea)          [mandatory]
 *    5.  Date: of posting  (ie. 911231125959)      [mandatory]
 *    6.  Archive: name     (ie. compiler)          [optional]
 *    7.  Part number of Archive: name  (ie. 01)    [optional]
 *    8.  Patch number of Archive: name (ie. 01)    [optional]
 */

int read_index_file (group_name)
	char *group_name;
{
	int error = 0;
	int i, n;
	char buf[LEN], *p;
	FILE *fp = NULL;

	top = 0;
	last_read_article = 0L;

	if ((fp = open_index_fp (group_name)) == NULL) {
		return FALSE;
	}

	/*
	 *  load header - discard group name, num. of arts in index file after any arts were killed
	 */
	if (fgets(buf, sizeof buf, fp) == NULL ||
		fgets(buf, sizeof buf, fp) == NULL) {
		error = 0;			
		goto corrupt_index;	
	}
	i = atoi (buf);

	/*
	 * num. of last_read_article including any that were killed
	 */
	if (fgets(buf, sizeof buf, fp) == NULL) {
		error = 1;				
		goto corrupt_index;	
	}							
	last_read_article = (long) atol (buf);
	
	/*
	 *  load articles
	 */
	for (; top < i ; top++) {
		if (top >= max_art) {
			expand_art ();
		}

		arts[top].thread = ART_EXPIRED;
		set_article (&arts[top]);

		/*
		 * Article no.
		 */
		if (fgets(buf, sizeof buf, fp) == NULL) {
			error = 2;
			goto corrupt_index;
		}
		arts[top].artnum = (long) atol (buf);

		/*
		 * Subject:
		 */
		if (fgets(buf, sizeof buf, fp) == NULL) {
			error = 3;
			goto corrupt_index;
		}

		if (buf[0] == '%') {
			n = atoi (&buf[1]);
			if (n >= top || n < 0) {
				error = 4;
				goto corrupt_index;
			}
			arts[top].subject = arts[n].subject;
		} else if (buf[0] == ' ') {
			for (p = &buf[1];  *p && *p != '\n'; p++)
				continue;	
			*p = '\0';
			arts[top].subject = hash_str (&buf[1]);
		} else {
			error = 5;
			goto corrupt_index;
		}
			
		/*
		 * From: (addr part)
		 */
		if (fgets(buf, sizeof buf, fp) == NULL) {
			error = 6;
			goto corrupt_index;
		}

		if (buf[0] == '%') {
			n = atoi (&buf[1]);
			if (n >= top || n < 0) {
				error = 7;
				goto corrupt_index;
			}
			arts[top].from = arts[n].from;
		} else if (buf[0] == ' ') {
			for (p = &buf[1];  *p && *p != '\n'; p++)
				continue;
			*p = '\0';
			arts[top].from = hash_str (&buf[1]);
		} else {
			error = 8;
			goto corrupt_index;
		}

		/*
		 * From: (full name)
		 */
		if (fgets(buf, sizeof buf, fp) == NULL) {
			error = 9;
			goto corrupt_index;
		}

		if (buf[0] == '%') {
			n = atoi (&buf[1]);
			if (n > top || n < 0) {
				error = 10;
				goto corrupt_index;
			}
			if (n == top) {		/* no full name so .name = .from */
				arts[top].name = arts[top].from;
			} else {
				arts[top].name = arts[n].name;
			}
		} else if (buf[0] == ' ') {
			for (p = &buf[1];  *p && *p != '\n'; p++)
				continue;
			*p = '\0';
			arts[top].name = hash_str (&buf[1]);
		} else {
			error = 11;
			goto corrupt_index;
		}

		/*
		 * Date:
		 */
		if (fgets(buf, sizeof buf, fp) == NULL) {
			error = 12;
			goto corrupt_index;
		}

		buf[strlen (buf)-1] = '\0';
		my_strncpy (arts[top].date, buf, 12);

		/*
		 * Archive-name:
		 */
		if (fgets(buf, sizeof buf, fp) == NULL) {
			error = 13;
			goto corrupt_index;
		}

		if (buf[0] == '\n') {
			arts[top].archive = (char *) 0;
		} else if (buf[0] == '%') {
			n = atoi (&buf[1]);
			if (n > top || n < 0) {
				error = 14;
				goto corrupt_index;
			}
			arts[top].archive = arts[n].archive;
		} else if (buf[0] == ' ') {
			for (p = &buf[1]; *p && *p != '\n' ; p++)
				continue;
			*p = '\0';
			arts[top].archive = hash_str (&buf[1]);
		} else {
			error = 15;
			goto corrupt_index;
		}

		/*
		 * part no.
		 */
		if (fgets(buf, sizeof buf, fp) == NULL) {
			error = 16;
			goto corrupt_index;
		}

		if (buf[0] != ' ') { 
			buf[strlen (buf)-1] = '\0';
			arts[top].part = str_dup (buf);
		}

		/*
		 * patch no.
		 */
		if (fgets(buf, sizeof buf, fp) == NULL) {
			error = 17;
			goto corrupt_index;
		}

		if (buf[0] != ' ') { 
			buf[strlen (buf)-1] = '\0';
			arts[top].patch = str_dup (buf);
		}

		debug_print_header (&arts[top]);
	}

	fclose(fp);
	return TRUE;

corrupt_index:
	if (! update) {
		sprintf (msg, txt_corrupt_index, index_file, error, top); 
		error_message (msg, "");
	}

	if (debug == 2) {
		sprintf (msg, "cp %s INDEX.BAD", index_file);
		system (msg);
	}

	last_read_article = 0L;
	if (fp) {
		fclose(fp);
	}	
	set_tin_uid_gid();
	unlink (index_file);
	set_real_uid_gid();
	top = 0;
	return FALSE;
}


/*
 *  Look in the local $HOME/RCDIR/INDEXDIR (or wherever) directory for the
 *  index file for the given group.  Hashing the group name gets
 *  a number.  See if that #.1 file exists; if so, read first line.
 *  Group we want?  If no, try #.2.  Repeat until no such file or
 *  we find an existing file that matches our group.
 */

void find_index_file (group)
	char *group;
{
	char *p;
	FILE *fp;
	int i = 1;
	static char buf[LEN];
	char dir[PATH_LEN];
	unsigned long h;

	h = hash_groupname (group);

	if (read_news_via_nntp && xindex_supported) {
		sprintf (index_file, "/tmp/xindex.%d", process_id);
		return;
	}
	
	if (local_index) {
		my_strncpy (dir, indexdir, sizeof (dir));
	} else {
		sprintf (dir, "%s/%s", spooldir, INDEXDIR);
	}
	
	while (TRUE) {
		sprintf (index_file, "%s/%lu.%d", dir, h, i);
		
		if ((fp = fopen (index_file, "r")) == (FILE *) 0) {
			return;
		}

		if (fgets (buf, sizeof (buf), fp) == (char *) 0) {
			fclose (fp);
			return;
		}
		fclose (fp);

		for (p = buf; *p && *p != '\n'; p++) {
			continue;
		}	
		*p = '\0';

		if (strcmp (buf, group) == 0) {
			return;
		}	
		i++;
	}	
}

/*
 *  Run the index file updater only for the groups we've loaded.
 */

void do_update ()
{
	int i, j;
	char group_path[PATH_LEN];
	char *p;
	long beg_epoch, end_epoch;
	
	if (verbose) {
		time (&beg_epoch);
	}

	for (i = 0; i < group_top; i++) {
		my_strncpy (group_path, active[my_group[i]].name, sizeof (group_path));
		for (p = group_path ; *p ; p++) {
			if (*p == '.') {
				*p = '/';
			}
		}
		if (verbose) {
			printf ("%s %s\n", (catchup ? "Catchup" : "Updating"),
					active[my_group[i]].name);
			fflush (stdout);
		}
		index_group (active[my_group[i]].name, group_path);
		if (catchup) {
			for (j = 0; j < top; j++) {
				arts[j].unread = ART_READ;
			}
			update_newsrc (active[my_group[i]].name, my_group[i], FALSE);
		}
	}

	if (verbose) {
		time (&end_epoch);
		sprintf (msg, "%s %d groups in %ld seconds\n", 
			(catchup ? "Caughtup" : "Updated"), group_top, end_epoch - beg_epoch);
		wait_message (msg);
	}
}

/*
 * convert date from ctime format to sortable format
 * "24 Jul 91 12:59:59", "Mon, 24 Jul 91 12:59:59" and
 * "Mon, 24 Jul 1991 12:59:59" are parsed and produce
 * output of the form "910724125959"
 */

char *parse_date (date, str)
	char *date;
	char *str;
{
	char buf[4];
	int i = 0;

	/* Check for extraneous day-of-week at start of date */
	while (isalpha(date[i]) || date[i] == ',' || date[i] == ' ') {
		i++;
	}
	
	if (date[i+1] == ' ') {	/* ie. "2 Aug..." instead of "12 Aug... */
		str[4] = '0';		/* day */
		str[5] = date[i++];
		i++;
	} else {
		str[4] = date[i++];		/* day */
		str[5] = date[i++];
		i++;
	}
	
	buf[0] = date[i++];		/* month in Jan,Feb,.. form */
	buf[1] = date[i++];
	buf[2] = date[i++];
	buf[3] = '\0';

	i++;
	
	str[0] = date[i++];		/* year */
	str[1] = date[i++];
	if (isdigit(date[i])) {         /* 19xx format */
		str[0] = date[i++];
		str[1] = date[i++];
	}
	
	i++;
	
	if (strcmp (buf, "Jan") == 0) {		/* convert Jan to 01 etc */
		str[2] = '0';
		str[3] = '1';
	} else if (strcmp (buf, "Feb") == 0) {
		str[2] = '0';
		str[3] = '2';
	} else if (strcmp (buf, "Mar") == 0) {
		str[2] = '0';
		str[3] = '3';
	} else if (strcmp (buf, "Apr") == 0) {
		str[2] = '0';
		str[3] = '4';
	} else if (strcmp (buf, "May") == 0) {
		str[2] = '0';
		str[3] = '5';
	} else if (strcmp (buf, "Jun") == 0) {
		str[2] = '0';
		str[3] = '6';
	} else if (strcmp (buf, "Jul") == 0) {
		str[2] = '0';
		str[3] = '7';
	} else if (strcmp (buf, "Aug") == 0) {
		str[2] = '0';
		str[3] = '8';
	} else if (strcmp (buf, "Sep") == 0) {
		str[2] = '0';
		str[3] = '9';
	} else if (strcmp (buf, "Oct") == 0) {
		str[2] = '1';
		str[3] = '0';
	} else if (strcmp (buf, "Nov") == 0) {
		str[2] = '1';
		str[3] = '1';
	} else if (strcmp (buf, "Dec") == 0) {
		str[2] = '1';
		str[3] = '2';
	} else {
		str[2] = '0';
		str[3] = '0';
	}
	
	str[6] = date[i++];		/* hour */
	str[7] = date[i++];

	i++;
	
	str[8] = date[i++];		/* minutes */
	str[9] = date[i++];
	
	i++;
	
	str[10] = date[i++];	/* seconds */
	str[11] = date[i++];

	str[12] = '\0';		/* terminate string */

	return (str);
}


int artnum_comp (p1, p2)
	char *p1;
	char *p2;
{
	struct article_t *s1 = (struct article_t *) p1;
	struct article_t *s2 = (struct article_t *) p2;

	/* s1->artnum less than s2->artnum */
	if (s1->artnum < s2->artnum) {
		return -1;
	}
	/* s1->artnum greater than s2->artnum */
	if (s1->artnum > s2->artnum) {
		return 1;
	}
	return 0;
}


int subj_comp (p1, p2)
	char *p1;
	char *p2;
{
	struct article_t *s1 = (struct article_t *) p1;
	struct article_t *s2 = (struct article_t *) p2;

	/* return result of strcmp (reversed for descending) */
	return (sort_art_type == SORT_BY_SUBJ_ASCEND 
			? my_stricmp (s1->subject, s2->subject) 
			: my_stricmp (s2->subject, s1->subject));
}


int from_comp (p1, p2)
	char *p1;
	char *p2;
{
	struct article_t *s1 = (struct article_t *) p1;
	struct article_t *s2 = (struct article_t *) p2;

	/* return result of strcmp (reversed for descending) */
	return (sort_art_type == SORT_BY_FROM_ASCEND 
			? my_stricmp (s1->from, s2->from) 
			: my_stricmp (s2->from, s1->from));
}


int date_comp (p1, p2)
	char *p1;
	char *p2;
{
	struct article_t *s1 = (struct article_t *) p1;
	struct article_t *s2 = (struct article_t *) p2;
	/* return result of strcmp (reversed for descending) */
	return (sort_art_type == SORT_BY_DATE_ASCEND 
			? strcmp (s1->date, s2->date) 
			: strcmp (s2->date, s1->date));
}


void set_article (art)
	struct article_t *art;
{	
	art->subject = (char *) 0;
	art->from = (char *) 0;
	art->name = (char *) 0;
	art->date[0] = '\0';
	art->archive = (char *) 0;
	art->part = (char *) 0;
	art->patch = (char *) 0;
	art->xref = NULL;
	art->unread = ART_UNREAD;
	art->inthread = FALSE;
	art->killed = FALSE;
	art->tagged = FALSE;
	art->hot = FALSE;
	art->zombie = FALSE;
}
