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

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

/* -*- mode: C; mode: fold -*- */
/*  Copyright (c) 1996 John E. Davis (davis@space.mit.edu)
 *  All rights reserved.
 */

#include "config.h"
#include "features.h"

#include <stdio.h>
#include <string.h>

#if SLRN_HAS_GROUPLENS
/* Rest of file in this #if statement */

#include <slang.h>
#include <stdarg.h>

#include <ctype.h>

#include "jdmacros.h"

#include "slrn.h"
#include "group.h"
#include "art.h"
#include "misc.h"
#include "uudecode.h"
#include "grplens.h"
#include "sltcp.h"

/*{{{ Low level grouplens tcp stuff */



#define GL_MAX_RESPONSE_LINE_LEN	1024

/*{{{ GL_Type structure */

typedef struct
{
#define GL_MAX_HOSTNAME_LEN		255
   char hostname[GL_MAX_HOSTNAME_LEN + 1];
   int port;
   
   char *token;
   char *pseudoname;
   int logged_in;
   SLTCP_Type tcp;
}
GL_Type;

static GL_Type GL_Server;

/*}}}*/

typedef struct
{
   char *msgid;
   float pred;
   float confhigh;
   float conflow;
}
GL_Prediction_Type;

typedef struct
{
   char *msgid;
   int rating;
   int saw_article;
}
GL_Rating_Type;


/*{{{ Error codes and Error functions */

static int GL_Error;
#define GL_ERROR_UNKNOWN	       -1
#define GL_ELOGIN_UNREGISTERED		1
#define GL_ELOGIN_BUSY			2
#define GL_ELOGIN_UNAVAILABLE		3
#define GL_ELOGIN_ALREADY_LOGGEDIN	4
#define GL_ERROR_PROPER_RESPONSE	6
#define GL_ERROR_MALLOC			7
#define GL_ESERVER_WRITE		8
#define GL_ESERVER_READ			9
#define GL_ERROR_TOKEN		       10
#define GL_ERROR_NEWSGROUP	       11

typedef struct
{
   char *err;
   unsigned int len;
   int errcode;
}
GL_Error_Type;



/*{{{ Utility functions */

static char *make_nstring (char *s, unsigned int len)
{
   char *s1;
   
   if (s == NULL) return s;
   
   if (NULL == (s1 = (char *) malloc (len + 1)))
     {
	GL_Error = GL_ERROR_MALLOC;
	return NULL;
     }
   strncpy (s1, s, len);
   s1[len] = 0;
   return s1;
}

static char *make_string (char *s)
{
   if (s == NULL) return s;
   return make_nstring (s, strlen (s));
}

static char *skip_whitespace (char *s)
{
   char ch;
   
   while (((ch = *s) != 0) && isspace(ch)) s++;
   return s;
}

static char *skip_nonwhitespace (char *s)
{
   char ch;
   
   while (((ch = *s) != 0) && (0 == isspace(ch))) s++;
   return s;
}

/*}}}*/

static char *gl_get_error (void)
{
   switch (GL_Error)
     {
      case GL_ELOGIN_UNREGISTERED: return "User is Unregistered";
      case GL_ELOGIN_BUSY: return "Service is Busy";
      case GL_ELOGIN_UNAVAILABLE: return "Service Unavailable";
      case GL_ELOGIN_ALREADY_LOGGEDIN: return "Already logged in";
      case GL_ERROR_PROPER_RESPONSE: return "Server failed to return proper response";
      case GL_ERROR_MALLOC: return "Not enough memory";
      case GL_ESERVER_WRITE: return "Error writing to server";
      case GL_ESERVER_READ: return "Error reading from server";
      case GL_ERROR_TOKEN: return "Token is invalid";
      case GL_ERROR_NEWSGROUP: return "Newsgroup not supported";
     }
   
   return "Unknown Error";
}


static void close_gl (GL_Type *gl)
{
   if (gl != NULL) sltcp_close (&gl->tcp);
}

static int get_gl_response (GL_Type *gl, char *buf, unsigned int len)
{
   char *b;
   
   if (NULL == sltcp_fgets (&gl->tcp, buf, GL_MAX_RESPONSE_LINE_LEN))
     {
	GL_Error = GL_ESERVER_READ;
	close_gl (gl);
	return -1;
     }
   
   b = buf + strlen (buf);
   if (b != buf)
     {
	b--;
	if (*b == '\n') *b = 0;
	if (b != buf)
	  {
	     b--;
	     if (*b == '\r') *b = 0;
	  }
     }
   return 0;
}

static void handle_error_response (GL_Type *gl, GL_Error_Type *tbl)
{
   char *s, *s1;
   char buf [GL_MAX_RESPONSE_LINE_LEN];
   
   if (-1 == get_gl_response (gl, buf, sizeof (buf)))
     return;
	
   s = skip_whitespace (buf);
   
   GL_Error = GL_ERROR_UNKNOWN;
   
   while (*s != 0)
     {
	unsigned int len;
	GL_Error_Type *t;
	
	s1 = skip_nonwhitespace (s);
	
	len = s1 - s;
	
	t = tbl;
	while (t->err != NULL)
	  {
	     if ((t->len == len) 
		 && (0 == slrn_case_strncmp ((unsigned char *)s, (unsigned char *) t->err, len)))
	       {
		  GL_Error = t->errcode;
		  if (GL_Error == GL_ERROR_TOKEN)
		    {
		       if (gl->token != NULL)
			 free (gl->token);
		       gl->token = NULL;
		       gl->logged_in = 0;
		    }
		  break;
	       }
	     t++;
	  }
	s = skip_whitespace (s1);
     }
   
   close_gl (gl);
}
   
   
static int is_error (char *buf)
{
   if (!slrn_case_strncmp ((unsigned char *) buf, (unsigned char *) "ERROR", 5))
     return 1;
   
   return 0;
}

/*}}}*/


/*{{{ parse_keyword_eqs_value */

/* Scan buf looking for keyword=VALUE */
static int parse_keyword_eqs_value (char *buf, char *kw, char **value, unsigned int *len)
{
   char *b, *b1, *v, *v1;
   char ch;
   unsigned int kwlen;

   *value = NULL;

   kwlen = strlen (kw);
   
   b = buf;
   while (1)
     {
	b = skip_whitespace (b);
	if (*b == 0) return -1;
	
	b1 = b;
	while (((ch = *b1) != 0) && (0 == isspace (ch)) && (ch != '='))
	  b1++;
	
	if (ch == 0) return -1;
	if (ch != '=')
	  {
	     b = b1;
	     continue;
	  }
	
	v = b1 + 1;
	if (*v == '"')
	  {
	     v++;
	     v1 = v;
	     while (((ch = *v1) != 0) && (ch != '"')) v1++;
	  }
	else v1 = skip_nonwhitespace (v);
	
	if ((b + kwlen == b1) 
	    && (0 == slrn_case_strncmp ((unsigned char *) kw, (unsigned char *) b, kwlen)))
	  {
	     *value = v;
	     *len = v1 - v;
	     return 0;
	  }
	
	if (*v1 == '"') v1++;
	b = v1;
     }
}

/*}}}*/
/*{{{ low level GL server functions */



   
static int send_gl_line (GL_Type *gl, char *msg, int flush)
{
   if (msg == NULL) return 0;
   
   if ((EOF == sltcp_fputs (&gl->tcp, msg))
       || (EOF == sltcp_fputs (&gl->tcp, "\r\n"))
       || (flush && (EOF == sltcp_fflush (&gl->tcp))))
     {
	GL_Error = GL_ESERVER_WRITE;
	close_gl (gl);
	return -1;
     }
   
   return 0;
}

static int connect_to_gl_host (GL_Type *gl, char *cmd)
{
   char buf [GL_MAX_RESPONSE_LINE_LEN];
   
   GL_Error = 0;
   
   if (gl->hostname == NULL)
     {
	GL_Error = GL_ERROR_UNKNOWN;
	return -1;
     }
   
   if (-1 == sltcp_open_connection (&gl->tcp, gl->hostname, gl->port))
     return -1;
   
   /* We should be able to read OK ...  If not, we failed to make proper 
    * connection.
    */

   if (-1 == get_gl_response (gl, buf, sizeof (buf)))
     return -1;
   
   if (0 != slrn_case_strncmp ((unsigned char *) buf, (unsigned char *) "OK", 2))
     {
	GL_Error = GL_ERROR_PROPER_RESPONSE;
	close_gl (gl);
	return -1;
     }
   
   return send_gl_line (gl, cmd, 1);
}


static int start_command (GL_Type *gl, char *fmt, ...)
{
   va_list ap;
   int ret;
   
   if (-1 == connect_to_gl_host (gl, NULL))
     return -1;
   
   va_start(ap, fmt);
   ret = sltcp_vfprintf (&gl->tcp, fmt, ap);
   va_end (ap);
   
   if ((ret == -1)
       || (EOF == sltcp_fputs (&gl->tcp, "\r\n")))
     {
	close_gl (gl);
	return -1;
     }
   
   return 0;
}



static int check_for_error_response (GL_Type *gl, GL_Error_Type *tbl)
{
   char response [GL_MAX_RESPONSE_LINE_LEN];
   
   if (-1 == get_gl_response (gl, response, sizeof (response)))
     return -1;
   
   if (is_error (response))
     {
	handle_error_response (gl, tbl);
	return -1;
     }
   return 0;
}

/* Open connection, send command check for error and handle it via table
 * if error occurs.
 */
static int gl_send_command (GL_Type *gl, GL_Error_Type *tbl, char *fmt, ...)
{
   va_list ap;
   int ret;
   
   if (-1 == connect_to_gl_host (gl, NULL))
     return -1;
   
   va_start(ap, fmt);
   ret = sltcp_vfprintf (&gl->tcp, fmt, ap);
   va_end (ap);
   
   if ((ret == -1)
       || (EOF == sltcp_fflush (&gl->tcp)))
     {
	GL_Error = GL_ESERVER_WRITE;
	close_gl (gl);
	return -1;
     }
   
   if (tbl == NULL) return 0;

   return check_for_error_response (gl, tbl);
}
   


/*}}}*/

/*{{{ login functions */

GL_Error_Type Login_Error_Table [] = 
{
   {":busy",		5,	GL_ELOGIN_BUSY},
   {":unavailable",	12,	GL_ELOGIN_UNAVAILABLE},
   {":unregistered",	13,	GL_ELOGIN_UNREGISTERED},
   {":alreadyLogin",	13,	GL_ELOGIN_ALREADY_LOGGEDIN},
   {NULL, 0, 0}
};



static int login (GL_Type *gl)
{
   char response [GL_MAX_RESPONSE_LINE_LEN];
   char *value; 
   unsigned int value_len;

   gl->logged_in = 0;
   
   if (-1 == gl_send_command (gl, Login_Error_Table, "LOGIN %s\r\n", gl->pseudoname))
     return -1;
   
   /* parse response to get token, etc... */
   if (-1 == get_gl_response (gl, response, sizeof (response)))
     return -1;
   
   /* CLose server then parse response. */
   close_gl (gl);
   
   /* expecting 1 or more fields of: 
    * :token=SOMETHING
    * :version=SOMETHING
    * :rkeys="BLA BLA BLA"
    * :pkeys="BLA BLA"
    */

   /* We only require token. */
   if (-1 == parse_keyword_eqs_value (response, ":token", &value, &value_len))
     {
	GL_Error = GL_ERROR_PROPER_RESPONSE;
	return -1;
     }
   
   if (NULL == (gl->token = make_nstring (value, value_len)))
     return -1;
   
   gl->logged_in = 1;

   return 0;
}

static int validate_token (GL_Type *gl)
{   
   if ((gl->token == NULL) || (gl->logged_in == 0))
     return login (gl);
   
   return 0;
}

/*}}}*/
/*{{{ logout functions */

static void logout (GL_Type *gl)
{
   if (gl->logged_in == 0) return;
   
   (void) gl_send_command (gl, NULL, "LOGOUT %s\r\n", gl->token);
   close_gl (gl);
}

/*}}}*/
/*{{{ gl_open_server */

static int gl_open_server (char *host, int port, char *pname)
{
   GL_Type *gl;
   
   if (host == NULL) return -1;
   if (port < 0) return -1;
   if (pname == NULL) return -1;
   
   gl = &GL_Server;
   memset ((char *) gl, 0, sizeof (GL_Type));
   
   if (NULL == (gl->pseudoname = make_string (pname)))
     return -1;
   
   strncpy (gl->hostname, host, GL_MAX_HOSTNAME_LEN);
   gl->hostname[GL_MAX_HOSTNAME_LEN] = 0;
   
   gl->port = port;
   
   if (-1 == login (gl))
     {
	free (gl->pseudoname);
	gl->pseudoname = NULL;
	
	return -1;
     }
   return 0;
}

/*}}}*/


static int gl_open_predictions (char *group)
{
   GL_Type *gl = &GL_Server;
   
   if (-1 == validate_token (gl))
     return -1;
       
   if (-1 == start_command (gl, "GetPredictions %s %s", gl->token, group))
     return -1;
   return 0;
}

static int terminate_command (GL_Type *gl, GL_Error_Type *tbl)
{
   if (-1 == send_gl_line (gl, ".", 1))
     return -1;
   
   return check_for_error_response (gl, tbl);
}

static int gl_want_prediction (char *msgid)
{
   GL_Type *gl;
   
   gl = &GL_Server;
   return send_gl_line (gl, msgid, 0);
}

static GL_Error_Type Predictions_Error_Table [] = 
{
   {":invalidToken",	13,	GL_ERROR_TOKEN},
   {":invalidGroup",	13,	GL_ERROR_NEWSGROUP},
   {NULL, 0, 0}
};

static int gl_close_predictions (void (*f) (GL_Prediction_Type *))
{  
   char buf [GL_MAX_RESPONSE_LINE_LEN];
   GL_Prediction_Type st;
   GL_Type *gl;

   gl = &GL_Server;
   
   if (-1 == terminate_command (gl, Predictions_Error_Table))
     {
	return -1;
     }
   
   
   while (-1 != get_gl_response (gl, buf, sizeof (buf)))
     {
	char *value;
	unsigned int value_len;
	int ok;
	
	ok = 0;
	if ((*buf == '.') && (buf[1] == 0))
	  {
	     close_gl (gl);
	     return 0;
	  }
	
	st.pred = -1.0;
	st.conflow = -1.0;
	st.confhigh = -1.0;

	if (0 == parse_keyword_eqs_value (buf, ":nopred", &value, &value_len))
	  continue;
	
	if ((0 == parse_keyword_eqs_value (buf, ":pred", &value, &value_len))
	    && (1 == sscanf (value, "%f", &st.pred)))
	  ok++;
	
	if ((0 == parse_keyword_eqs_value (buf, ":conflow", &value, &value_len))
	    && (1 == sscanf (value, "%f", &st.conflow)))
	  ok++;
	
	if ((0 == parse_keyword_eqs_value (buf, ":confhigh", &value, &value_len))
	    && (1 == sscanf (value, "%f", &st.confhigh)))
	  ok++;
	
	if (ok && (st.pred > 0.0)) 
	  {
	     char *b;
	     /* Now get message id.  It is first thing on line. */
	     st.msgid = skip_whitespace (buf);
	     b = skip_nonwhitespace (st.msgid);
	     *b = 0;
	     
	     if (*st.msgid == '<') (*f) (&st);
	  }
     }
   return -1;
}

static int gl_open_ratings (char *group)
{
   GL_Type *gl = &GL_Server;
   
   if (-1 == validate_token (gl))
     return -1;
   
   if (-1 == start_command (gl, "PutRatings %s %s", gl->token, group))
     return -1;
   return 0;
}

static int gl_put_rating (GL_Rating_Type *rt)
{
   char buf [GL_MAX_RESPONSE_LINE_LEN];
   GL_Type *gl;
   
   gl = &GL_Server;
   
   if (rt->rating <= 0) return 0;
   sprintf (buf, "%s :rating=%4.2f :sawHeader=1", rt->msgid, (float) rt->rating);
   if (rt->saw_article)
     strcat (buf + strlen (buf), " :sawArticle=1");
   
   return send_gl_line (gl, buf, 0);
}

static int gl_close_ratings (void)
{
   GL_Type *gl;
   
   gl = &GL_Server;
   
   if (-1 == terminate_command (gl, Predictions_Error_Table))
     return -1;
   
   close_gl (gl);
   return 0;
}

static void gl_close_server (void)
{
   GL_Type *gl;
   
   gl = &GL_Server;

   if (gl->logged_in == 0) 
     return;
   
   (void) logout (gl);
   
   if (gl->pseudoname != NULL)
     free (gl->pseudoname);
   
   if (gl->token != NULL)
     free (gl->token);
   
   memset ((char *)gl, 0, sizeof (GL_Type));
}

/*}}}*/

int Slrn_Use_Group_Lens = 0;

static void do_error (void)
{
   slrn_error ("Failed: %s", gl_get_error ());
}

char *Slrn_GroupLens_Host;
int Slrn_GroupLens_Port;
char *Slrn_GroupLens_Pseudoname;

typedef struct GL_Newsgroup_List_Type
{
   char *group;
   struct GL_Newsgroup_List_Type *next;
}
GL_Newsgroup_List_Type;

static GL_Newsgroup_List_Type *Newsgroup_List = NULL;

int slrn_grouplens_add_group (char *group)
{
   GL_Newsgroup_List_Type *g;
   
   if (group == NULL) return -1;
   g = (GL_Newsgroup_List_Type *) malloc (sizeof (GL_Newsgroup_List_Type));
   if (g == NULL)
     return -1;
   
   g->group = slrn_make_startup_string (group);
   g->next = Newsgroup_List;
   Newsgroup_List = g;
   
   return 0;
}

static int is_group_valid (char *name)
{
   GL_Newsgroup_List_Type *g;
   
   g = Newsgroup_List;
   while (g != NULL)
     {
	if (0 == slrn_case_strcmp ((unsigned char *)name, (unsigned char *)g->group))
	  return 1;
	
	g = g->next;
     }
   return 0;
}

static int read_grplens_file (void)
{
   char file [256];
   char *name = ".grplens";
   char line [1024];
   FILE *fp;
   
   fp = slrn_open_home_file (name, "r", file, 0);
   if (fp == NULL) return -1;

   slrn_message ("Reading %s", file);
   
   while (NULL != fgets (line, sizeof (line), fp))
     {
	char *b;
	
	b = slrn_skip_whitespace (line);
	if ((*b == 0) || (*b == '#') || (*b == '%'))
	  continue;
	
	(void) slrn_trim_string (b);
	
	if (!slrn_case_strncmp ((unsigned char *)b, (unsigned char *) "BBBHOST", 7))
	  {
	     b = slrn_skip_whitespace (b + 7);
	     if (*b == 0) continue;
	     if (Slrn_GroupLens_Host != NULL)
	       SLFREE (Slrn_GroupLens_Host);
	     Slrn_GroupLens_Host = slrn_make_startup_string (b);
	     continue;
	  }
	
	if (!slrn_case_strncmp ((unsigned char *)b, (unsigned char *)"PSEUDONYM", 9))
	  {
	     b = slrn_skip_whitespace (b + 9);
	     if (*b == 0) continue;
	     if (Slrn_GroupLens_Pseudoname != NULL)
	       SLFREE (Slrn_GroupLens_Pseudoname);
	     Slrn_GroupLens_Pseudoname = slrn_make_startup_string (b);
	     continue;
	  }
	
	if (!slrn_case_strncmp ((unsigned char *) b, (unsigned char *)"BBBPORT", 7))
	  {
	     b = slrn_skip_whitespace (b + 7);
	     if (*b == 0) continue;
	     if (1 != sscanf (b, "%d", &Slrn_GroupLens_Port))
	       Slrn_GroupLens_Port = -1;
	     
	     continue;
	  }
	
	if (!slrn_case_strncmp ((unsigned char *)b, (unsigned char *)"DISPLAYTYPE", 11))
	  continue;
	
	/* Anything else is a newsgroup */
	
	(void) slrn_grouplens_add_group (b);
     }
   
   fclose (fp);
   return 0;
}

int slrn_init_grouplens (void)
{
   Slrn_Use_Group_Lens = 0;

   (void) read_grplens_file ();
   
   if ((Slrn_GroupLens_Host == NULL)
       || (Slrn_GroupLens_Port <= 0)
       || (Slrn_GroupLens_Pseudoname == NULL))
     {
	return -1;
     }
   
   slrn_message ("Connecting to GroupLens host %s, port %d...", Slrn_GroupLens_Host, Slrn_GroupLens_Port);
   
   if (-1 == gl_open_server (Slrn_GroupLens_Host, Slrn_GroupLens_Port, Slrn_GroupLens_Pseudoname))
     {
	slrn_error ("GroupLens login failure: %s", gl_get_error ());
	return -1;
     }
   
   Slrn_Use_Group_Lens = 1;
   slrn_message ("GroupLens support initialized.");
   return 0;
}

void slrn_close_grouplens (void)
{
   if (Slrn_Use_Group_Lens == 0) return;
   slrn_message ("Logging out of GroupLens server...");
   Slrn_Message_Present = 0;
   Slrn_Use_Group_Lens = 0;
   gl_close_server ();
}

static int Did_Rating = 0;
static int Prediction_Count;

static void prediction_callback (GL_Prediction_Type *s)
{
   Slrn_Header_Type *h;
   int i;
   
   h = slrn_find_header_with_msgid (s->msgid);

   if (h == NULL)
     return;			       /* not supposed to happen */
   
   i = (int) (s->pred + 0.5);

   if (i < 0) i = 0;
   
   h->gl_pred = i;
   Prediction_Count++;
}

int slrn_get_grouplens_scores (void)
{
   Slrn_Header_Type *h;
   int ret;
   
   if ((Slrn_Use_Group_Lens == 0) 
       || (Slrn_First_Header == NULL))
     return -1;

   if (0 == is_group_valid (Slrn_Current_Group_Name))
     return -1;
   
   Prediction_Count = 0;
   
   slrn_message ("Getting GroupLens predictions...");
   slrn_smg_refresh ();
   Slrn_Message_Present = 0;
 
   if (-1 == gl_open_predictions (Slrn_Current_Group_Name))
     {
	do_error ();
	return -1;
     }
   
   h = Slrn_First_Header;
   
   ret = 0;
   while (h != NULL)
     {
	if (ret == 0) ret = gl_want_prediction (h->msgid);
	h = h->real_next;
     }
   
   if (ret == -1) 
     {
	do_error ();
	return -1;
     }
   
   if (-1 == gl_close_predictions (prediction_callback))
     {
	do_error ();
	return -1;
     }
   
   return Prediction_Count;
}

     
int slrn_put_grouplens_scores (void)
{  
   Slrn_Header_Type *h;
   
   if (Slrn_Use_Group_Lens == 0) return 0;
   if (Did_Rating == 0) return 0;
   Did_Rating = 0;
   
   h = Slrn_First_Header;
   if (h == NULL) return 0;
   
   slrn_message ("Sending GroupLens ratings...");
   slrn_smg_refresh ();
   Slrn_Message_Present = 0;
   
   if (-1 == gl_open_ratings (Slrn_Current_Group_Name))
     {
	do_error ();
	return -1;
     }
   
   while (h != NULL)
     {
	GL_Rating_Type r;
	
	if (h->gl_rating > 0)
	  {
	     r.msgid = h->msgid;
	     if (h->gl_rating >= 10)
	       {
		  r.saw_article = 1;
		  r.rating = h->gl_rating / 10;
	       }
	     else
	       {
		  r.saw_article = 0;
		  r.rating = h->gl_rating;
	       }
	     
	     if (-1 == gl_put_rating (&r))
	       {
		  do_error ();
		  return -1;
	       }
	  }
	h = h->real_next;
     }
   
   if (-1 == gl_close_ratings ())
     {
	do_error ();
	return -1;
     }
   
   return 0;
}

void slrn_group_lens_rate_article (Slrn_Header_Type *h, int score, int saw_article)
{
   if (saw_article) score = score * 10;
   h->gl_rating = score;
   Did_Rating++;
}


		
#endif				       /* SLRN_HAS_GROUPLENS */

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