comparison libpurple/protocols/bonjour/jabber.c @ 17558:6e4e2d234c3a

Update Bonjour to do nonblocking I/O correctly. This also includes a number of error handling bugfixes (and various other improvements). This doesn't handle the scenario where a partial message is read - I need to figure out how libxml2 handles such a scenario to fix it correctly. There are also also a few quirks that I noticed and didn't get around to fixing: -We don't wait for a "</stream:stream>" from the peer before closing the socket. -We don't make sure that the peer has sent us the stream start message before starting.
author Daniel Atallah <daniel.atallah@gmail.com>
date Fri, 08 Jun 2007 18:24:23 +0000
parents 612dc5149964
children bce4211c4980
comparison
equal deleted inserted replaced
17557:2af1f8ccd396 17558:6e4e2d234c3a
47 #define STREAM_END "</stream:stream>" 47 #define STREAM_END "</stream:stream>"
48 /* TODO: specify version='1.0' and send stream features */ 48 /* TODO: specify version='1.0' and send stream features */
49 #define DOCTYPE "<?xml version=\"1.0\" encoding=\"UTF-8\" ?>\n" \ 49 #define DOCTYPE "<?xml version=\"1.0\" encoding=\"UTF-8\" ?>\n" \
50 "<stream:stream xmlns=\"jabber:client\" xmlns:stream=\"http://etherx.jabber.org/streams\" from=\"%s\" to=\"%s\">" 50 "<stream:stream xmlns=\"jabber:client\" xmlns:stream=\"http://etherx.jabber.org/streams\" from=\"%s\" to=\"%s\">"
51 51
52 static gint
53 _connect_to_buddy(PurpleBuddy *gb)
54 {
55 gint socket_fd;
56 struct sockaddr_in buddy_address;
57 BonjourBuddy *bb = gb->proto_data;
58
59 purple_debug_info("bonjour", "Connecting to buddy %s at %s:%d.\n",
60 purple_buddy_get_name(gb), bb->ip ? bb->ip : "(null)", bb->port_p2pj);
61
62 /* Create a socket and make it non-blocking */
63 socket_fd = socket(PF_INET, SOCK_STREAM, 0);
64 if (socket_fd < 0) {
65 purple_debug_warning("bonjour", "Error opening socket: %s\n", strerror(errno));
66 return -1;
67 }
68
69 buddy_address.sin_family = PF_INET;
70 buddy_address.sin_port = htons(bb->port_p2pj);
71 inet_aton(bb->ip, &(buddy_address.sin_addr));
72 memset(&(buddy_address.sin_zero), '\0', 8);
73
74 /* TODO: make this nonblocking before connecting */
75 if (connect(socket_fd, (struct sockaddr*)&buddy_address, sizeof(struct sockaddr)) == 0)
76 fcntl(socket_fd, F_SETFL, O_NONBLOCK);
77 else {
78 purple_debug_warning("bonjour", "Error connecting to buddy %s at %s:%d error: %s\n", purple_buddy_get_name(gb), bb->ip ? bb->ip : "(null)", bb->port_p2pj, strerror(errno));
79 close(socket_fd);
80 socket_fd = -1;
81 }
82
83 return socket_fd;
84 }
85
86 #if 0 /* this isn't used anywhere... */ 52 #if 0 /* this isn't used anywhere... */
87 static const char * 53 static const char *
88 _font_size_purple_to_ichat(int size) 54 _font_size_purple_to_ichat(int size)
89 { 55 {
90 switch (size) { 56 switch (size) {
111 static BonjourJabberConversation * 77 static BonjourJabberConversation *
112 bonjour_jabber_conv_new() { 78 bonjour_jabber_conv_new() {
113 79
114 BonjourJabberConversation *bconv = g_new0(BonjourJabberConversation, 1); 80 BonjourJabberConversation *bconv = g_new0(BonjourJabberConversation, 1);
115 bconv->socket = -1; 81 bconv->socket = -1;
116 bconv->watcher_id = -1; 82 bconv->tx_buf = purple_circ_buffer_new(512);
83 bconv->tx_handler = -1;
84 bconv->rx_handler = -1;
117 85
118 return bconv; 86 return bconv;
119 } 87 }
88
120 89
121 static const char * 90 static const char *
122 _font_size_ichat_to_purple(int size) 91 _font_size_ichat_to_purple(int size)
123 { 92 {
124 if (size > 24) { 93 if (size > 24) {
137 106
138 return "1"; 107 return "1";
139 } 108 }
140 109
141 static void 110 static void
142 _jabber_parse_and_write_message_to_ui(xmlnode *message_node, PurpleConnection *connection, PurpleBuddy *gb) 111 _jabber_parse_and_write_message_to_ui(xmlnode *message_node, PurpleConnection *connection, PurpleBuddy *pb)
143 { 112 {
144 xmlnode *body_node, *html_node, *events_node; 113 xmlnode *body_node, *html_node, *events_node;
145 char *body, *html_body = NULL; 114 char *body, *html_body = NULL;
146 const char *ichat_balloon_color = NULL; 115 const char *ichat_balloon_color = NULL;
147 const char *ichat_text_color = NULL; 116 const char *ichat_text_color = NULL;
186 155
187 events_node = xmlnode_get_child_with_namespace(message_node, "x", "jabber:x:event"); 156 events_node = xmlnode_get_child_with_namespace(message_node, "x", "jabber:x:event");
188 if (events_node != NULL) 157 if (events_node != NULL)
189 { 158 {
190 if (xmlnode_get_child(events_node, "composing") != NULL) 159 if (xmlnode_get_child(events_node, "composing") != NULL)
191 {
192 composing_event = TRUE; 160 composing_event = TRUE;
193 }
194 if (xmlnode_get_child(events_node, "id") != NULL) 161 if (xmlnode_get_child(events_node, "id") != NULL)
195 { 162 {
196 /* The user is just typing */ 163 /* The user is just typing */
197 /* TODO: Deal with typing notification */ 164 /* TODO: Deal with typing notification */
198 g_free(body); 165 g_free(body);
216 } 183 }
217 184
218 /* TODO: Should we do something with "composing_event" here? */ 185 /* TODO: Should we do something with "composing_event" here? */
219 186
220 /* Send the message to the UI */ 187 /* Send the message to the UI */
221 serv_got_im(connection, gb->name, body, 0, time(NULL)); 188 serv_got_im(connection, pb->name, body, 0, time(NULL));
222 189
223 g_free(body); 190 g_free(body);
224 g_free(html_body); 191 g_free(html_body);
225 } 192 }
226 193
279 purple_debug_info("bonjour", "Receive: -%s- %d bytes\n", *message, total_message_length); 246 purple_debug_info("bonjour", "Receive: -%s- %d bytes\n", *message, total_message_length);
280 247
281 return total_message_length; 248 return total_message_length;
282 } 249 }
283 250
251 static void
252 _send_data_write_cb(gpointer data, gint source, PurpleInputCondition cond)
253 {
254 PurpleBuddy *pb = data;
255 BonjourBuddy *bb = pb->proto_data;
256 BonjourJabberConversation *bconv = bb->conversation;
257 int ret, writelen;
258
259 /* TODO: Make sure that the stream has been established before sending */
260
261 writelen = purple_circ_buffer_get_max_read(bconv->tx_buf);
262
263 if (writelen == 0) {
264 purple_input_remove(bconv->tx_handler);
265 bconv->tx_handler = -1;
266 return;
267 }
268
269 ret = send(bconv->socket, bconv->tx_buf->outptr, writelen, 0);
270
271 if (ret < 0 && errno == EAGAIN)
272 return;
273 else if (ret <= 0) {
274 PurpleConversation *conv;
275 const char *error = strerror(errno);
276
277 purple_debug_error("bonjour", "Error sending message to buddy %s error: %s\n",
278 purple_buddy_get_name(pb), error ? error : "(null)");
279
280 conv = purple_find_conversation_with_account(PURPLE_CONV_TYPE_IM, bb->name, pb->account);
281 if (conv != NULL)
282 purple_conversation_write(conv, NULL,
283 _("Unable to send message."),
284 PURPLE_MESSAGE_SYSTEM, time(NULL));
285
286 bonjour_jabber_close_conversation(bb->conversation);
287 bb->conversation = NULL;
288 return;
289 }
290
291 purple_circ_buffer_mark_read(bconv->tx_buf, ret);
292 }
293
284 static gint 294 static gint
285 _send_data(gint socket, char *message) 295 _send_data(PurpleBuddy *pb, char *message)
286 { 296 {
287 gint message_len = strlen(message); 297 gint ret;
288 gint partial_sent = 0; 298 int len = strlen(message);
289 gchar *partial_message = message; 299 BonjourBuddy *bb = pb->proto_data;
290 300 BonjourJabberConversation *bconv = bb->conversation;
291 while ((partial_sent = send(socket, partial_message, message_len, 0)) < message_len) 301
292 { 302 /* If we're not ready to actually send, append it to the buffer */
293 if (partial_sent != -1) { 303 if (bconv->tx_handler != -1
294 partial_message += partial_sent; 304 || bconv->connect_data != NULL
295 message_len -= partial_sent; 305 || !bconv->stream_started
296 } else { 306 || purple_circ_buffer_get_max_read(bconv->tx_buf) > 0) {
297 return -1; 307 ret = -1;
298 } 308 errno = EAGAIN;
299 } 309 } else {
300 310 ret = send(bconv->socket, message, len, 0);
301 return strlen(message); 311 }
312
313 if (ret == -1 && errno == EAGAIN)
314 ret = 0;
315 else if (ret <= 0) {
316 PurpleConversation *conv;
317 const char *error = strerror(errno);
318
319 purple_debug_error("bonjour", "Error sending message to buddy %s error: %s\n",
320 purple_buddy_get_name(pb), error ? error : "(null)");
321
322 conv = purple_find_conversation_with_account(PURPLE_CONV_TYPE_IM, bb->name, pb->account);
323 if (conv != NULL)
324 purple_conversation_write(conv, NULL,
325 _("Unable to send message."),
326 PURPLE_MESSAGE_SYSTEM, time(NULL));
327
328 bonjour_jabber_close_conversation(bb->conversation);
329 bb->conversation = NULL;
330 return -1;
331 }
332
333 if (ret < len) {
334 if (bconv->tx_handler == -1)
335 bconv->tx_handler = purple_input_add(bconv->socket, PURPLE_INPUT_WRITE,
336 _send_data_write_cb, pb);
337 purple_circ_buffer_append(bconv->tx_buf, message + ret, len - ret);
338 }
339
340 return ret;
302 } 341 }
303 342
304 static void 343 static void
305 _client_socket_handler(gpointer data, gint socket, PurpleInputCondition condition) 344 _client_socket_handler(gpointer data, gint socket, PurpleInputCondition condition)
306 { 345 {
307 char *message = NULL; 346 char *message = NULL;
308 gint message_length; 347 gint message_length;
309 PurpleBuddy *pb = data; 348 PurpleBuddy *pb = data;
310 PurpleAccount *account = pb->account; 349 PurpleAccount *account = pb->account;
311 PurpleConversation *conversation;
312 BonjourBuddy *bb = pb->proto_data; 350 BonjourBuddy *bb = pb->proto_data;
313 gboolean closed_conversation = FALSE; 351 gboolean closed_conversation = FALSE;
314 xmlnode *message_node; 352 xmlnode *message_node;
315 353
316 /* Read the data from the socket */ 354 /* Read the data from the socket */
317 if ((message_length = _read_data(socket, &message)) == -1) { 355 if ((message_length = _read_data(socket, &message)) == -1) {
318 /* There have been an error reading from the socket */ 356 /* There have been an error reading from the socket */
319 /* TODO: Shouldn't we handle the error if it isn't EAGAIN? */ 357 if (errno != EAGAIN) {
358 bonjour_jabber_close_conversation(bb->conversation);
359 bb->conversation = NULL;
360
361 /* I guess we really don't need to notify the user.
362 * If they try to send another message it'll reconnect */
363 }
320 return; 364 return;
321 } else if (message_length == 0) { /* The other end has closed the socket */ 365 } else if (message_length == 0) { /* The other end has closed the socket */
322 closed_conversation = TRUE; 366 closed_conversation = TRUE;
323 } else { 367 } else {
324 message[message_length] = '\0'; 368 message[message_length] = '\0';
329 } 373 }
330 } 374 }
331 375
332 /* Parse the message into an XMLnode for analysis */ 376 /* Parse the message into an XMLnode for analysis */
333 message_node = xmlnode_from_str(message, strlen(message)); 377 message_node = xmlnode_from_str(message, strlen(message));
334
335 /* Check if the start of the stream has been received, if not check that the current */
336 /* data is the start of the stream */
337 if (!(bb->conversation->stream_started))
338 {
339 /* Check if this is the start of the stream */
340 if ((message_node != NULL) &&
341 g_ascii_strcasecmp(xmlnode_get_attrib(message_node, "xmlns"), "jabber:client") &&
342 (xmlnode_get_attrib(message_node,"xmlns:stream") != NULL))
343 {
344 bb->conversation->stream_started = TRUE;
345 }
346 else
347 {
348 char *stream_start = g_strdup_printf(DOCTYPE, purple_account_get_username(pb->account),
349 purple_buddy_get_name(pb));
350
351 /* TODO: This needs to be nonblocking! */
352 if (send(bb->conversation->socket, stream_start, strlen(stream_start), 0) == -1)
353 purple_debug_error("bonjour", "Unable to start a conversation with %s\n", bb->name);
354 else
355 bb->conversation->stream_started = TRUE;
356
357 g_free(stream_start);
358 }
359 }
360 378
361 /* 379 /*
362 * Check that this is not the end of the conversation. This is 380 * Check that this is not the end of the conversation. This is
363 * using a magic string, but xmlnode won't play nice when just 381 * using a magic string, but xmlnode won't play nice when just
364 * parsing an end tag 382 * parsing an end tag
365 */ 383 */
366 if (closed_conversation || purple_str_has_prefix(message, STREAM_END)) { 384 if (closed_conversation || purple_str_has_prefix(message, STREAM_END)) {
367 char *closed_conv_message; 385 PurpleConversation *conv;
368 386
369 /* Close the socket, clear the watcher and free memory */ 387 /* Close the socket, clear the watcher and free memory */
370 bonjour_jabber_close_conversation(bb->conversation); 388 bonjour_jabber_close_conversation(bb->conversation);
371 bb->conversation = NULL; 389 bb->conversation = NULL;
372 390
373 /* Inform the user that the conversation has been closed */ 391 /* Inform the user that the conversation has been closed */
374 conversation = purple_find_conversation_with_account(PURPLE_CONV_TYPE_IM, pb->name, account); 392 conv = purple_find_conversation_with_account(PURPLE_CONV_TYPE_IM, pb->name, account);
375 closed_conv_message = g_strdup_printf(_("%s has closed the conversation."), pb->name); 393 if (conv != NULL) {
376 purple_conversation_write(conversation, NULL, closed_conv_message, PURPLE_MESSAGE_SYSTEM, time(NULL)); 394 char *tmp = g_strdup_printf(_("%s has closed the conversation."), pb->name);
377 g_free(closed_conv_message); 395 purple_conversation_write(conv, NULL, tmp, PURPLE_MESSAGE_SYSTEM, time(NULL));
396 g_free(tmp);
397 }
378 } else if (message_node != NULL) { 398 } else if (message_node != NULL) {
379 /* Parse the message to get the data and send to the ui */ 399 /* Parse the message to get the data and send to the ui */
380 _jabber_parse_and_write_message_to_ui(message_node, account->gc, pb); 400 _jabber_parse_and_write_message_to_ui(message_node, account->gc, pb);
381 } else { 401 } else {
382 /* TODO: Deal with receiving only a partial message */ 402 /* TODO: Deal with receiving only a partial message */
383 } 403 }
384 404
385 g_free(message); 405 g_free(message);
386 if (message_node != NULL) 406 if (message_node != NULL)
387 xmlnode_free(message_node); 407 xmlnode_free(message_node);
408 }
409
410 struct _stream_start_data {
411 char *msg;
412 PurpleInputFunction tx_handler_cb;
413 };
414
415 static void
416 _start_stream(gpointer data, gint source, PurpleInputCondition condition)
417 {
418 PurpleBuddy *pb = data;
419 BonjourBuddy *bb = pb->proto_data;
420 struct _stream_start_data *ss = bb->conversation->stream_data;
421 int len, ret;
422
423 len = strlen(ss->msg);
424
425 /* Start Stream */
426 ret = send(source, ss->msg, len, 0);
427
428 if (ret == -1 && errno == EAGAIN)
429 return;
430 else if (ret <= 0) {
431 const char *err = strerror(errno);
432 PurpleConversation *conv;
433
434 purple_debug_error("bonjour", "Error starting stream with buddy %s at %s:%d error: %s\n",
435 purple_buddy_get_name(pb), bb->ip ? bb->ip : "(null)", bb->port_p2pj, err ? err : "(null)");
436
437 conv = purple_find_conversation_with_account(PURPLE_CONV_TYPE_IM, bb->name, pb->account);
438 if (conv != NULL)
439 purple_conversation_write(conv, NULL,
440 _("Unable to send the message, the conversation couldn't be started."),
441 PURPLE_MESSAGE_SYSTEM, time(NULL));
442
443 bonjour_jabber_close_conversation(bb->conversation);
444 bb->conversation = NULL;
445
446 return;
447 }
448
449 /* This is EXTREMELY unlikely to happen */
450 if (ret < len) {
451 char *tmp = g_strdup(ss->msg + ret);
452 g_free(ss->msg);
453 ss->msg = tmp;
454 return;
455 }
456
457 /* Stream started; process the send buffer if there is one*/
458 purple_input_remove(bb->conversation->tx_handler);
459 bb->conversation->tx_handler= -1;
460
461 bb->conversation->stream_started = TRUE;
462
463 g_free(ss->msg);
464 g_free(ss);
465 bb->conversation->stream_data = NULL;
466
467 if (ss->tx_handler_cb) {
468 bb->conversation->tx_handler = purple_input_add(source, PURPLE_INPUT_WRITE,
469 ss->tx_handler_cb, pb);
470 /* We can probably write the data now. */
471 (ss->tx_handler_cb)(pb, source, PURPLE_INPUT_WRITE);
472 }
388 } 473 }
389 474
390 static void 475 static void
391 _server_socket_handler(gpointer data, int server_socket, PurpleInputCondition condition) 476 _server_socket_handler(gpointer data, int server_socket, PurpleInputCondition condition)
392 { 477 {
425 bb = pb->proto_data; 510 bb = pb->proto_data;
426 511
427 /* Check if the conversation has been previously started */ 512 /* Check if the conversation has been previously started */
428 if (bb->conversation == NULL) 513 if (bb->conversation == NULL)
429 { 514 {
515 int ret, len;
516 char *stream_start = g_strdup_printf(DOCTYPE, purple_account_get_username(pb->account),
517 purple_buddy_get_name(pb));
518
519 len = strlen(stream_start);
520
521 /* Start the stream */
522 ret = send(client_socket, stream_start, len, 0);
523
524 if (ret == -1 && errno == EAGAIN)
525 ret = 0;
526 else if (ret <= 0) {
527 const char *err = strerror(errno);
528
529 purple_debug_error("bonjour", "Error starting stream with buddy %s at %s:%d error: %s\n",
530 purple_buddy_get_name(pb), bb->ip ? bb->ip : "(null)", bb->port_p2pj, err ? err : "(null)");
531
532 close(client_socket);
533 g_free(stream_start);
534
535 return;
536 }
537
430 bb->conversation = bonjour_jabber_conv_new(); 538 bb->conversation = bonjour_jabber_conv_new();
431 bb->conversation->socket = client_socket; 539 bb->conversation->socket = client_socket;
432 540 bb->conversation->rx_handler = purple_input_add(client_socket,
433 if (bb->conversation->stream_started == FALSE) { 541 PURPLE_INPUT_READ, _client_socket_handler, pb);
434 char *stream_start = g_strdup_printf(DOCTYPE, purple_account_get_username(pb->account), 542
435 purple_buddy_get_name(pb)); 543 /* This is unlikely to happen */
436 /* Start the stream */ 544 if (ret < len) {
437 send(bb->conversation->socket, stream_start, strlen(stream_start), 0); 545 struct _stream_start_data *ss = g_new(struct _stream_start_data, 1);
546 ss->msg = g_strdup(stream_start + ret);
547 ss->tx_handler_cb = NULL; /* We have nothing to write yet */
548 bb->conversation->stream_data = ss;
549 /* Finish sending the stream start */
550 bb->conversation->tx_handler = purple_input_add(client_socket,
551 PURPLE_INPUT_WRITE, _start_stream, pb);
552 } else {
438 bb->conversation->stream_started = TRUE; 553 bb->conversation->stream_started = TRUE;
439 g_free(stream_start); 554 }
440 } 555
441 556 g_free(stream_start);
442 /* Open a watcher for the client socket */
443 bb->conversation->watcher_id = purple_input_add(client_socket, PURPLE_INPUT_READ,
444 _client_socket_handler, pb);
445 } else { 557 } else {
446 close(client_socket); 558 close(client_socket);
447 } 559 }
448 } 560 }
449 561
517 data->watcher_id = purple_input_add(data->socket, PURPLE_INPUT_READ, _server_socket_handler, data); 629 data->watcher_id = purple_input_add(data->socket, PURPLE_INPUT_READ, _server_socket_handler, data);
518 630
519 return data->port; 631 return data->port;
520 } 632 }
521 633
634 static void
635 _connected_to_buddy(gpointer data, gint source, const gchar *error)
636 {
637 PurpleBuddy *pb = data;
638 BonjourBuddy *bb = pb->proto_data;
639 int len, ret;
640 char *stream_start = g_strdup_printf(DOCTYPE, purple_account_get_username(pb->account), purple_buddy_get_name(pb));
641
642 bb->conversation->connect_data = NULL;
643
644 if (source < 0) {
645 PurpleConversation *conv;
646
647 purple_debug_error("bonjour", "Error connecting to buddy %s at %s:%d error: %s\n",
648 purple_buddy_get_name(pb), bb->ip ? bb->ip : "(null)", bb->port_p2pj, error ? error : "(null)");
649
650 conv = purple_find_conversation_with_account(PURPLE_CONV_TYPE_IM, bb->name, pb->account);
651 if (conv != NULL)
652 purple_conversation_write(conv, NULL,
653 _("Unable to send the message, the conversation couldn't be started."),
654 PURPLE_MESSAGE_SYSTEM, time(NULL));
655
656 bonjour_jabber_close_conversation(bb->conversation);
657 bb->conversation = NULL;
658 return;
659 }
660
661 len = strlen(stream_start);
662
663 /* Start the stream and send queued messages */
664 ret = send(source, stream_start, len, 0);
665
666 if (ret == -1 && errno == EAGAIN)
667 ret = 0;
668 else if (ret <= 0) {
669 const char *err = strerror(errno);
670 PurpleConversation *conv;
671
672 purple_debug_error("bonjour", "Error starting stream with buddy %s at %s:%d error: %s\n",
673 purple_buddy_get_name(pb), bb->ip ? bb->ip : "(null)", bb->port_p2pj, err ? err : "(null)");
674
675 conv = purple_find_conversation_with_account(PURPLE_CONV_TYPE_IM, bb->name, pb->account);
676 if (conv != NULL)
677 purple_conversation_write(conv, NULL,
678 _("Unable to send the message, the conversation couldn't be started."),
679 PURPLE_MESSAGE_SYSTEM, time(NULL));
680
681 close(source);
682 bonjour_jabber_close_conversation(bb->conversation);
683 bb->conversation = NULL;
684
685 g_free(stream_start);
686
687 return;
688 }
689
690 bb->conversation->socket = source;
691 bb->conversation->rx_handler = purple_input_add(source,
692 PURPLE_INPUT_READ, _client_socket_handler, pb);
693
694 /* This is unlikely to happen */
695 if (ret < len) {
696 struct _stream_start_data *ss = g_new(struct _stream_start_data, 1);
697 ss->msg = g_strdup(stream_start + ret);
698 ss->tx_handler_cb = _send_data_write_cb;
699 bb->conversation->stream_data = ss;
700 /* Finish sending the stream start */
701 bb->conversation->tx_handler = purple_input_add(source,
702 PURPLE_INPUT_WRITE, _start_stream, pb);
703 }
704 /* Process the send buffer */
705 else {
706 bb->conversation->stream_started = TRUE;
707 /* Watch for when we can write the buffered messages */
708 bb->conversation->tx_handler = purple_input_add(source, PURPLE_INPUT_WRITE,
709 _send_data_write_cb, pb);
710 /* We can probably write the data now. */
711 _send_data_write_cb(pb, source, PURPLE_INPUT_WRITE);
712 }
713
714 g_free(stream_start);
715 }
716
522 int 717 int
523 bonjour_jabber_send_message(BonjourJabber *data, const gchar *to, const gchar *body) 718 bonjour_jabber_send_message(BonjourJabber *data, const gchar *to, const gchar *body)
524 { 719 {
525 xmlnode *message_node, *node, *node2; 720 xmlnode *message_node, *node, *node2;
526 gchar *message; 721 gchar *message;
538 bb = pb->proto_data; 733 bb = pb->proto_data;
539 734
540 /* Check if there is a previously open conversation */ 735 /* Check if there is a previously open conversation */
541 if (bb->conversation == NULL) 736 if (bb->conversation == NULL)
542 { 737 {
543 int socket = _connect_to_buddy(pb); 738 PurpleProxyConnectData *connect_data;
544 if (socket < 0) 739
740 /* Make sure that the account always has a proxy of "none".
741 * This is kind of dirty, but proxy_connect_none() isn't exposed. */
742 static PurpleProxyInfo *tmp_none_proxy_info = NULL;
743 if (!tmp_none_proxy_info) {
744 tmp_none_proxy_info = purple_proxy_info_new();
745 purple_proxy_info_set_type(tmp_none_proxy_info, PURPLE_PROXY_NONE);
746 }
747 purple_account_set_proxy_info(data->account, tmp_none_proxy_info);
748
749 connect_data =
750 purple_proxy_connect(data->account->gc, data->account, bb->ip,
751 bb->port_p2pj, _connected_to_buddy, pb);
752
753 if (connect_data == NULL) {
754 purple_debug_error("bonjour", "Unable to connect to buddy (%s).\n", to);
545 return -10001; 755 return -10001;
756 }
546 757
547 bb->conversation = bonjour_jabber_conv_new(); 758 bb->conversation = bonjour_jabber_conv_new();
548 bb->conversation->socket = socket; 759 bb->conversation->connect_data = connect_data;
549 bb->conversation->watcher_id = purple_input_add(bb->conversation->socket, 760 /* We don't want _send_data() to register the tx_handler;
550 PURPLE_INPUT_READ, _client_socket_handler, pb); 761 * that neeeds to wait until we're actually connected. */
762 bb->conversation->tx_handler = 0;
551 } 763 }
552 764
553 message_node = xmlnode_new("message"); 765 message_node = xmlnode_new("message");
554 xmlnode_set_attrib(message_node, "to", bb->name); 766 xmlnode_set_attrib(message_node, "to", bb->name);
555 xmlnode_set_attrib(message_node, "from", purple_account_get_username(data->account)); 767 xmlnode_set_attrib(message_node, "from", purple_account_get_username(data->account));
572 784
573 node = xmlnode_new_child(message_node, "x"); 785 node = xmlnode_new_child(message_node, "x");
574 xmlnode_set_namespace(node, "jabber:x:event"); 786 xmlnode_set_namespace(node, "jabber:x:event");
575 xmlnode_insert_child(node, xmlnode_new("composing")); 787 xmlnode_insert_child(node, xmlnode_new("composing"));
576 788
577
578 message = xmlnode_to_str(message_node, NULL); 789 message = xmlnode_to_str(message_node, NULL);
579 xmlnode_free(message_node); 790 xmlnode_free(message_node);
580 791
581 /* Check if the stream for the conversation has been started */ 792 ret = _send_data(pb, message) >= 0;
582 if (bb->conversation->stream_started == FALSE) 793
583 {
584 char *stream_start = g_strdup_printf(DOCTYPE, purple_account_get_username(pb->account),
585 purple_buddy_get_name(pb));
586 /* Start the stream */
587 if (send(bb->conversation->socket, stream_start, strlen(stream_start), 0) == -1)
588 {
589 PurpleConversation *conv;
590
591 purple_debug_error("bonjour", "Unable to start a conversation\n");
592 purple_debug_warning("bonjour", "send error: %s\n", strerror(errno));
593 conv = purple_find_conversation_with_account(PURPLE_CONV_TYPE_IM, bb->name, data->account);
594 purple_conversation_write(conv, NULL,
595 _("Unable to send the message, the conversation couldn't be started."),
596 PURPLE_MESSAGE_SYSTEM, time(NULL));
597
598 bonjour_jabber_close_conversation(bb->conversation);
599 bb->conversation = NULL;
600
601 g_free(message);
602 g_free(stream_start);
603 return 0;
604 }
605
606 g_free(stream_start);
607 bb->conversation->stream_started = TRUE;
608 }
609
610 /* Send the message */
611 ret = (_send_data(bb->conversation->socket, message) == -1);
612 g_free(message); 794 g_free(message);
613 795
614 if (ret == -1) 796 return ret;
615 return -10000;
616
617 return 1;
618 } 797 }
619 798
620 void 799 void
621 bonjour_jabber_close_conversation(BonjourJabberConversation *bconv) 800 bonjour_jabber_close_conversation(BonjourJabberConversation *bconv)
622 { 801 {
628 if (bconv->stream_started) 807 if (bconv->stream_started)
629 send(bconv->socket, STREAM_END, strlen(STREAM_END), 0); 808 send(bconv->socket, STREAM_END, strlen(STREAM_END), 0);
630 /* TODO: We're really supposed to wait for "</stream:stream>" before closing the socket */ 809 /* TODO: We're really supposed to wait for "</stream:stream>" before closing the socket */
631 close(bconv->socket); 810 close(bconv->socket);
632 } 811 }
633 purple_input_remove(bconv->watcher_id); 812 if (bconv->rx_handler != -1)
813 purple_input_remove(bconv->rx_handler);
814 if (bconv->tx_handler > 0)
815 purple_input_remove(bconv->tx_handler);
634 816
635 /* Free all the data related to the conversation */ 817 /* Free all the data related to the conversation */
818 purple_circ_buffer_destroy(bconv->tx_buf);
819 if (bconv->connect_data != NULL)
820 purple_proxy_connect_cancel(bconv->connect_data);
821 if (bconv->stream_data != NULL) {
822 struct _stream_start_data *ss = bconv->stream_data;
823 g_free(ss->msg);
824 g_free(ss);
825 }
636 g_free(bconv); 826 g_free(bconv);
637 } 827 }
638 } 828 }
639 829
640 void 830 void
655 for (l = buddies; l; l = l->next) { 845 for (l = buddies; l; l = l->next) {
656 BonjourBuddy *bb = ((PurpleBuddy*) l->data)->proto_data; 846 BonjourBuddy *bb = ((PurpleBuddy*) l->data)->proto_data;
657 bonjour_jabber_close_conversation(bb->conversation); 847 bonjour_jabber_close_conversation(bb->conversation);
658 bb->conversation = NULL; 848 bb->conversation = NULL;
659 } 849 }
850
660 g_slist_free(buddies); 851 g_slist_free(buddies);
661 } 852 }
662 } 853 }