/* * sbsessions.c * * SB session (=>IM window) management (but not the protocol nor UI) * * (c) 2002-2005 Thomas White * Part of TuxMessenger - GTK+-based MSN Messenger client * * This package is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; version 2 dated June, 1991. * * This package 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. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this package; if not, write to the Free Software * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA * 02111-1307, USA. * */ #ifdef HAVE_CONFIG_H #include #endif #include #include #include #include #include "debug.h" #include "messagewindow.h" #include "sbsessions.h" #include "sbprotocol.h" #include "msnprotocol.h" #include "contactlist.h" #include "msngenerics.h" #include "options.h" #include "msnp2p.h" #include "error.h" #include "routines.h" #include "fonttrans.h" SbSession *sessions = NULL; static SbSession *sbsessions_lastsession() { SbSession *session = sessions; while ( session ) { assert(session != NULL); if ( session->next == NULL ) { return session; } else { session = session->next; } } return NULL; /* If there were no sessions at all. */ } static void sbsessions_inituser(SbUser *new_user) { } static int sbsessions_ready_callback(SbSession *session) { if ( !session->ready ) { debug_print("SS %8p: Not ready after 60 seconds - deleting.\n", session); sbsessions_destroy(session); return FALSE; } session->ready_timeout = 0; return FALSE; /* Don't repeat */ } static SbSession *sbsessions_new(const char *username, SbSessionSource source) { SbSession *new_session; SbSession *last_session; new_session = malloc(sizeof(SbSession)); new_session->users = malloc(sizeof(SbUser)); assert(new_session->users != NULL); new_session->messagewindow = NULL; new_session->users->next = NULL; new_session->users->username = strdup(username); sbsessions_inituser(new_session->users); new_session->ready = FALSE; new_session->num_users = 1; new_session->rbufsize = 0; new_session->wbufsize = 0; new_session->rbuffer = NULL; new_session->wbuffer = NULL; new_session->roffset = 0; new_session->roffset = 0; new_session->am_typing = 0; new_session->am_typing_callback = 0; new_session->threeway_intent = NULL; new_session->multipackets = NULL; new_session->trid = 1; /* Reset in sbprotocol_connect */ new_session->cached = NULL; new_session->source = source; new_session->rcallback = 0; new_session->wcallback = 0; /* 60 seconds to get ready. */ new_session->ready_timeout = gtk_timeout_add(60000, (GtkFunction)sbsessions_ready_callback, new_session); /* Link it into the list. */ last_session = sbsessions_lastsession(); if ( last_session != NULL ) { assert(last_session->next == NULL); last_session->next = new_session; } else { sessions = new_session; } new_session->next = NULL; return new_session; } SbSession *sbsessions_create_local(const char *username) { SbSession *session = sbsessions_new(username, SESSION_SOURCE_LOCAL); session->neg_trid = msnprotocol_initiatesb(); debug_print("SS %8p: New local session for user %s.\n", session, username); return session; } /* Called when the user clicks on a name in the contact list. */ SbSession *sbsessions_create_remote(char *username, char *switchboardaddress, char *sessionid, char *authchallenge) { SbSession *session = sbsessions_new(username, SESSION_SOURCE_REMOTE); session->neg_trid = 0; sbprotocol_initiate_remote(session, switchboardaddress, sessionid, authchallenge); debug_print("SS %8p: New remote session for user %s.\n", session, username); return session; } /* Create a three-way session in "one go". */ SbSession *sbsessions_create_threeway(char *username1, char *username2) { SbSession *session = sbsessions_new(username1, SESSION_SOURCE_LOCAL); session->threeway_intent = strdup(username2); session->neg_trid = msnprotocol_initiatesb(); debug_print("SS %8p: New local session for user %s. Intending to invite %s too.\n", session, username1, username2); return session; } void sbsessions_plug(SbSession *session, MessageWindow *messagewindow) { session->messagewindow = messagewindow; } /* Unplug any SB sessions which are plugged into a given IM window */ void sbsessions_unplug(MessageWindow *messagewindow) { SbSession *session = sessions; while ( session != NULL ) { if ( session->messagewindow == messagewindow ) { session->messagewindow = NULL; } session = session->next; } } /* Find a headless session with this username. */ SbSession *sbsessions_find_headless(const char *username) { SbSession *session = sessions; while ( session != NULL ) { if ( (session->num_users == 1) && (strcmp(username, session->users->username) == 0) && (session->messagewindow == NULL) ) { return session; } session = session->next; } return NULL; } /* Find a session with just this user in, and noone about to randomly join (hopefully...) */ SbSession *sbsessions_find_single_safe(const char *username) { SbSession *session = sessions; while ( session != NULL ) { if ( (session->num_users == 1) && (strcmp(username, session->users->username) == 0) ) { if ( session->threeway_intent == NULL ) { return session; } } session = session->next; } return NULL; } /* SbUsers are unique to a particular SbSession. Find the parent SbSession given the SbUser. Could just keep a note of the parent record in the SbUser record, but sbsessions_stoptyping needs to know if the SbUser is still attached to (any) SbSession. */ SbSession *sbsessions_find_user(SbUser *target_user) { SbSession *session; session = sessions; while ( session != NULL ) { SbUser *user = session->users; while ( user != NULL ) { if ( user == target_user ) { return session; } user = user->next; } session = session->next; } debug_print("SS %8p: Couldn't find user in all sessions.\n", NULL); return NULL; } /* Return a session's record given "TrID". */ SbSession *sbsessions_find_trid(unsigned int trid) { SbSession *session; session = sessions; while ( session != NULL ) { assert(session != NULL); if ( session->neg_trid == trid ) { return session; } else { session = session->next; } } debug_print("SS %8p: Failed to find session negotiated with TrId %i\n", NULL, trid); return NULL; } SbUser *sbsessions_find_username(SbSession *session, const char *username) { SbUser *user; assert(session != NULL); assert(session->users != NULL); user = session->users; while ( user != NULL ) { assert(user != NULL); /* Case-insensitive here. Username may have different case depending on its source, since the servers seem to change usernames to lower case but clients might not in (e.g.) TypingUser controls. */ if ( strcasecmp(user->username, username) == 0 ) { return user; } else { user = user->next; } } return NULL; } /* Locate a user on a given SB session from their DP SHA1D field. */ SbUser *sbsessions_find_dpsha1d(SbSession *session, char *sha1d) { SbUser *user; assert(session != NULL); assert(session->users != NULL); user = session->users; while ( user != NULL ) { assert(user != NULL); if ( strcmp(user->dpsha1d, sha1d) == 0 ) { return user; } else { user = user->next; } } return NULL; } void sbsessions_destroy(SbSession *session) { SbSession *prev_session; SbUser *user; CachedMsg *cached; /* Unplug the session and close the socket. */ messagewindow_unplug(session); sbprotocol_close(session); /* Remove the session from the list. */ prev_session = sessions; if ( prev_session != session ) { while ( prev_session != NULL ) { assert(prev_session != NULL); if ( prev_session->next == session ) { break; } else { prev_session = prev_session->next; } } assert(prev_session->next == session); /* Link it out of the list. */ prev_session->next = session->next; } else { /* This session was the first on the list. */ assert(sessions == session); /* Can't fail... */ sessions = session->next; /* Which may be NULL if the list is now empty. */ } /* Drop all MSNP2P sessions in this SB session. */ msnp2p_abortall(session); /* Drop any cached messages, and inform the user. */ cached = session->cached; while ( cached != NULL ) { CachedMsg *next = cached->next; if ( !session->messagewindow ) { messagewindow_mitigate(session); } messagewindow_reportdropped(session->messagewindow, cached->message, cached->length); free(cached->message); free(cached); cached = next; messagewindow_unplug(session); /* This session doesn't exist, remember? */ } /* Remove all the users. */ user = session->users; while ( user != NULL ) { SbUser *next_user = user->next; free(user->username); free(user); user = next_user; } /* Wind up the low-level business. */ if ( session->rcallback != 0 ) { gdk_input_remove(session->rcallback); } session->rcallback = 0; if ( session->wcallback != 0 ) { gdk_input_remove(session->wcallback); } session->wcallback = 0; if ( session->ready_timeout != 0 ) { gtk_timeout_remove(session->ready_timeout); } session->ready_timeout = 0; if ( session->am_typing_callback != 0 ) { gtk_timeout_remove(session->am_typing_callback); session->am_typing = 0; session->am_typing_callback = 0; } if ( session->rbuffer != NULL ) { free(session->rbuffer); } if ( session->wbuffer != NULL ) { free(session->wbuffer); } free(session); } SbUser *sbsessions_lastuser(SbSession *session) { SbUser *user; user = session->users; while ( user != NULL ) { assert(user != NULL); if ( user->next == NULL ) { return user; } else { user = user->next; } } debug_print("SS %8p: Reached end of sbsessions_lastuser! This doesn't happen!\n", session); return NULL; } static SbUser *sbsessions_addnewuser(SbSession *session, char *username) { SbUser *new_user; SbUser *previous_user; new_user = malloc(sizeof(SbUser)); assert(new_user != NULL); new_user->next = NULL; new_user->username = strdup(username); sbsessions_inituser(new_user); previous_user = sbsessions_lastuser(session); assert(previous_user->next == NULL); previous_user->next = new_user; session->num_users++; return new_user; } /* Deal with any user joining a session (whether by JOI or IRO). */ void sbsessions_joined(SbSession *session, char *username, char *friendlyname) { SbUser *user; /* "friendlyname" is from the JOI message. This user might not be in any of the contact lists. Now would be a good time to check, and if not, create a temporary record for them. */ if ( contactlist_friendlyname(username) == NULL ) { debug_print("SS %8p: Creating temporary user record for '%s'/'%s'\n", session, username, friendlyname); contactlist_tldetails(CONTACT_SOURCE_RNG, username, friendlyname, ONLINE_NLN); } user = sbsessions_find_username(session, username); if ( user == NULL ) { /* User is new - probably got invited by someone else */ debug_print("SS %8p: Couldn't find user %s in SB record - adding them.\n", session, username); user = sbsessions_addnewuser(session, username); } if ( session->messagewindow != NULL ) { messagewindow_joined(session->messagewindow, username); } } /* Deal with any user leaving a session. */ void sbsessions_left(SbSession *session, char *username) { SbUser *user; user = sbsessions_find_username(session, username); if ( user == NULL ) { debug_print("SS %8p: Couldn't find leaving user %s in SB record!\n", session, username); return; } if ( session->num_users == 1 ) { sbprotocol_leavesession(session); } else { /* "Normal" situation. Remove user record, their DP and status bar. */ SbUser *find_user; session->num_users--; if ( session->messagewindow != NULL ) { messagewindow_removeuser(session->messagewindow, username); } /* Find the preceding user in the session and link this user out. */ find_user = session->users; if ( find_user == user ) { /* User was the first in the list. */ session->users = user->next; } else { while ( find_user != NULL ) { assert(find_user != NULL); if ( find_user->next == user ) { find_user->next = user->next; break; } else { find_user = find_user->next; } } } free(user->username); free(user); } } static int sbsessions_amtyping_callback(SbSession *session) { session->am_typing = 0; session->am_typing_callback = 0; return FALSE; /* Don't repeat */ } void sbsessions_am_typing(SbSession *session) { if ( !session->ready ) { debug_print("SS %8p: Not ready - not sending TypingUser.\n", session); return; } /* Calculate timeout then send TypingUser control. */ if ( session->am_typing == 0 ) { sbprotocol_sendtypingcontrol(session); session->am_typing = 1; session->am_typing_callback = gtk_timeout_add(5000, (GtkFunction)sbsessions_amtyping_callback, session); } } /* Check a session is ready for I/O. */ int sbsessions_sessionready(SbSession *session) { if ( session == NULL ) { return FALSE; } return session->ready; } /* Delete all sessions. */ void sbsessions_destroy_all() { SbSession *session; SbSession *next; debug_print("SB %8p: destroying all sessions.\n", NULL); session = sessions; while ( session != NULL ) { next = session->next; sbsessions_destroy(session); session = next; } }