Mercurial > pidgin.yaz
diff libpurple/protocols/msn/switchboard.c @ 15374:5fe8042783c1
Rename gtk/ and libgaim/ to pidgin/ and libpurple/
author | Sean Egan <seanegan@gmail.com> |
---|---|
date | Sat, 20 Jan 2007 02:32:10 +0000 |
parents | |
children | 32c366eeeb99 |
line wrap: on
line diff
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/libpurple/protocols/msn/switchboard.c Sat Jan 20 02:32:10 2007 +0000 @@ -0,0 +1,1264 @@ +/** + * @file switchboard.c MSN switchboard functions + * + * gaim + * + * Gaim is the legal property of its developers, whose names are too numerous + * to list here. Please refer to the COPYRIGHT file distributed with this + * source distribution. + * + * This program 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; either version 2 of the License, or + * (at your option) any later version. + * + * 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. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +#include "msn.h" +#include "prefs.h" +#include "switchboard.h" +#include "notification.h" +#include "msn-utils.h" + +#include "error.h" + +static MsnTable *cbs_table; + +static void msg_error_helper(MsnCmdProc *cmdproc, MsnMessage *msg, + MsnMsgErrorType error); + +/************************************************************************** + * Main + **************************************************************************/ + +MsnSwitchBoard * +msn_switchboard_new(MsnSession *session) +{ + MsnSwitchBoard *swboard; + MsnServConn *servconn; + + g_return_val_if_fail(session != NULL, NULL); + + swboard = g_new0(MsnSwitchBoard, 1); + + swboard->session = session; + swboard->servconn = servconn = msn_servconn_new(session, MSN_SERVCONN_SB); + swboard->cmdproc = servconn->cmdproc; + + swboard->msg_queue = g_queue_new(); + swboard->empty = TRUE; + + swboard->cmdproc->data = swboard; + swboard->cmdproc->cbs_table = cbs_table; + + session->switches = g_list_append(session->switches, swboard); + + return swboard; +} + +void +msn_switchboard_destroy(MsnSwitchBoard *swboard) +{ + MsnSession *session; + MsnMessage *msg; + GList *l; + +#ifdef MSN_DEBUG_SB + gaim_debug_info("msn", "switchboard_destroy: swboard(%p)\n", swboard); +#endif + + g_return_if_fail(swboard != NULL); + + if (swboard->destroying) + return; + + swboard->destroying = TRUE; + + /* If it linked us is because its looking for trouble */ + while (swboard->slplinks != NULL) + msn_slplink_destroy(swboard->slplinks->data); + + /* Destroy the message queue */ + while ((msg = g_queue_pop_head(swboard->msg_queue)) != NULL) + { + if (swboard->error != MSN_SB_ERROR_NONE) + { + /* The messages could not be sent due to a switchboard error */ + msg_error_helper(swboard->cmdproc, msg, + MSN_MSG_ERROR_SB); + } + msn_message_unref(msg); + } + + g_queue_free(swboard->msg_queue); + + /* msg_error_helper will both remove the msg from ack_list and + unref it, so we don't need to do either here */ + while ((l = swboard->ack_list) != NULL) + msg_error_helper(swboard->cmdproc, l->data, MSN_MSG_ERROR_SB); + + g_free(swboard->im_user); + g_free(swboard->auth_key); + g_free(swboard->session_id); + + for (l = swboard->users; l != NULL; l = l->next) + g_free(l->data); + + session = swboard->session; + session->switches = g_list_remove(session->switches, swboard); + +#if 0 + /* This should never happen or we are in trouble. */ + if (swboard->servconn != NULL) + msn_servconn_destroy(swboard->servconn); +#endif + + swboard->cmdproc->data = NULL; + + msn_servconn_set_disconnect_cb(swboard->servconn, NULL); + + msn_servconn_destroy(swboard->servconn); + + g_free(swboard); +} + +void +msn_switchboard_set_auth_key(MsnSwitchBoard *swboard, const char *key) +{ + g_return_if_fail(swboard != NULL); + g_return_if_fail(key != NULL); + + swboard->auth_key = g_strdup(key); +} + +const char * +msn_switchboard_get_auth_key(MsnSwitchBoard *swboard) +{ + g_return_val_if_fail(swboard != NULL, NULL); + + return swboard->auth_key; +} + +void +msn_switchboard_set_session_id(MsnSwitchBoard *swboard, const char *id) +{ + g_return_if_fail(swboard != NULL); + g_return_if_fail(id != NULL); + + if (swboard->session_id != NULL) + g_free(swboard->session_id); + + swboard->session_id = g_strdup(id); +} + +const char * +msn_switchboard_get_session_id(MsnSwitchBoard *swboard) +{ + g_return_val_if_fail(swboard != NULL, NULL); + + return swboard->session_id; +} + +void +msn_switchboard_set_invited(MsnSwitchBoard *swboard, gboolean invited) +{ + g_return_if_fail(swboard != NULL); + + swboard->invited = invited; +} + +gboolean +msn_switchboard_is_invited(MsnSwitchBoard *swboard) +{ + g_return_val_if_fail(swboard != NULL, FALSE); + + return swboard->invited; +} + +/************************************************************************** + * Utility + **************************************************************************/ + +static void +send_clientcaps(MsnSwitchBoard *swboard) +{ + MsnMessage *msg; + + msg = msn_message_new(MSN_MSG_CAPS); + msn_message_set_content_type(msg, "text/x-clientcaps"); + msn_message_set_flag(msg, 'U'); + msn_message_set_bin_data(msg, MSN_CLIENTINFO, strlen(MSN_CLIENTINFO)); + + msn_switchboard_send_msg(swboard, msg, TRUE); + + msn_message_destroy(msg); +} + +static void +msn_switchboard_add_user(MsnSwitchBoard *swboard, const char *user) +{ + MsnCmdProc *cmdproc; + GaimAccount *account; + + g_return_if_fail(swboard != NULL); + + cmdproc = swboard->cmdproc; + account = cmdproc->session->account; + + swboard->users = g_list_prepend(swboard->users, g_strdup(user)); + swboard->current_users++; + swboard->empty = FALSE; + +#ifdef MSN_DEBUG_CHAT + gaim_debug_info("msn", "user=[%s], total=%d\n", user, + swboard->current_users); +#endif + + if (!(swboard->flag & MSN_SB_FLAG_IM) && (swboard->conv != NULL)) + { + /* This is a helper switchboard. */ + gaim_debug_error("msn", "switchboard_add_user: conv != NULL\n"); + return; + } + + if ((swboard->conv != NULL) && + (gaim_conversation_get_type(swboard->conv) == GAIM_CONV_TYPE_CHAT)) + { + gaim_conv_chat_add_user(GAIM_CONV_CHAT(swboard->conv), user, NULL, + GAIM_CBFLAGS_NONE, TRUE); + } + else if (swboard->current_users > 1 || swboard->total_users > 1) + { + if (swboard->conv == NULL || + gaim_conversation_get_type(swboard->conv) != GAIM_CONV_TYPE_CHAT) + { + GList *l; + +#ifdef MSN_DEBUG_CHAT + gaim_debug_info("msn", "[chat] Switching to chat.\n"); +#endif + +#if 0 + /* this is bad - it causes msn_switchboard_close to be called on the + * switchboard we're in the middle of using :( */ + if (swboard->conv != NULL) + gaim_conversation_destroy(swboard->conv); +#endif + + swboard->chat_id = cmdproc->session->conv_seq++; + swboard->flag |= MSN_SB_FLAG_IM; + swboard->conv = serv_got_joined_chat(account->gc, + swboard->chat_id, + "MSN Chat"); + + for (l = swboard->users; l != NULL; l = l->next) + { + const char *tmp_user; + + tmp_user = l->data; + +#ifdef MSN_DEBUG_CHAT + gaim_debug_info("msn", "[chat] Adding [%s].\n", tmp_user); +#endif + + gaim_conv_chat_add_user(GAIM_CONV_CHAT(swboard->conv), + tmp_user, NULL, GAIM_CBFLAGS_NONE, TRUE); + } + +#ifdef MSN_DEBUG_CHAT + gaim_debug_info("msn", "[chat] We add ourselves.\n"); +#endif + + gaim_conv_chat_add_user(GAIM_CONV_CHAT(swboard->conv), + gaim_account_get_username(account), + NULL, GAIM_CBFLAGS_NONE, TRUE); + + g_free(swboard->im_user); + swboard->im_user = NULL; + } + } + else if (swboard->conv == NULL) + { + swboard->conv = gaim_find_conversation_with_account(GAIM_CONV_TYPE_IM, + user, account); + } + else + { + gaim_debug_warning("msn", "switchboard_add_user: This should not happen!\n"); + } +} + +static GaimConversation * +msn_switchboard_get_conv(MsnSwitchBoard *swboard) +{ + GaimAccount *account; + + g_return_val_if_fail(swboard != NULL, NULL); + + if (swboard->conv != NULL) + return swboard->conv; + + gaim_debug_error("msn", "Switchboard with unassigned conversation\n"); + + account = swboard->session->account; + + return (swboard->conv = gaim_conversation_new(GAIM_CONV_TYPE_IM, + account, swboard->im_user)); +} + +static void +msn_switchboard_report_user(MsnSwitchBoard *swboard, GaimMessageFlags flags, const char *msg) +{ + GaimConversation *conv; + + g_return_if_fail(swboard != NULL); + g_return_if_fail(msg != NULL); + + if ((conv = msn_switchboard_get_conv(swboard)) != NULL) + { + gaim_conversation_write(conv, NULL, msg, flags, time(NULL)); + } +} + +static void +swboard_error_helper(MsnSwitchBoard *swboard, int reason, const char *passport) +{ + g_return_if_fail(swboard != NULL); + + gaim_debug_warning("msg", "Error: Unable to call the user %s for reason %i\n", + passport ? passport : "(null)", reason); + + /* TODO: if current_users > 0, this is probably a chat and an invite failed, + * we should report that in the chat or something */ + if (swboard->current_users == 0) + { + swboard->error = reason; + msn_switchboard_close(swboard); + } +} + +static void +cal_error_helper(MsnTransaction *trans, int reason) +{ + MsnSwitchBoard *swboard; + const char *passport; + char **params; + + params = g_strsplit(trans->params, " ", 0); + + passport = params[0]; + + swboard = trans->data; + + gaim_debug_warning("msn", "cal_error_helper: command %s failed for reason %i\n",trans->command,reason); + + swboard_error_helper(swboard, reason, passport); + + g_strfreev(params); +} + +static void +msg_error_helper(MsnCmdProc *cmdproc, MsnMessage *msg, MsnMsgErrorType error) +{ + MsnSwitchBoard *swboard; + + g_return_if_fail(cmdproc != NULL); + g_return_if_fail(msg != NULL); + + if ((error != MSN_MSG_ERROR_SB) && (msg->nak_cb != NULL)) + msg->nak_cb(msg, msg->ack_data); + + swboard = cmdproc->data; + + /* This is not good, and should be fixed somewhere else. */ + g_return_if_fail(swboard != NULL); + + if (msg->type == MSN_MSG_TEXT) + { + const char *format, *str_reason; + char *body_str, *body_enc, *pre, *post; + +#if 0 + if (swboard->conv == NULL) + { + if (msg->ack_ref) + msn_message_unref(msg); + + return; + } +#endif + + if (error == MSN_MSG_ERROR_TIMEOUT) + { + str_reason = _("Message may have not been sent " + "because a timeout occurred:"); + } + else if (error == MSN_MSG_ERROR_SB) + { + switch (swboard->error) + { + case MSN_SB_ERROR_OFFLINE: + str_reason = _("Message could not be sent, " + "not allowed while invisible:"); + break; + case MSN_SB_ERROR_USER_OFFLINE: + str_reason = _("Message could not be sent " + "because the user is offline:"); + break; + case MSN_SB_ERROR_CONNECTION: + str_reason = _("Message could not be sent " + "because a connection error occurred:"); + break; + case MSN_SB_ERROR_TOO_FAST: + str_reason = _("Message could not be sent " + "because we are sending too quickly:"); + break; + default: + str_reason = _("Message could not be sent " + "because an error with " + "the switchboard occurred:"); + break; + } + } + else + { + str_reason = _("Message may have not been sent " + "because an unknown error occurred:"); + } + + body_str = msn_message_to_string(msg); + body_enc = g_markup_escape_text(body_str, -1); + g_free(body_str); + + format = msn_message_get_attr(msg, "X-MMS-IM-Format"); + msn_parse_format(format, &pre, &post); + body_str = g_strdup_printf("%s%s%s", pre ? pre : "", + body_enc ? body_enc : "", post ? post : ""); + g_free(body_enc); + g_free(pre); + g_free(post); + + msn_switchboard_report_user(swboard, GAIM_MESSAGE_ERROR, + str_reason); + msn_switchboard_report_user(swboard, GAIM_MESSAGE_RAW, + body_str); + + g_free(body_str); + } + + /* If a timeout occures we will want the msg around just in case we + * receive the ACK after the timeout. */ + if (msg->ack_ref && error != MSN_MSG_ERROR_TIMEOUT) + { + swboard->ack_list = g_list_remove(swboard->ack_list, msg); + msn_message_unref(msg); + } +} + +/************************************************************************** + * Message Stuff + **************************************************************************/ + +/** Called when a message times out. */ +static void +msg_timeout(MsnCmdProc *cmdproc, MsnTransaction *trans) +{ + MsnMessage *msg; + + msg = trans->data; + + msg_error_helper(cmdproc, msg, MSN_MSG_ERROR_TIMEOUT); +} + +/** Called when we receive an error of a message. */ +static void +msg_error(MsnCmdProc *cmdproc, MsnTransaction *trans, int error) +{ + msg_error_helper(cmdproc, trans->data, MSN_MSG_ERROR_UNKNOWN); +} + +#if 0 +/** Called when we receive an ack of a special message. */ +static void +msg_ack(MsnCmdProc *cmdproc, MsnCommand *cmd) +{ + MsnMessage *msg; + + msg = cmd->trans->data; + + if (msg->ack_cb != NULL) + msg->ack_cb(msg->ack_data); + + msn_message_unref(msg); +} + +/** Called when we receive a nak of a special message. */ +static void +msg_nak(MsnCmdProc *cmdproc, MsnCommand *cmd) +{ + MsnMessage *msg; + + msg = cmd->trans->data; + + msn_message_unref(msg); +} +#endif + +static void +release_msg(MsnSwitchBoard *swboard, MsnMessage *msg) +{ + MsnCmdProc *cmdproc; + MsnTransaction *trans; + char *payload; + gsize payload_len; + + g_return_if_fail(swboard != NULL); + g_return_if_fail(msg != NULL); + + cmdproc = swboard->cmdproc; + + payload = msn_message_gen_payload(msg, &payload_len); + +#ifdef MSN_DEBUG_SB + msn_message_show_readable(msg, "SB SEND", FALSE); +#endif + + trans = msn_transaction_new(cmdproc, "MSG", "%c %d", + msn_message_get_flag(msg), payload_len); + + /* Data for callbacks */ + msn_transaction_set_data(trans, msg); + + if (msg->type == MSN_MSG_TEXT) + { + msg->ack_ref = TRUE; + msn_message_ref(msg); + swboard->ack_list = g_list_append(swboard->ack_list, msg); + msn_transaction_set_timeout_cb(trans, msg_timeout); + } + else if (msg->type == MSN_MSG_SLP) + { + msg->ack_ref = TRUE; + msn_message_ref(msg); + swboard->ack_list = g_list_append(swboard->ack_list, msg); + msn_transaction_set_timeout_cb(trans, msg_timeout); +#if 0 + if (msg->ack_cb != NULL) + { + msn_transaction_add_cb(trans, "ACK", msg_ack); + msn_transaction_add_cb(trans, "NAK", msg_nak); + } +#endif + } + + trans->payload = payload; + trans->payload_len = payload_len; + + msg->trans = trans; + + msn_cmdproc_send_trans(cmdproc, trans); +} + +static void +queue_msg(MsnSwitchBoard *swboard, MsnMessage *msg) +{ + g_return_if_fail(swboard != NULL); + g_return_if_fail(msg != NULL); + + gaim_debug_info("msn", "Appending message to queue.\n"); + + g_queue_push_tail(swboard->msg_queue, msg); + + msn_message_ref(msg); +} + +static void +process_queue(MsnSwitchBoard *swboard) +{ + MsnMessage *msg; + + g_return_if_fail(swboard != NULL); + + gaim_debug_info("msn", "Processing queue\n"); + + while ((msg = g_queue_pop_head(swboard->msg_queue)) != NULL) + { + gaim_debug_info("msn", "Sending message\n"); + release_msg(swboard, msg); + msn_message_unref(msg); + } +} + +gboolean +msn_switchboard_can_send(MsnSwitchBoard *swboard) +{ + g_return_val_if_fail(swboard != NULL, FALSE); + + if (swboard->empty || !g_queue_is_empty(swboard->msg_queue)) + return FALSE; + + return TRUE; +} + +void +msn_switchboard_send_msg(MsnSwitchBoard *swboard, MsnMessage *msg, + gboolean queue) +{ + g_return_if_fail(swboard != NULL); + g_return_if_fail(msg != NULL); + + if (msn_switchboard_can_send(swboard)) + release_msg(swboard, msg); + else if (queue) + queue_msg(swboard, msg); +} + +/************************************************************************** + * Switchboard Commands + **************************************************************************/ + +static void +ans_cmd(MsnCmdProc *cmdproc, MsnCommand *cmd) +{ + MsnSwitchBoard *swboard; + + swboard = cmdproc->data; + swboard->ready = TRUE; +} + +static void +bye_cmd(MsnCmdProc *cmdproc, MsnCommand *cmd) +{ + MsnSwitchBoard *swboard; + const char *user; + + swboard = cmdproc->data; + user = cmd->params[0]; + + /* cmdproc->data is set to NULL when the switchboard is destroyed; + * we may get a bye shortly thereafter. */ + g_return_if_fail(swboard != NULL); + + if (!(swboard->flag & MSN_SB_FLAG_IM) && (swboard->conv != NULL)) + gaim_debug_error("msn_switchboard", "bye_cmd: helper bug\n"); + + if (swboard->conv == NULL) + { + /* This is a helper switchboard */ + msn_switchboard_destroy(swboard); + } + else if ((swboard->current_users > 1) || + (gaim_conversation_get_type(swboard->conv) == GAIM_CONV_TYPE_CHAT)) + { + /* This is a switchboard used for a chat */ + gaim_conv_chat_remove_user(GAIM_CONV_CHAT(swboard->conv), user, NULL); + swboard->current_users--; + if (swboard->current_users == 0) + msn_switchboard_destroy(swboard); + } + else + { + /* This is a switchboard used for a im session */ + msn_switchboard_destroy(swboard); + } +} + +static void +iro_cmd(MsnCmdProc *cmdproc, MsnCommand *cmd) +{ + GaimAccount *account; + GaimConnection *gc; + MsnSwitchBoard *swboard; + + account = cmdproc->session->account; + gc = account->gc; + swboard = cmdproc->data; + + swboard->total_users = atoi(cmd->params[2]); + + msn_switchboard_add_user(swboard, cmd->params[3]); +} + +static void +joi_cmd(MsnCmdProc *cmdproc, MsnCommand *cmd) +{ + MsnSession *session; + GaimAccount *account; + GaimConnection *gc; + MsnSwitchBoard *swboard; + const char *passport; + + passport = cmd->params[0]; + + session = cmdproc->session; + account = session->account; + gc = account->gc; + swboard = cmdproc->data; + + msn_switchboard_add_user(swboard, passport); + + process_queue(swboard); + + if (!session->http_method) + send_clientcaps(swboard); + + if (swboard->closed) + msn_switchboard_close(swboard); +} + +static void +msg_cmd_post(MsnCmdProc *cmdproc, MsnCommand *cmd, char *payload, size_t len) +{ + MsnMessage *msg; + + msg = msn_message_new_from_cmd(cmdproc->session, cmd); + + msn_message_parse_payload(msg, payload, len); +#ifdef MSN_DEBUG_SB + msn_message_show_readable(msg, "SB RECV", FALSE); +#endif + + if (msg->remote_user != NULL) + g_free (msg->remote_user); + + msg->remote_user = g_strdup(cmd->params[0]); + msn_cmdproc_process_msg(cmdproc, msg); + + msn_message_destroy(msg); +} + +static void +msg_cmd(MsnCmdProc *cmdproc, MsnCommand *cmd) +{ + cmdproc->servconn->payload_len = atoi(cmd->params[2]); + cmdproc->last_cmd->payload_cb = msg_cmd_post; +} + +static void +nak_cmd(MsnCmdProc *cmdproc, MsnCommand *cmd) +{ + MsnMessage *msg; + + msg = cmd->trans->data; + g_return_if_fail(msg != NULL); + + msg_error_helper(cmdproc, msg, MSN_MSG_ERROR_NAK); +} + +static void +ack_cmd(MsnCmdProc *cmdproc, MsnCommand *cmd) +{ + MsnSwitchBoard *swboard; + MsnMessage *msg; + + msg = cmd->trans->data; + + if (msg->ack_cb != NULL) + msg->ack_cb(msg, msg->ack_data); + + swboard = cmdproc->data; + swboard->ack_list = g_list_remove(swboard->ack_list, msg); + msn_message_unref(msg); +} + +static void +out_cmd(MsnCmdProc *cmdproc, MsnCommand *cmd) +{ + GaimConnection *gc; + MsnSwitchBoard *swboard; + + gc = cmdproc->session->account->gc; + swboard = cmdproc->data; + + if (swboard->current_users > 1) + serv_got_chat_left(gc, swboard->chat_id); + + msn_switchboard_disconnect(swboard); +} + +static void +usr_cmd(MsnCmdProc *cmdproc, MsnCommand *cmd) +{ + MsnSwitchBoard *swboard; + + swboard = cmdproc->data; + +#if 0 + GList *l; + + for (l = swboard->users; l != NULL; l = l->next) + { + const char *user; + user = l->data; + + msn_cmdproc_send(cmdproc, "CAL", "%s", user); + } +#endif + + swboard->ready = TRUE; + msn_cmdproc_process_queue(cmdproc); +} + +/************************************************************************** + * Message Handlers + **************************************************************************/ +static void +plain_msg(MsnCmdProc *cmdproc, MsnMessage *msg) +{ + GaimConnection *gc; + MsnSwitchBoard *swboard; + const char *body; + char *body_str; + char *body_enc; + char *body_final; + size_t body_len; + const char *passport; + const char *value; + + gc = cmdproc->session->account->gc; + swboard = cmdproc->data; + + body = msn_message_get_bin_data(msg, &body_len); + body_str = g_strndup(body, body_len); + body_enc = g_markup_escape_text(body_str, -1); + g_free(body_str); + + passport = msg->remote_user; + + if (!strcmp(passport, "messenger@microsoft.com") && + strstr(body, "immediate security update")) + { + return; + } + +#if 0 + if ((value = msn_message_get_attr(msg, "User-Agent")) != NULL) + { + gaim_debug_misc("msn", "User-Agent = '%s'\n", value); + } +#endif + + if ((value = msn_message_get_attr(msg, "X-MMS-IM-Format")) != NULL) + { + char *pre, *post; + + msn_parse_format(value, &pre, &post); + + body_final = g_strdup_printf("%s%s%s", pre ? pre : "", + body_enc ? body_enc : "", post ? post : ""); + + g_free(pre); + g_free(post); + g_free(body_enc); + } + else + { + body_final = body_enc; + } + + swboard->flag |= MSN_SB_FLAG_IM; + + if (swboard->current_users > 1 || + ((swboard->conv != NULL) && + gaim_conversation_get_type(swboard->conv) == GAIM_CONV_TYPE_CHAT)) + { + /* If current_users is always ok as it should then there is no need to + * check if this is a chat. */ + if (swboard->current_users <= 1) + gaim_debug_misc("msn", "plain_msg: current_users(%d)\n", + swboard->current_users); + + serv_got_chat_in(gc, swboard->chat_id, passport, 0, body_final, + time(NULL)); + if (swboard->conv == NULL) + { + swboard->conv = gaim_find_chat(gc, swboard->chat_id); + swboard->flag |= MSN_SB_FLAG_IM; + } + } + else + { + serv_got_im(gc, passport, body_final, 0, time(NULL)); + if (swboard->conv == NULL) + { + swboard->conv = gaim_find_conversation_with_account(GAIM_CONV_TYPE_IM, + passport, gaim_connection_get_account(gc)); + swboard->flag |= MSN_SB_FLAG_IM; + } + } + + g_free(body_final); +} + +static void +control_msg(MsnCmdProc *cmdproc, MsnMessage *msg) +{ + GaimConnection *gc; + MsnSwitchBoard *swboard; + char *passport; + + gc = cmdproc->session->account->gc; + swboard = cmdproc->data; + passport = msg->remote_user; + + if (swboard->current_users == 1 && + msn_message_get_attr(msg, "TypingUser") != NULL) + { + serv_got_typing(gc, passport, MSN_TYPING_RECV_TIMEOUT, + GAIM_TYPING); + } +} + +static void +clientcaps_msg(MsnCmdProc *cmdproc, MsnMessage *msg) +{ +#if 0 + MsnSession *session; + MsnSwitchBoard *swboard; + MsnUser *user; + GHashTable *clientcaps; + const char *value; + + char *passport = msg->sender; + + session = cmdproc->session; + swboard = cmdproc->servconn->swboard; + + clientcaps = msn_message_get_hashtable_from_body(msg); +#endif +} + +static void +nudge_msg(MsnCmdProc *cmdproc, MsnMessage *msg) +{ + MsnSwitchBoard *swboard; + char *username, *str; + GaimAccount *account; + GaimBuddy *buddy; + const char *user; + + swboard = cmdproc->data; + account = cmdproc->session->account; + user = msg->remote_user; + + if ((buddy = gaim_find_buddy(account, user)) != NULL) + username = g_markup_escape_text(gaim_buddy_get_alias(buddy), -1); + else + username = g_markup_escape_text(user, -1); + + str = g_strdup_printf(_("%s just sent you a Nudge!"), username); + g_free(username); + msn_switchboard_report_user(swboard, GAIM_MESSAGE_SYSTEM, str); + g_free(str); +} + +/************************************************************************** + * Connect stuff + **************************************************************************/ +static void +connect_cb(MsnServConn *servconn) +{ + MsnSwitchBoard *swboard; + MsnCmdProc *cmdproc; + GaimAccount *account; + + cmdproc = servconn->cmdproc; + g_return_if_fail(cmdproc != NULL); + + account = cmdproc->session->account; + swboard = cmdproc->data; + g_return_if_fail(swboard != NULL); + + if (msn_switchboard_is_invited(swboard)) + { + swboard->empty = FALSE; + + msn_cmdproc_send(cmdproc, "ANS", "%s %s %s", + gaim_account_get_username(account), + swboard->auth_key, swboard->session_id); + } + else + { + msn_cmdproc_send(cmdproc, "USR", "%s %s", + gaim_account_get_username(account), + swboard->auth_key); + } +} + +static void +disconnect_cb(MsnServConn *servconn) +{ + MsnSwitchBoard *swboard; + + swboard = servconn->cmdproc->data; + g_return_if_fail(swboard != NULL); + + msn_servconn_set_disconnect_cb(swboard->servconn, NULL); + + msn_switchboard_destroy(swboard); +} + +gboolean +msn_switchboard_connect(MsnSwitchBoard *swboard, const char *host, int port) +{ + g_return_val_if_fail(swboard != NULL, FALSE); + + msn_servconn_set_connect_cb(swboard->servconn, connect_cb); + msn_servconn_set_disconnect_cb(swboard->servconn, disconnect_cb); + + return msn_servconn_connect(swboard->servconn, host, port); +} + +void +msn_switchboard_disconnect(MsnSwitchBoard *swboard) +{ + g_return_if_fail(swboard != NULL); + + msn_servconn_disconnect(swboard->servconn); +} + +/************************************************************************** + * Call stuff + **************************************************************************/ +static void +got_cal(MsnCmdProc *cmdproc, MsnCommand *cmd) +{ +#if 0 + MsnSwitchBoard *swboard; + const char *user; + + swboard = cmdproc->data; + + user = cmd->params[0]; + + msn_switchboard_add_user(swboard, user); +#endif +} + +static void +cal_timeout(MsnCmdProc *cmdproc, MsnTransaction *trans) +{ + gaim_debug_warning("msn", "cal_timeout: command %s timed out\n", trans->command); + + cal_error_helper(trans, MSN_SB_ERROR_UNKNOWN); +} + +static void +cal_error(MsnCmdProc *cmdproc, MsnTransaction *trans, int error) +{ + int reason = MSN_SB_ERROR_UNKNOWN; + + if (error == 215) + { + gaim_debug_info("msn", "Invited user already in switchboard\n"); + return; + } + else if (error == 217) + { + reason = MSN_SB_ERROR_USER_OFFLINE; + } + + gaim_debug_warning("msn", "cal_error: command %s gave error %i\n", trans->command, error); + + cal_error_helper(trans, reason); +} + +void +msn_switchboard_request_add_user(MsnSwitchBoard *swboard, const char *user) +{ + MsnTransaction *trans; + MsnCmdProc *cmdproc; + + g_return_if_fail(swboard != NULL); + + cmdproc = swboard->cmdproc; + + trans = msn_transaction_new(cmdproc, "CAL", "%s", user); + /* this doesn't do anything, but users seem to think that + * 'Unhandled command' is some kind of error, so we don't report it */ + msn_transaction_add_cb(trans, "CAL", got_cal); + + msn_transaction_set_data(trans, swboard); + msn_transaction_set_timeout_cb(trans, cal_timeout); + + if (swboard->ready) + msn_cmdproc_send_trans(cmdproc, trans); + else + msn_cmdproc_queue_trans(cmdproc, trans); +} + +/************************************************************************** + * Create & Transfer stuff + **************************************************************************/ + +static void +got_swboard(MsnCmdProc *cmdproc, MsnCommand *cmd) +{ + MsnSwitchBoard *swboard; + char *host; + int port; + swboard = cmd->trans->data; + + if (g_list_find(cmdproc->session->switches, swboard) == NULL) + /* The conversation window was closed. */ + return; + + msn_switchboard_set_auth_key(swboard, cmd->params[4]); + + msn_parse_socket(cmd->params[2], &host, &port); + + if (!msn_switchboard_connect(swboard, host, port)) + msn_switchboard_destroy(swboard); + + g_free(host); +} + +static void +xfr_error(MsnCmdProc *cmdproc, MsnTransaction *trans, int error) +{ + MsnSwitchBoard *swboard; + int reason = MSN_SB_ERROR_UNKNOWN; + + if (error == 913) + reason = MSN_SB_ERROR_OFFLINE; + else if (error == 800) + reason = MSN_SB_ERROR_TOO_FAST; + + swboard = trans->data; + + gaim_debug_info("msn", "xfr_error %i for %s: trans %x, command %s, reason %i\n", + error, (swboard->im_user ? swboard->im_user : "(null)"), trans, + (trans->command ? trans->command : "(null)"), reason); + + swboard_error_helper(swboard, reason, swboard->im_user); +} + +void +msn_switchboard_request(MsnSwitchBoard *swboard) +{ + MsnCmdProc *cmdproc; + MsnTransaction *trans; + + g_return_if_fail(swboard != NULL); + + cmdproc = swboard->session->notification->cmdproc; + + trans = msn_transaction_new(cmdproc, "XFR", "%s", "SB"); + msn_transaction_add_cb(trans, "XFR", got_swboard); + + msn_transaction_set_data(trans, swboard); + msn_transaction_set_error_cb(trans, xfr_error); + + msn_cmdproc_send_trans(cmdproc, trans); +} + +void +msn_switchboard_close(MsnSwitchBoard *swboard) +{ + g_return_if_fail(swboard != NULL); + + if (swboard->error != MSN_SB_ERROR_NONE) + { + msn_switchboard_destroy(swboard); + } + else if (g_queue_is_empty(swboard->msg_queue) || + !swboard->session->connected) + { + MsnCmdProc *cmdproc; + cmdproc = swboard->cmdproc; + msn_cmdproc_send_quick(cmdproc, "OUT", NULL, NULL); + + msn_switchboard_destroy(swboard); + } + else + { + swboard->closed = TRUE; + } +} + +gboolean +msn_switchboard_release(MsnSwitchBoard *swboard, MsnSBFlag flag) +{ + g_return_val_if_fail(swboard != NULL, FALSE); + + swboard->flag &= ~flag; + + if (flag == MSN_SB_FLAG_IM) + /* Forget any conversation that used to be associated with this + * swboard. */ + swboard->conv = NULL; + + if (swboard->flag == 0) + { + msn_switchboard_close(swboard); + return TRUE; + } + + return FALSE; +} + +/************************************************************************** + * Init stuff + **************************************************************************/ + +void +msn_switchboard_init(void) +{ + cbs_table = msn_table_new(); + + msn_table_add_cmd(cbs_table, "ANS", "ANS", ans_cmd); + msn_table_add_cmd(cbs_table, "ANS", "IRO", iro_cmd); + + msn_table_add_cmd(cbs_table, "MSG", "ACK", ack_cmd); + msn_table_add_cmd(cbs_table, "MSG", "NAK", nak_cmd); + + msn_table_add_cmd(cbs_table, "USR", "USR", usr_cmd); + + msn_table_add_cmd(cbs_table, NULL, "MSG", msg_cmd); + msn_table_add_cmd(cbs_table, NULL, "JOI", joi_cmd); + msn_table_add_cmd(cbs_table, NULL, "BYE", bye_cmd); + msn_table_add_cmd(cbs_table, NULL, "OUT", out_cmd); + +#if 0 + /* They might skip the history */ + msn_table_add_cmd(cbs_table, NULL, "ACK", NULL); +#endif + + msn_table_add_error(cbs_table, "MSG", msg_error); + msn_table_add_error(cbs_table, "CAL", cal_error); + + /* Register the message type callbacks. */ + msn_table_add_msg_type(cbs_table, "text/plain", + plain_msg); + msn_table_add_msg_type(cbs_table, "text/x-msmsgscontrol", + control_msg); + msn_table_add_msg_type(cbs_table, "text/x-clientcaps", + clientcaps_msg); + msn_table_add_msg_type(cbs_table, "text/x-clientinfo", + clientcaps_msg); + msn_table_add_msg_type(cbs_table, "application/x-msnmsgrp2p", + msn_p2p_msg); + msn_table_add_msg_type(cbs_table, "text/x-mms-emoticon", + msn_emoticon_msg); + msn_table_add_msg_type(cbs_table, "text/x-mms-animemoticon", + msn_emoticon_msg); + msn_table_add_msg_type(cbs_table, "text/x-msnmsgr-datacast", + nudge_msg); +#if 0 + msn_table_add_msg_type(cbs_table, "text/x-msmmsginvite", + msn_invite_msg); +#endif +} + +void +msn_switchboard_end(void) +{ + msn_table_destroy(cbs_table); +}