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.