From 7dbafe3f0fa465949ef66d800a8cbd0b191c9519 Mon Sep 17 00:00:00 2001 From: erdgeist <> Date: Mon, 27 Feb 2012 00:06:17 +0000 Subject: Complete rewrite of user handling. HEADS UP\! --- vchat-user.c | 660 +++++++++++++++++++++++++++-------------------------------- 1 file changed, 299 insertions(+), 361 deletions(-) (limited to 'vchat-user.c') diff --git a/vchat-user.c b/vchat-user.c index 4b44080..cd0df14 100755 --- a/vchat-user.c +++ b/vchat-user.c @@ -1,426 +1,364 @@ /* - * vchat-client - alpha version - * vchat-user.c - functions working with the userlist - * - * Copyright (C) 2001 Andreas Kotes - * - * This program is free software. It can be redistributed and/or modified, - * provided that this copyright notice is kept intact. This program is - * distributed in the hope that it will be useful, but without any warranty; - * without even the implied warranty of merchantability or fitness for a - * particular purpose. In no event shall the copyright holder be liable for - * any direct, indirect, incidental or special damages arising in any way out - * of the use of this software. - * - */ - -/* general includes */ + * vchat-client + +*/ + +#include #include +#include #include -#include -#include +#include #include -#include #include -#include "vchat.h" -struct user -{ - char *nick; /* nick of user */ - int chan; /* channel user is on */ - int chan_valid; /* are we sure he is? */ - int client_pv; /* client protocol version */ - int messaged; /* did we message with this user? */ - struct user *next;/* next user in linked list */ -}; +#include "vchat.h" +#include "vchat-user.h" /* version of this module */ char *vchat_us_version = "$Id$"; -/* externally used variables */ -/* current nick */ -char *nick = NULL; -/* current channel */ -int chan = 0; -/* userlist */ -user *nicks = NULL; - -/* add user to userlist */ -void -ul_add (char *name, int ignored) -{ - user *tmp = NULL; - - /* no list? create one */ - if (!nicks) - { - nicks = malloc (sizeof (user)); - memset(nicks,0,sizeof(user)); - nicks->nick = strdup(name); - nicks->chan = 0; /* users default in channel 0 */ - nicks->chan_valid = 0; - nicks->next = NULL; - } - else - { - /* travel list until end */ - tmp = nicks; - while (tmp) - { - /* it is this user? return */ - if (!strcmp (name, tmp->nick)) - return; - /* is there a next user? */ - if (tmp->next) - tmp = tmp->next; - else - { - /* create one */ - tmp->next = malloc (sizeof (user)); - tmp = tmp->next; - memset(tmp,0,sizeof(user)); - tmp->nick = strdup(name); - tmp->chan = 0; - tmp->chan_valid = 0; - tmp->next = NULL; - tmp = NULL; - } - } - } -#ifndef OLDREADLINE - rl_last_func = NULL; -#endif -} - -/* delete user from userlist */ -void -ul_del (char *name, int ignored) +typedef struct { - user *tmp = NULL, *ltmp = NULL; - - /* is it this client? return */ - if (nick && !strcmp (nick, name)) - return; - /* no list? return */ - if (!nicks) - return; - /* the user on top of list? */ - if (!strcmp (name, nicks->nick)) - { - /* remove user and copy next in list */ - tmp = nicks->next; - free (nicks); - nicks = tmp; - return; - } - /* travel through list, skip first entry */ - ltmp = nicks; - tmp = nicks->next; - while (tmp) - { - /* is it this user? */ - if (!strcmp (name, tmp->nick)) - { - /* hook next to last, discard this */ - ltmp->next = tmp->next; - free (tmp); - return; - } - /* advance in list */ - ltmp = tmp; - tmp = tmp->next; - } -#ifndef OLDREADLINE - rl_last_func = NULL; -#endif + char *nick; + enum { UL_NONE = 0x00, UL_ME = 0x01, UL_IN_MY_CHAN = 0x02, UL_NOT_IN_LIST = 0x04 } flags; + uint64_t last_public; + uint64_t last_private; +} user; +static user *g_users; //< all users, incl self +static size_t g_users_count; //< number of users in list +static char *g_nick; //< own nick +static int g_channel; //< own channel +int ul_case_first = 0; + +static int ul_nick_lookup( const char *nick, int *exact_match ) { + int i; + + *exact_match = 1; + for( i=0; inick) || !strcmp(tmpstr, tmp->nick)) - { - return tmp; - } - /* advance in list */ - tmp = tmp->next; +/* own nick and channel setters/getters */ +void own_nick_set( char *nick ) { + if( nick ) { + int base; + if( g_nick ) + base = ul_rename( g_nick, nick ); + else + base = ul_add( nick, 0 ); + if( base >= 0 ) { + g_users[base].flags |= UL_ME; + g_nick = g_users[base].nick; } - return NULL; -} + } else + ul_del( g_nick ); -char * -ul_matchuser( char *regex) { - user *tmp = nicks; - char *dest = tmpstr; - regex_t preg; + setstroption(CF_NICK, nick); +} - *dest = 0; - if( !regcomp( &preg, regex, REG_ICASE | REG_EXTENDED | REG_NEWLINE)) { - while( tmp ) { - /* does the username match? */ - if( !regexec( &preg, tmp->nick, 0, NULL, 0)) /* append username to list */ - dest += snprintf ( dest, 256, " %s", tmp->nick); - tmp = tmp->next; - } +void own_channel_set( int channel ) { + if( channel != g_channel ) { + /* Remove all users from my chan, will be re-set on join message */ + int i; + for( i=0; inext && ( tmp->next != who ) ) - tmp = tmp->next; +int own_nick_check( char *nick ) { + if( !g_nick ) return -1; + return !strncasecmp(g_nick,nick,strlen(g_nick) ); +} - if( tmp->next == who ) { - tmp->next = tmp->next->next; - who->next = nicks; - nicks = who; - } -#ifndef OLDREADLINE - rl_last_func = NULL; -#endif +int own_channel_get( ) { + return g_channel; } -void -ul_msgto (char *name) { - user *tmp = ul_finduser(name); +/* Add/remove/rename */ +int ul_add(char *name, int in_my_chan_flag ) { + + /* Test if user is already known */ + int exact_match, base = ul_nick_lookup( name, &exact_match ); + if( !exact_match ) { + /* Make space for new user */ + user * new_users = realloc( g_users, sizeof( user ) * ( 1 + g_users_count ) ); + if( !new_users ) return -1; + + /* Copy the tail */ + g_users = new_users; + memmove( g_users + base + 1, g_users + base, ( g_users_count - base ) * sizeof( user ) ); + g_users[base].nick = strdup( name ); + g_users[base].flags = UL_NONE; + g_users[base].last_public = 0; + g_users[base].last_private = 0; + + g_users_count++; + } - if (tmp) { - tmp->messaged |= 1; - ul_usertofront( tmp ); + g_users[base].flags &= ~UL_NOT_IN_LIST; + switch( in_my_chan_flag ) { + case 1: g_users[base].flags |= UL_IN_MY_CHAN; break; + case 0: g_users[base].flags &= ~UL_IN_MY_CHAN; break; + case -1: default: break; } + + return base; } -void -ul_msgfrom (char *name) { - user *tmp = ul_finduser(name); +int ul_del(char *name) { + /* Test if user is already known */ + int exact_match, base = ul_nick_lookup( name, &exact_match ); + if( !exact_match ) return -1; - if (tmp) { - tmp->messaged |= 2; - ul_usertofront( tmp ); - } -} + /* Release the name buffer */ + free( g_users[base].nick ); + if( g_users[base].flags & UL_ME ) g_nick = 0; -/* set channel of user */ -void -ul_moveuser (char *name, int channel) { - user *tmp = ul_finduser(name); + /* Copy the tail */ + memmove( g_users + base, g_users + base + 1, ( g_users_count - base - 1 ) * sizeof( user ) ); - if (tmp) { - /* store channel information and mark it valid */ - tmp->chan = channel; - tmp->chan_valid = 1; - } -#ifndef OLDREADLINE - rl_last_func = NULL; -#endif + /* Shrink user list, realloc to a smaller size never fails */ + g_users = realloc( g_users, sizeof( user ) * --g_users_count ); + return 0; } -/* let user leave a channel */ -void -ul_leave (char *name, int channel) -{ - user *tmp = ul_finduser(name); - /* is it this client? handle and return */ - if (nick && !strcmp (nick, name)) - { - ownleave (channel); - return; - } +int ul_rename(char *oldname, char *newname) { + /* Ensure user */ + int base = ul_add( oldname, -1 ); + if( base >= 0 ) { + free( g_users[base].nick ); + g_users[base].nick = strdup( newname ); + if( g_users[base].flags & UL_ME ) + g_nick = g_users[base].nick; + if( g_users[base].flags & UL_IN_MY_CHAN ) + ul_public_action(newname); + } + return base; +} - if (tmp) - { - /* mark channel information invalid */ - tmp->chan_valid = 0; - return; - } -#ifndef OLDREADLINE - rl_last_func = NULL; -#endif +void ul_clear() { + int i; + for( i=0; inick); - tmp->nick = strdup (newnick); - return; - } -#ifndef OLDREADLINE - rl_last_func = NULL; -#endif +void ul_rebuild_list( ) { + int i; + for( i=0; inext; - free (tmp->nick); - free (tmp); - /* advance */ - tmp = tmp2; +void ul_clean() { + int i; + for( i=0; inick, text, len)); +/* Seting state */ +void ul_leave_chan(char *name) { + /* Ensure user and kick him off the channel */ + ul_add(name, 0); } -int ulnc_ncasenick(user *tmp, const char *text, int len, int value) { - return (!strncasecmp(tmp->nick, text, len)); +void ul_enter_chan(char *name) { + /* Ensure user and put him on the channel */ + int base = ul_add(name, 1); + if( base >= 0 ) + ul_public_action(name); + + /* Reflect in UI */ + if( own_nick_check( name ) ) + ownjoin( g_channel ); } -char * -ulnc_complete (const char *text, int state, int value, int (*checkfn)(user *,const char *,int,int)) { - static int len; - static user *tmp; - char *name; - - /* first round? reset pointers! */ - if (!state) - { - tmp = nicks; - len = strlen (text); - } +void ul_private_action(char *name) { + /* Ensure user and keep channel state */ + int base = ul_add(name, -1); + if( base >= 0 ) + g_users[base].last_private = ul_now(); +} - /* walk list .. */ - while (tmp) - { - /* we found a match? */ - if (checkfn(tmp,text,len,value)) - { - /* copy nick, advance pointer for next call, return nick */ - name = tmp->nick; - tmp = tmp->next; - return name; - } - else - { - tmp = tmp->next; - } - } - return NULL; +void ul_public_action(char *name) { + /* Ensure user and put him on the channel */ + int base = ul_add(name, 1); + if( base >= 0 ) + g_users[base].last_public = ul_now(); } -/* nick completion functions for readline in vchat-ui.c */ -char * -ul_nickcomp (const char *text, int state) -{ - int ncasemode = 1; - char *name = NULL; - if (!state) ncasemode = 0; - if (!ncasemode) { - name = ulnc_complete(text,state,0,ulnc_casenick); - if (!state && !name) ncasemode = 1; +/* Finding users ul_finduser? */ +char * ul_match_user(char *regex) { + char *dest = tmpstr; + int i; + regex_t preg; + + *dest = 0; + if( !regcomp( &preg, regex, REG_ICASE | REG_EXTENDED | REG_NEWLINE)) { + + /* does the username match? */ + /* XXX overflow for too many matches */ + for( i=0; inick, text, len) && (tmp->chan_valid) && (tmp->chan == value)); +static int ul_compare_private( const void *a, const void *b ) { + const user *_a = (const user *)a, *_b = (const user *)b; + if( _a->last_private > _b->last_private ) return -1; + return 1; } -int ulnc_ncasenickc(user *tmp, const char *text, int len, int value) { - return (!strncasecmp(tmp->nick, text, len) && (tmp->chan_valid) && (tmp->chan == value)); -} +static int ul_compare_begin_of_line_ncase( const void *a, const void *b ) { + const user *_a = (const user *)a, *_b = (const user *)b; + size_t tmpstr_len; + int a_i, b_i; -/* nick completion for channel, used by vchat-ui.c */ -char * -ul_cnickcomp (const char *text, int state) -{ - int ncasemode = 1; - static char *name = NULL; + /* First ensure that users in current channel win */ + if( !(_a->flags & UL_IN_MY_CHAN ) ) return 1; + if( !(_b->flags & UL_IN_MY_CHAN ) ) return -1; - if (!state) ncasemode = 0; - if (!ncasemode) { - name = ulnc_complete(text,state,chan,ulnc_casenickc); - if (!state && !name) ncasemode = 1; - } - if (ncasemode) - name = ulnc_complete(text,state,chan,ulnc_ncasenickc); - if (name) { - snprintf(tmpstr,TMPSTRSIZE,"%s:",name); - return strdup(tmpstr); - } else - return NULL; + tmpstr_len = strlen( tmpstr ); + a_i = strncasecmp( _a->nick, tmpstr, tmpstr_len ); + b_i = strncasecmp( _b->nick, tmpstr, tmpstr_len ); + + if( a_i && b_i ) return 0; // Both nicks dont match + if( !a_i && b_i ) return -1; // a matches insensitive, b doesnt + if( a_i && !b_i ) return 1; // b matches insensitive, a doesnt + + /* From here both nicks match the prefix, ensure that own_nick + always appears last */ + if( _a->flags & UL_ME ) return 1; + if( _b->flags & UL_ME ) return -1; + + /* Now the user with the most recent public activity wins */ + if( _a->last_public > _b->last_public ) return -1; + + return 1; } -int ulnc_casenickm(user *tmp, const char *text, int len, int value) { - return (!strncmp(tmp->nick, text, len) && (tmp->messaged)); +static int ul_compare_begin_of_line_case( const void *a, const void *b ) { + const user *_a = (const user *)a, *_b = (const user *)b; + size_t tmpstr_len; + int a_i, b_i, a_s, b_s; + + /* First ensure that users in current channel win */ + if( !(_a->flags & UL_IN_MY_CHAN ) ) return 1; + if( !(_b->flags & UL_IN_MY_CHAN ) ) return -1; + + tmpstr_len = strlen( tmpstr ); + a_i = strncasecmp( _a->nick, tmpstr, tmpstr_len ); + a_s = strncmp ( _a->nick, tmpstr, tmpstr_len ); + b_i = strncasecmp( _b->nick, tmpstr, tmpstr_len ); + b_s = strncmp ( _b->nick, tmpstr, tmpstr_len ); + + if( a_i && b_i ) return 0; // Both nicks dont match at all + if( !a_i && b_i ) return -1; // a matches insensitive, b doesnt + if( a_i && !b_i ) return 1; // b matches insensitive, a doesnt + + if( !a_s && b_s ) return -1; // a matches sensitive, b doesnt + if( a_s && !b_s ) return 1; // b matches sensitive, a doesnt + + /* From now we know that both match with same quality, ensure + that own nick always appears last */ + if( _a->flags & UL_ME ) return 1; + if( _b->flags & UL_ME ) return -1; + + /* Now the user with the most recent public activity wins */ + if( _a->last_public > _b->last_public ) return -1; + + return 1; } -int ulnc_ncasenickm(user *tmp, const char *text, int len, int value) { - return (!strncasecmp(tmp->nick, text, len) && (tmp->messaged)); +static int ul_compare_middle( const void *a, const void *b ) { + const user *_a = (const user *)a, *_b = (const user *)b; + return strcasecmp( _b->nick, _a->nick ); } -/* nick completion for channel, used by vchat-ui.c */ -char * -ul_mnickcomp (const char *text, int state) -{ - int ncasemode = 1; - static char *name = NULL; +/* Nick completion function for readline */ +char **ul_complete_user(char *text, int start, int end ) { + char **result = 0; + int i, result_count = 0; + + /* Never want readline to complete filenames */ + rl_attempted_completion_over = 1; + + /* Prepare return array ... of max g_users_count (char*) + Plus least common prefix in [0] and null terminator + */ + result = malloc( sizeof(char*) * ( 2 + g_users_count ) ); + if( !result ) return 0; + + if( start == 0 && end == 0 ) { + /* Completion on begin of line yields list of everyone we + were in private conversation, sorted by time of last .m */ + qsort( g_users, g_users_count, sizeof(user), ul_compare_private ); + for( i=0; i 0 ) { + /* Completion on begin of line with some chars already typed yields + a list of everyone in channel, matching prefix, sorted by last + public activity */ + snprintf( tmpstr, end + 1, "%s", text ); + if( ul_case_first ) + qsort( g_users, g_users_count, sizeof(user), ul_compare_begin_of_line_case ); + else + qsort( g_users, g_users_count, sizeof(user), ul_compare_begin_of_line_ncase ); - if (!state) ncasemode = 0; - if (!ncasemode) { - name = ulnc_complete(text,state,chan,ulnc_casenickm); - if (!state && !name) ncasemode = 1; - } - if (ncasemode) - name = ulnc_complete(text,state,chan,ulnc_ncasenickm); - if (name) { - snprintf(tmpstr,TMPSTRSIZE,".m %s",name); - return strdup(tmpstr); + for( i=0; i + and thus should complete all users, sorted alphabetically without + preferences. */ + snprintf( tmpstr, end - start + 1, "%s", text ); + qsort( g_users, g_users_count, sizeof(user), ul_compare_middle ); + for( i=0; i