/* ARBITRON.C - Calculate number of readers of each newsgroup for ANU NEWS.
	This is meant to be run once per month, with results mailed to
	netsurvey@decwrl.dec.com in time to get there before midnight on the
	last day of the month.  To be safe, do this on about the 20th of the
	month. The results are placed in the file ARBITRON.OUT.

	Just about all of the numbers generated by this program are estimates
	of some kind.  Number of users on the system is the number of
	accounts that have logged on interactively in the past 30 days.  Number
	of news readers is the number of users who are readers of at least
	one newsgroup.

	This must run from a place that has read access to every NEWSRC
	file on the system.  The NEWSRC file must be in the user's
	default directory to be counted.

	Lenny Glassmann
	Cambridge Computer Associates
	56 Beaver St.
	New York, NY 10004
	(212) 425-5830
	lenny@ccavax.camb.com

Thanks to:
	Geoff Huston (gih900@csc.anu.oz.au) for ANU NEWS.
	Joe Meadows (joe@fhcrcvax.bitnet) for the UAF reading code.
	Brian Reid (reid@decwrl.dec.com) for the original UNIX arbitron.

History:
	Dec 27 1989 - Initial release.
	Jan 13 1990 - V1.1 count groups with only 1 item.
	Mar  8 1991 - Glass@vixvax.mgi.com - profile routines faked to
		      avoid side effects from newsprofile routines
	Mar 12 1991 - Geoff Huston V2
		      Alter newsrc file format to include substring
		      #<hexdate># which is date of last read within the
		      newsgroup by the user.
		      Also do not read inlocal database - the system may
		      be configured as a diskless client and have no
		      local newsgroup.
**	V6.1b9	17-Aug-1994     Mark Martinec   mark.martinec@ijs.si
**	  - code cleanup to make it compile under gcc 2.6.0 with full
**	    warnings reporting turned on - with no or very few harmless warnings
**	V6.1b9	17-Sep-1994     Mark Martinec   mark.martinec@ijs.si
**	  - code cleanup to preserve the read-only nature of string literals
**	    (strategically placed 'const' attribute to string parameters)
*/

#ifdef vaxc
#module ARBITRON "V2.1"
#endif

#define _ARBITRON_C
#define module_name "ARBITRON"

/* Cause variable declarations for things declared in NEWSVARIABLES.H */
#define _NEWS_MAIN_PROGRAM

#include "newsinclude.h"
#include "newsextern.h"
#include "uaf.h"

#if NAKED_INCLUDES
#include libdef
#else
#include <libdef.h>
#endif

/* The _toupper and _tolower are defined as macros in ctype.h (with VAXC),
 * but the gcc does not define them (which is ok, as they are not ansi) -
 * - so we do it here:  */
#ifndef _tolower
#define _tolower(c)	((c) >= 'A' && (c) <= 'Z' ? (c) | 0x20 : (c))
#endif

typedef struct g_struct {
    char *name;
    unsigned int readers;
    unsigned int add_one;
    struct g_struct *next;
    } GRP_INFO, *GRP_INFO_PTR;

typedef struct u_struct {
    char *devdir;
    char *name;
    struct u_struct *next;
    } USER_STRUCT;

typedef unsigned long SYSTIME[2];

static struct FAB uaffab;
static struct RAB uafrab;
static struct UAFDEF record;
static FILE *outfile;
static GRP_INFO_PTR grparray = 0, grplast = 0;
static SYSTIME curtime;
static char version[] = "vms-arbitron-2.1";
static int news_readers = 0;
static int num_users = 0;

extern int sor$return_rec(struct dsc$descriptor_s *);
extern int sor$begin_sort(int *, unsigned short *,
                          int, int, int, int, int, int, int);
extern int sor$release_rec(struct dsc$descriptor_s *);
extern int sor$sort_merge(int);

void open_uaf();
void open_outfiles();
int duplicate_user(char *, char *);
int reads_group(char *, int);
void add_to_totals();
void sort_output();
void write_header();
void write_data();

int main(void)
{
  const char *reg_filename = "newsrc.*";
  $DESCRIPTOR_CONST(delta_d, "30 0");		/* Thirty days */
  char *cp;
  char *cp2;
  char user_reads_news;
  int devlen;
  int dirlen;
  short namelen;
  char username[UAF$S_USERNAME + 1];
  SYSTIME delta_t;
  SYSTIME month_ago;
  char fnam[256], rnam[255], *p, *in_line;
  int file_count, prev_thirty;
  time_t read_time;
  struct FAB *context = 0;
  $DESCRIPTOR_CONST(fnam_dsc,fnam);
  $DESCRIPTOR(rnam_dsc,rnam);
  FILE *fpr;

  /* figure out the date that was 30 days ago */
  sys$gettim(&curtime);
  sys$bintim(&delta_d, &delta_t);
  lib$sub_times(&curtime, &delta_t, &month_ago);

  time(&read_time);
  prev_thirty = read_time - (30 * 24 * 60 * 60);

  open_uaf();		/* open SYSUAF.DAT */
  open_outfiles();

  while (sys$get(&uafrab) & 1)	{	/* read every record in the UAF */
    cp = username;
    cp2 = record.uaf$t_username;
    namelen = 0;
    while ((*cp2 != ' ') && (namelen++ < UAF$S_USERNAME)) *cp++ = *cp2++;
    *cp = '\0';
    printf ("%s ", username);

    if (lib$sub_times(&record.uaf$q_lastlogin_i, &month_ago,&delta_t) == LIB$_NEGTIM) {
      printf("hasn't logged in this month\n");
      continue;				/* skip this user */
      }

    num_users++;
    devlen = *record.uaf$t_defdev;
    dirlen = *record.uaf$t_defdir;
    if (devlen + dirlen + strlen (reg_filename) + 1 > sizeof(news_register)) {
      printf ("register file name is too long\n");
      continue;
      }
    memcpy(news_register, &record.uaf$t_defdev[1], devlen);
    memcpy(news_register + devlen, &record.uaf$t_defdir[1], dirlen);
    *(news_register + devlen + dirlen) = '\0';

    /* if more than one user has the same default directory, and therfore
       the same NEWSRC file, we only want to count it once.		     */

    if (duplicate_user(news_register, username)) continue;

    user_reads_news = FALSE;
    strcat(news_register, reg_filename);
    strcpy(fnam, news_register);
    file_count = 0;
    context = 0;
    fnam_dsc.dsc$w_length = strlen(news_register);

    while (lib$find_file(&fnam_dsc,&rnam_dsc,&context,0,0,0,0) & 1) {
      rnam[254] = '\0';
      if ( (p = strchr(rnam,' ')) ) *p = '\0';

      if (!(fpr = fopen(rnam,"r"))) continue;
      if (!(in_line = fgetl(fpr))) {
        fclose(fpr);
        continue;
        }
      while ( (in_line = fgetl(fpr)) ) {
        if (!strcmp(in_line,"MARKLIST\n") ||
            !strcmp(in_line,"KILLLIST\n") ||
            !strcmp(in_line,"PROFILE\n")) break;
        read_time = 0;
        if ((cp = strchr(in_line,':')) && (p = strchr(cp,'#'))
            && sscanf(p+1,"%lX",&read_time) && (read_time > prev_thirty)) {
          *cp = '\0';
          reads_group(in_line,1);
          user_reads_news = TRUE;
          }
	else if (cp) {
          *cp = '\0';
          reads_group(in_line,0);
          }
        }
      fclose(fpr);
      }
    lib$find_file_end(&context);
    if (user_reads_news) {
      news_readers++;
      add_to_totals();
      printf ("reads news\n");
      }
    else printf ("doesn't read news\n");
    }
  sort_output ();
  write_header ();
  write_data ();
}

int reads_group(g,inc)
  char *g;
  int inc;
{
  GRP_INFO_PTR tmp = grparray;

  while (tmp && strcmp(tmp->name,g)) tmp = tmp->next;
  if (tmp) return(tmp->add_one += inc);
  tmp = malloc(sizeof *tmp);
  strcpy((tmp->name = malloc(strlen(g) + 1)),g);
  tmp->readers = 0;
  tmp->add_one = inc;
  tmp->next = 0;
  if (grplast) grplast->next = tmp;
  else grparray = tmp;
  grplast = tmp;
  return(1);
}

void add_to_totals(void)
{
  GRP_INFO_PTR tmp = grparray;

  while (tmp) {
    if (tmp->add_one) {
      ++(tmp->readers);
      tmp->add_one = 0;
      }
    tmp = tmp->next;
    }
}

void write_header(void)
{
  char *cp;
  char timbuf[13];
  $DESCRIPTOR (timbuf_d, timbuf);
  char *hostnam;

  if (!(hostnam = news_getenv("NEWS_ADDRESS",1))) {
    printf("\nUnable to read NEWS_ADDRESS logical name\n");
    strcpy(hostnam=malloc(32), "unknown");
    }

  for (cp = hostnam; *cp; cp++) *cp = _tolower (*cp);
  fprintf (outfile, "Host            %s\n", hostnam);
  fprintf (outfile, "Users           %d\n", num_users);
  fprintf (outfile, "NetReaders      %d\n", news_readers);
  sys$asctim (0, &timbuf_d, &curtime, 0);
  fprintf (outfile, "ReportDate      %c%c%c%.4s\n",
	timbuf[3], _tolower(timbuf[4]), _tolower(timbuf[5]), &timbuf[7]);
  fprintf (outfile, "SystemType      %s\n", version);
}

void write_data(void)
{
  GRP_INFO group_rec;
  struct dsc$descriptor_s rec_d;
  unsigned int ret;

  if (!grparray) return;
  rec_d.dsc$w_length = sizeof (group_rec);
  rec_d.dsc$b_dtype = DSC$K_DTYPE_T;
  rec_d.dsc$b_class = DSC$K_CLASS_S;
  rec_d.dsc$a_pointer = (char *) &group_rec;

  while ((ret = sor$return_rec (&rec_d)) == SS$_NORMAL)
    fprintf (outfile, "%d %s\n", group_rec.readers, group_rec.name);

  if (ret != SS$_ENDOFFILE) lib$stop(ret);
}

/* sort the newsgroup lines in descending order by number of readers */
void sort_output(void)
{
  GRP_INFO_PTR tmp = grparray;
  unsigned int ret;
  unsigned short sort_buf[] =
	{1, DSC$K_DTYPE_LU, 1, 0,0};
  unsigned short reclen = sizeof (*grparray);
  struct dsc$descriptor_s rec_d;

  if (!grparray) return;
  sort_buf[3] = (char *)&(grparray->readers) - (char *)grparray;
  sort_buf[4] = sizeof(grparray->readers);
  if ((ret = sor$begin_sort ((int *)&sort_buf, &reclen, 0, 0, 0, 0, 0, 0, 0)) != SS$_NORMAL) lib$stop (ret);
  rec_d.dsc$w_length = sizeof (*grparray);
  rec_d.dsc$b_dtype = DSC$K_DTYPE_T;
  rec_d.dsc$b_class = DSC$K_CLASS_S;

  while (tmp) {
    rec_d.dsc$a_pointer = (char *) tmp;
    if ((ret = sor$release_rec (&rec_d)) != SS$_NORMAL) lib$stop(ret);
    tmp = tmp->next;
    }

  if ((ret = sor$sort_merge (0)) != SS$_NORMAL) lib$stop(ret);
}

/* check for users who have the same default directory */
int duplicate_user (devdir, name)
  char *devdir;
  char *name;
{
  static USER_STRUCT *top_user = NULL;
  register USER_STRUCT *userp;
  register USER_STRUCT *new;
  USER_STRUCT *prev;
  char past = FALSE;
  short cmp;

  prev = NULL;
  userp = top_user;
  while (userp && !past) {
    cmp = strcmp (userp->devdir, devdir);
    if (cmp == 0) {
      printf ("duplicated by %s\n", userp->name);
      return (TRUE);
      }
    if (cmp > 0) past = TRUE;
    else {
      prev = userp;
      userp = userp->next;
      }
    }

  new = malloc (sizeof (USER_STRUCT));
  new->devdir = malloc (strlen (devdir) + 1);
  strcpy (new->devdir, devdir);

  new->name = malloc (strlen (name) + 1);
  strcpy (new->name, name);

  if (top_user == NULL) {
    new->next = NULL;
    top_user = new;
    }
  else if (prev == NULL) {
    new->next = top_user;
    top_user = new;
    }
  else {
    new->next = prev->next;
    prev->next = new;
    }

  return (FALSE);
}

void open_uaf(void)
{
  int sts;
  static char filename[] = "sysuaf";
  static char dfltname[] = "sys$system:.dat";

  uaffab = cc$rms_fab;
  uafrab = cc$rms_rab;
  uaffab.fab$b_fns = sizeof(filename) - 1;
  uaffab.fab$l_fna = filename;
  uaffab.fab$b_dns = sizeof(dfltname) - 1;
  uaffab.fab$l_dna = dfltname;
  uaffab.fab$b_fac = FAB$M_GET;
  uaffab.fab$b_shr = FAB$M_SHRGET | FAB$M_SHRPUT | FAB$M_SHRDEL | FAB$M_SHRUPD;
  uafrab.rab$l_rop = RAB$M_NLK;
  uafrab.rab$w_usz = sizeof(record);
  uafrab.rab$l_ubf = (char *) &record;
  uafrab.rab$l_fab = &uaffab;

  sts = sys$open(&uaffab);
  if (!(sts & 1)) lib$stop (sts, uaffab.fab$l_stv);

  sts = sys$connect(&uafrab);
  if (!(sts & 1)) lib$stop (sts, uafrab.rab$l_stv);
}

void open_outfiles(void)
{
  if ((outfile = fopen ("arbitron.out", "w")) == NULL) {
    printf ("arbitron: unable to open arbitron.out\n");
    exit (0);
    }
}
