chat: make Invite... menu work again
[siplcs.git] / src / core / sipe-chat.c
blob30544b591363fe271e0de58ffd7557a249f66a88
1 /**
2 * @file sipe-chat.c
4 * pidgin-sipe
6 * Copyright (C) 2009-10 SIPE Project <http://sipe.sourceforge.net/>
8 * This program is free software; you can redistribute it and/or modify
9 * it under the terms of the GNU General Public License as published by
10 * the Free Software Foundation; either version 2 of the License, or
11 * (at your option) any later version.
13 * This program is distributed in the hope that it will be useful,
14 * but WITHOUT ANY WARRANTY; without even the implied warranty of
15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 * GNU General Public License for more details.
18 * You should have received a copy of the GNU General Public License
19 * along with this program; if not, write to the Free Software
20 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
23 #ifdef HAVE_CONFIG_H
24 #include "config.h"
25 #endif
27 #include <stdlib.h>
28 #include <string.h>
29 #include <time.h>
31 #include <glib.h>
33 #include "sipe-common.h"
34 #include "sipmsg.h"
35 #include "sip-transport.h"
36 #include "sipe-backend.h"
37 #include "sipe-chat.h"
38 #include "sipe-conf.h"
39 #include "sipe-core.h"
40 #include "sipe-core-private.h"
41 #include "sipe-dialog.h"
42 #include "sipe-groupchat.h"
43 #include "sipe-nls.h"
44 #include "sipe-schedule.h"
45 #include "sipe-session.h"
46 #include "sipe-utils.h"
47 #include "sipe-xml.h"
48 #include "sipe.h"
50 static GList *chat_sessions = NULL;
52 struct sipe_chat_session *sipe_chat_create_session(enum sipe_chat_type type,
53 const gchar *id,
54 const gchar *title)
56 struct sipe_chat_session *session = g_new0(struct sipe_chat_session, 1);
57 session->id = g_strdup(id);
58 session->title = g_strdup(title);
59 session->type = type;
60 chat_sessions = g_list_prepend(chat_sessions, session);
61 return(session);
64 void sipe_chat_remove_session(struct sipe_chat_session *session)
66 chat_sessions = g_list_remove(chat_sessions, session);
67 sipe_backend_chat_session_destroy(session->backend);
68 g_free(session->title);
69 g_free(session->id);
70 g_free(session);
73 void sipe_chat_destroy(void)
75 while (chat_sessions) {
76 struct sipe_chat_session *chat_session = chat_sessions->data;
77 SIPE_DEBUG_INFO("sipe_chat_destroy: '%s' (%s)",
78 chat_session->title, chat_session->id);
79 sipe_chat_remove_session(chat_session);
83 void sipe_core_chat_invite(struct sipe_core_public *sipe_public,
84 struct sipe_chat_session *chat_session,
85 const char *name)
87 struct sipe_core_private *sipe_private = SIPE_CORE_PRIVATE;
89 SIPE_DEBUG_INFO("sipe_core_chat_create: who '%s'", name);
91 switch (chat_session->type) {
92 case SIPE_CHAT_TYPE_MULTIPARTY:
93 case SIPE_CHAT_TYPE_CONFERENCE:
95 struct sip_session *session = sipe_session_find_chat(sipe_private,
96 chat_session);
98 if (session) {
99 gchar *uri = sip_uri(name);
100 sipe_invite_to_chat(sipe_private, session, uri);
101 g_free(uri);
104 break;
105 case SIPE_CHAT_TYPE_GROUPCHAT:
106 /* @TODO */
107 SIPE_DEBUG_INFO_NOFORMAT("GROUP CHAT: INVITE NOT IMPLEMENTED!");
108 break;
109 default:
110 break;
114 void sipe_core_chat_rejoin(struct sipe_core_public *sipe_public,
115 struct sipe_chat_session *chat_session)
117 struct sipe_core_private *sipe_private = SIPE_CORE_PRIVATE;
119 SIPE_DEBUG_INFO("sipe_core_chat_rejoin: '%s'", chat_session->title);
121 switch (chat_session->type) {
122 case SIPE_CHAT_TYPE_MULTIPARTY:
123 /* @TODO */
124 break;
125 case SIPE_CHAT_TYPE_CONFERENCE:
126 /* @TODO */
127 break;
128 case SIPE_CHAT_TYPE_GROUPCHAT:
129 sipe_groupchat_rejoin(sipe_private, chat_session);
130 break;
131 default:
132 break;
136 void sipe_core_chat_leave(struct sipe_core_public *sipe_public,
137 struct sipe_chat_session *chat_session)
139 struct sipe_core_private *sipe_private = SIPE_CORE_PRIVATE;
141 SIPE_DEBUG_INFO("sipe_core_chat_leave: '%s'", chat_session->title);
143 switch (chat_session->type) {
144 case SIPE_CHAT_TYPE_MULTIPARTY:
145 case SIPE_CHAT_TYPE_CONFERENCE:
147 struct sip_session *session = sipe_session_find_chat(sipe_private,
148 chat_session);
150 if (session) {
151 sipe_session_close(sipe_private, session);
154 break;
155 case SIPE_CHAT_TYPE_GROUPCHAT:
156 sipe_groupchat_leave(sipe_private, chat_session);
157 break;
158 default:
159 break;
163 void sipe_core_chat_send(struct sipe_core_public *sipe_public,
164 struct sipe_chat_session *chat_session,
165 const char *what)
167 struct sipe_core_private *sipe_private = SIPE_CORE_PRIVATE;
169 SIPE_DEBUG_INFO("sipe_core_chat_send: '%s' to '%s'",
170 what, chat_session->title);
172 switch (chat_session->type) {
173 case SIPE_CHAT_TYPE_MULTIPARTY:
174 case SIPE_CHAT_TYPE_CONFERENCE:
176 struct sip_session *session = sipe_session_find_chat(sipe_private,
177 chat_session);
179 if (session) {
180 sipe_session_enqueue_message(session,
181 what,
182 NULL);
183 sipe_im_process_queue(sipe_private, session);
186 break;
187 case SIPE_CHAT_TYPE_GROUPCHAT:
188 sipe_groupchat_send(sipe_private, chat_session, what);
189 break;
190 default:
191 break;
195 gchar *
196 sipe_chat_get_name(void)
199 * A non-volatile ID counter.
200 * Should survive connection drop & reconnect.
202 static guint chat_seq = 0;
204 /* Generate next ID */
205 gchar *chat_name = g_strdup_printf(_("Chat #%d"), ++chat_seq);
206 SIPE_DEBUG_INFO("sipe_chat_get_name: added new: %s", chat_name);
208 return chat_name;
211 static void
212 sipe_refer(struct sipe_core_private *sipe_private,
213 struct sip_session *session,
214 const gchar *who)
216 gchar *hdr;
217 gchar *contact;
218 gchar *epid = get_epid(sipe_private);
219 struct sip_dialog *dialog = sipe_dialog_find(session,
220 session->roster_manager);
221 const char *ourtag = dialog && dialog->ourtag ? dialog->ourtag : NULL;
223 contact = get_contact(sipe_private);
224 hdr = g_strdup_printf(
225 "Contact: %s\r\n"
226 "Refer-to: <%s>\r\n"
227 "Referred-By: <sip:%s>%s%s;epid=%s\r\n"
228 "Require: com.microsoft.rtc-multiparty\r\n",
229 contact,
230 who,
231 sipe_private->username,
232 ourtag ? ";tag=" : "",
233 ourtag ? ourtag : "",
234 epid);
235 g_free(epid);
237 sip_transport_request(sipe_private,
238 "REFER",
239 session->roster_manager,
240 session->roster_manager,
241 hdr,
242 NULL,
243 dialog,
244 NULL);
246 g_free(hdr);
247 g_free(contact);
250 static gboolean
251 sipe_is_election_finished(struct sip_session *session)
253 gboolean res = TRUE;
255 SIPE_DIALOG_FOREACH {
256 if (dialog->election_vote == 0) {
257 res = FALSE;
258 break;
260 } SIPE_DIALOG_FOREACH_END;
262 if (res) {
263 session->is_voting_in_progress = FALSE;
265 return res;
268 static gboolean
269 process_info_response(struct sipe_core_private *sipe_private,
270 struct sipmsg *msg,
271 struct transaction *trans);
273 static void
274 sipe_send_election_set_rm(struct sipe_core_private *sipe_private,
275 struct sip_dialog *dialog)
277 const gchar *hdr = "Content-Type: application/x-ms-mim\r\n";
279 gchar *body = g_strdup_printf(
280 "<?xml version=\"1.0\"?>\r\n"
281 "<action xmlns=\"http://schemas.microsoft.com/sip/multiparty/\">"
282 "<SetRM uri=\"sip:%s\"/></action>\r\n",
283 sipe_private->username);
285 sip_transport_info(sipe_private,
286 hdr,
287 body,
288 dialog,
289 process_info_response);
291 g_free(body);
294 void
295 sipe_process_pending_invite_queue(struct sipe_core_private *sipe_private,
296 struct sip_session *session)
298 gchar *invitee;
299 GSList *entry = session->pending_invite_queue;
301 while (entry) {
302 invitee = entry->data;
303 sipe_invite_to_chat(sipe_private, session, invitee);
304 entry = session->pending_invite_queue = g_slist_remove(session->pending_invite_queue, invitee);
305 g_free(invitee);
309 static void
310 sipe_election_result(struct sipe_core_private *sipe_private,
311 void *sess)
313 struct sip_session *session = (struct sip_session *)sess;
314 gchar *rival;
315 gboolean has_won = TRUE;
317 if (session->roster_manager) {
318 SIPE_DEBUG_INFO(
319 "sipe_election_result: RM has already been elected in the meantime. It is %s",
320 session->roster_manager);
321 return;
324 session->is_voting_in_progress = FALSE;
326 SIPE_DIALOG_FOREACH {
327 if (dialog->election_vote < 0) {
328 has_won = FALSE;
329 rival = dialog->with;
330 break;
332 } SIPE_DIALOG_FOREACH_END;
334 if (has_won) {
335 SIPE_DEBUG_INFO_NOFORMAT("sipe_election_result: we have won RM election!");
337 session->roster_manager = sip_uri_self(sipe_private);
339 SIPE_DIALOG_FOREACH {
340 /* send SetRM to each chat participant*/
341 sipe_send_election_set_rm(sipe_private, dialog);
342 } SIPE_DIALOG_FOREACH_END;
343 } else {
344 SIPE_DEBUG_INFO("sipe_election_result: we loose RM election to %s", rival);
346 session->bid = 0;
348 sipe_process_pending_invite_queue(sipe_private, session);
351 static gboolean
352 process_info_response(struct sipe_core_private *sipe_private,
353 struct sipmsg *msg,
354 SIPE_UNUSED_PARAMETER struct transaction *trans)
356 const gchar *contenttype = sipmsg_find_header(msg, "Content-Type");
357 const gchar *callid = sipmsg_find_header(msg, "Call-ID");
358 struct sip_dialog *dialog;
359 struct sip_session *session;
361 session = sipe_session_find_chat_by_callid(sipe_private, callid);
362 if (!session) {
363 SIPE_DEBUG_INFO("process_info_response: failed find dialog for callid %s, exiting.", callid);
364 return FALSE;
367 if (msg->response == 200 && g_str_has_prefix(contenttype, "application/x-ms-mim")) {
368 sipe_xml *xn_action = sipe_xml_parse(msg->body, msg->bodylen);
369 const sipe_xml *xn_request_rm_response = sipe_xml_child(xn_action, "RequestRMResponse");
370 const sipe_xml *xn_set_rm_response = sipe_xml_child(xn_action, "SetRMResponse");
372 if (xn_request_rm_response) {
373 const char *with = sipe_xml_attribute(xn_request_rm_response, "uri");
374 const char *allow = sipe_xml_attribute(xn_request_rm_response, "allow");
376 dialog = sipe_dialog_find(session, with);
377 if (!dialog) {
378 SIPE_DEBUG_INFO("process_info_response: failed find dialog for %s, exiting.", with);
379 sipe_xml_free(xn_action);
380 return FALSE;
383 if (allow && !g_strcasecmp(allow, "true")) {
384 SIPE_DEBUG_INFO("process_info_response: %s has voted PRO", with);
385 dialog->election_vote = 1;
386 } else if (allow && !g_strcasecmp(allow, "false")) {
387 SIPE_DEBUG_INFO("process_info_response: %s has voted CONTRA", with);
388 dialog->election_vote = -1;
391 if (sipe_is_election_finished(session)) {
392 sipe_election_result(sipe_private,
393 session);
396 } else if (xn_set_rm_response) {
399 sipe_xml_free(xn_action);
403 return TRUE;
406 static void
407 sipe_send_election_request_rm(struct sipe_core_private *sipe_private,
408 struct sip_dialog *dialog,
409 int bid)
411 const gchar *hdr = "Content-Type: application/x-ms-mim\r\n";
413 gchar *body = g_strdup_printf(
414 "<?xml version=\"1.0\"?>\r\n"
415 "<action xmlns=\"http://schemas.microsoft.com/sip/multiparty/\">"
416 "<RequestRM uri=\"sip:%s\" bid=\"%d\"/></action>\r\n",
417 sipe_private->username, bid);
419 sip_transport_info(sipe_private,
420 hdr,
421 body,
422 dialog,
423 process_info_response);
425 g_free(body);
428 static void
429 sipe_election_start(struct sipe_core_private *sipe_private,
430 struct sip_session *session)
432 if (session->is_voting_in_progress) {
433 SIPE_DEBUG_INFO_NOFORMAT("sipe_election_start: other election is in progress, exiting.");
434 return;
435 } else {
436 session->is_voting_in_progress = TRUE;
438 session->bid = rand();
440 SIPE_DEBUG_INFO("sipe_election_start: RM election has initiated. Our bid=%d", session->bid);
442 SIPE_DIALOG_FOREACH {
443 /* reset election_vote for each chat participant */
444 dialog->election_vote = 0;
446 /* send RequestRM to each chat participant*/
447 sipe_send_election_request_rm(sipe_private, dialog, session->bid);
448 } SIPE_DIALOG_FOREACH_END;
450 sipe_schedule_seconds(sipe_private,
451 "<+election-result>",
452 session,
454 sipe_election_result,
455 NULL);
458 void
459 sipe_invite_to_chat(struct sipe_core_private *sipe_private,
460 struct sip_session *session,
461 const gchar *who)
463 /* a conference */
464 if (session->chat_session->type == SIPE_CHAT_TYPE_CONFERENCE)
466 sipe_invite_conf(sipe_private, session, who);
468 else /* a multi-party chat */
470 gchar *self = sip_uri_self(sipe_private);
471 if (session->roster_manager) {
472 if (sipe_strcase_equal(session->roster_manager, self)) {
473 sipe_invite(sipe_private, session, who, NULL, NULL, NULL, FALSE);
474 } else {
475 sipe_refer(sipe_private, session, who);
477 } else {
478 SIPE_DEBUG_INFO_NOFORMAT("sipe_buddy_menu_chat_invite: no RM available");
480 session->pending_invite_queue = slist_insert_unique_sorted(
481 session->pending_invite_queue, g_strdup(who), (GCompareFunc)strcmp);
483 sipe_election_start(sipe_private, session);
485 g_free(self);
490 Local Variables:
491 mode: c
492 c-file-style: "bsd"
493 indent-tabs-mode: t
494 tab-width: 8
495 End: