Mercurial > pidgin.yaz
comparison libpurple/protocols/bonjour/jabber.c @ 17796: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
17795:2af1f8ccd396 | 17796: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 } |